蓝牙低功耗概述
Qt Bluetooth 低功耗API支持外设/服务器和中心/客户端两种角色。该API在所有主流Qt平台上均受支持,唯一的例外是Windows平台不支持外设角色。
什么是蓝牙低功耗
蓝牙低功耗(Bluetooth Low Energy),也称为蓝牙智能(Bluetooth Smart),是一种无线计算机网络技术,于 2011 年正式推出。它与“经典”蓝牙一样,工作在 2.4 GHz 频率上。 主要区别在于,正如其技术名称所示,它具有低能耗的特点。这使得蓝牙低功耗设备仅靠一枚纽扣电池即可持续运行数月甚至数年。该技术由蓝牙 v4.0 引入。支持该技术的设备被称为“蓝牙智能就绪设备”。该技术的关键特性包括:
- 峰值、平均及空闲模式下功耗极低
- 能够使用标准纽扣电池持续运行数年
- 低成本
- 多厂商互操作性
- 增强的通信距离
蓝牙低功耗(Bluetooth Low Energy)采用客户端-服务器架构。服务器(也称为外设)提供温度或心率等服务,并向外广播这些服务。客户端(称为中央设备)连接到服务器,并读取服务器广播的数值。 例如,一套公寓中安装了蓝牙智能就绪(Bluetooth Smart Ready)传感器,如温控器、湿度传感器或压力传感器。这些传感器作为外设,会广播公寓内的环境参数。与此同时,手机或电脑可以连接到这些传感器,获取相关数据,并将其作为更大规模的环境控制应用程序的一部分呈现给用户。
基本服务结构
蓝牙低功耗(Bluetooth Low Energy)基于两种协议:ATT(属性协议)和 GATT(通用属性配置文件)。它们规定了每个蓝牙智能就绪设备所使用的通信层。
ATT 协议
ATT 的基本构成单元是属性。每个属性由三个元素组成:
- 一个值——有效载荷或所需的信息
- 一个 UUID——属性的类型(由 GATT 使用)
- 一个 16 位句柄——属性的唯一标识符
服务器存储这些属性,客户端则使用 ATT 协议在服务器上读取和写入值。
GATT 配置文件
GATT 通过为预定义的 UUID 赋予特定含义,对一组属性进行分组。下表展示了一个在特定日期暴露心率数据的服务示例。实际值存储在以下两个特征中:
| 标识符 | UUID | 值 | 描述 |
|---|---|---|---|
| 0x0001 | 0x2800 | UUID 0x180D | 开始心率服务 |
| 0x0002 | 0x2803 | UUID 0x2A37,值句柄:0x0003 | 心率测量(HRM)类型的特征 |
| 0x0003 | 0x2A37 | 65 次/分钟 | 心率值 |
| 0x0004 | 0x2803 | UUID 0x2A08,值句柄:0x0005 | 日期时间类型的特征 |
| 0x0005 | 0x2A08 | 2014年8月18日 11:00 | 测量日期和时间 |
| 0x0006 | 0x2800 | UUID xxxxxx | 开始下一次服务 |
| ... | ... | ... | ... |
GATT规定,上述使用的UUID0x2800 标志着服务定义的开始。 紧随0x2800 之后的每个属性均属于该服务,直至遇到下一个0x2800 或到达服务结尾。同样地,众所周知的 UUID0x2803 表示将出现一个特征,且每个特征都有一个类型来定义其值的性质。上例中使用了 UUID0x2A08 (日期时间)和0x2A37 (心率测量)。 上述每个 UUID 均由蓝牙特别兴趣小组 (Bluetooth Special Interest Group) 定义,并可在GATT 规范中找到。虽然建议在可用的情况下使用预定义的 UUID,但完全可以使用新的、尚未被使用的 UUID 来表示特征和服务类型。
通常,每项服务可能包含一个或多个特征。特征包含数据,并可通过描述符进行进一步描述,这些描述符提供额外信息或操作该特征的手段。 所有服务、特征和描述符均通过其 128 位 UUID 进行识别。最后,可以在服务内部嵌套其他服务(参见下图)。

使用Qt Bluetooth 低功耗API
本节介绍如何使用 Qt Bluetooth 提供的蓝牙低功耗 API。在客户端,该 API 允许建立与外设的连接、发现其服务,以及读取和写入设备上存储的数据。在服务器端,它允许设置服务、广播服务,并在客户端写入特征时接收通知。 下面的示例代码摘自“心率游戏”和“心率服务器”示例。
建立连接
为了能够读写蓝牙低功耗外设的特征值,必须先查找并连接该设备。这需要外设广播其存在状态和服务。我们借助QBluetoothDeviceDiscoveryAgent 类开始设备发现。我们订阅其QBluetoothDeviceDiscoveryAgent::deviceDiscovered()信号,并通过start()启动搜索:
m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
m_deviceDiscoveryAgent->setLowEnergyDiscoveryTimeout(15000);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered,
this, &DeviceFinder::addDevice);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::errorOccurred,
this, &DeviceFinder::scanError);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished,
this, &DeviceFinder::scanFinished);
connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::canceled,
this, &DeviceFinder::scanFinished);
m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);由于我们仅关注低功耗设备,因此会在接收槽中过滤设备类型。设备类型可通过QBluetoothDeviceInfo::coreConfigurations()的标志来确定。随着更多详细信息的发现,同一设备可能会多次触发deviceDiscovered()信号。在此,我们对这些设备发现进行匹配,以便用户仅看到单个设备:
void DeviceFinder::addDevice(const QBluetoothDeviceInfo &device)
{
// If device is LowEnergy-device, add it to the list
if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) {
auto devInfo = new DeviceInfo(device);
auto it = std::find_if(m_devices.begin(), m_devices.end(),
[devInfo](DeviceInfo *dev) {
return devInfo->getAddress() == dev->getAddress();
});
if (it == m_devices.end()) {
m_devices.append(devInfo);
} else {
auto oldDev = *it;
*it = devInfo;
delete oldDev;
}
setInfo(tr("Low Energy device found. Scanning more..."));
setIcon(IconProgress);
}
//...
}一旦得知外设的地址,我们就使用QLowEnergyController 类。该类是所有蓝牙低功耗开发的入口点。该类的构造函数接受远程设备的QBluetoothAddress 值。最后,我们设置常规槽,并使用connectToDevice() 直接连接到该设备:
m_control = QLowEnergyController::createCentral(m_currentDevice->getDevice(), this);
connect(m_control, &QLowEnergyController::serviceDiscovered,
this, &DeviceHandler::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished,
this, &DeviceHandler::serviceScanDone);
connect(m_control, &QLowEnergyController::errorOccurred, this,
[this](QLowEnergyController::Error error) {
Q_UNUSED(error);
setError("Cannot connect to remote device.");
setIcon(IconError);
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
setInfo("Controller connected. Search services...");
setIcon(IconProgress);
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
setError("LowEnergy controller disconnected");
setIcon(IconError);
});
// Connect
m_control->connectToDevice();服务搜索
上述代码片段展示了应用程序在建立连接后如何启动服务发现。
下方的serviceDiscovered() 槽是由QLowEnergyController::serviceDiscovered()信号触发的,并提供间歇性的进度报告。由于我们讨论的是用于监视附近HeartRate设备的“心率监听器”应用,因此我们会忽略所有非QBluetoothUuid::ServiceClassUuid::HeartRate 类型的服务。
void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt)
{
if (gatt == QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate)) {
setInfo("Heart Rate service discovered. Waiting for service scan to be done...");
setIcon(IconProgress);
m_foundHeartRateService = true;
}
}最终,系统会发出QLowEnergyController::discoveryFinished()信号,以指示服务发现已成功完成。如果找到了HeartRate服务,则会创建一个QLowEnergyService 实例来表示该服务。返回的服务对象提供了用于更新通知所需的信号,并且可通过调用QLowEnergyService::discoverDetails()来触发服务详细信息的发现:
// If heartRateService found, create new service
if (m_foundHeartRateService)
m_service = m_control->createServiceObject(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate), this);
if (m_service) {
connect(m_service, &QLowEnergyService::stateChanged, this, &DeviceHandler::serviceStateChanged);
connect(m_service, &QLowEnergyService::characteristicChanged, this, &DeviceHandler::updateHeartRateValue);
connect(m_service, &QLowEnergyService::descriptorWritten, this, &DeviceHandler::confirmedDescriptorWrite);
m_service->discoverDetails();
} else {
setError("Heart Rate Service not found.");
setIcon(IconError);
}在详细搜索过程中,该服务的state()状态会从RemoteService 过渡到RemoteServiceDiscovering ,最终以RemoteServiceDiscovered 结束:
void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s)
{
switch (s) {
case QLowEnergyService::RemoteServiceDiscovering:
setInfo(tr("Discovering services..."));
setIcon(IconProgress);
break;
case QLowEnergyService::RemoteServiceDiscovered:
{
setInfo(tr("Service discovered."));
setIcon(IconBluetooth);
const QLowEnergyCharacteristic hrChar =
m_service->characteristic(QBluetoothUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement));
if (!hrChar.isValid()) {
setError("HR Data not found.");
setIcon(IconError);
break;
}
m_notificationDesc = hrChar.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration);
if (m_notificationDesc.isValid())
m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100"));
break;
}
default:
//nothing for now
break;
}
emit aliveChanged();
}与外设的交互
在上面的代码示例中,所需的特征类型为HeartRateMeasurement 。由于应用程序需监测心率变化,因此必须为该特征启用变更通知。 请注意,并非所有特征都提供变更通知。由于 HeartRate 特征已标准化,因此可以假设能够接收通知。最终,必须为QLowEnergyCharacteristic::properties() 设置QLowEnergyCharacteristic::Notify 标志,并且必须存在类型为QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration 的描述符,以确认可获得相应的通知。
最后,我们根据蓝牙低功耗(Bluetooth Low Energy)标准处理 HeartRate 特征的值:
void DeviceHandler::updateHeartRateValue(const QLowEnergyCharacteristic &c, const QByteArray &value)
{
// ignore any other characteristic change -> shouldn't really happen though
if (c.uuid() != QBluetoothUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement))
return;
auto data = reinterpret_cast<const quint8 *>(value.constData());
quint8 flags = *data;
//Heart Rate
int hrvalue = 0;
if (flags & 0x1) // HR 16 bit? otherwise 8 bit
hrvalue = static_cast<int>(qFromLittleEndian<quint16>(data[1]));
else
hrvalue = static_cast<int>(data[1]);
addMeasurement(hrvalue);
}通常,特征值是一系列字节。这些字节的具体解释取决于特征类型和值结构。其中相当一部分已由蓝牙特别兴趣小组(Bluetooth SIG)标准化,而其他部分可能遵循自定义协议。上面的代码片段演示了如何读取标准化的 HeartRate 值。
广播服务
如果要在外设上实现 GATT 服务器应用程序,我们需要定义要向中央设备提供的服务,并对其进行广播:
QLowEnergyAdvertisingData advertisingData;
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
advertisingData.setIncludePowerLevel(true);
advertisingData.setLocalName("HeartRateServer");
advertisingData.setServices(QList<QBluetoothUuid>()<<QBluetoothUuid::ServiceClassUuid::HeartRate);
boolerrorOccurred= false;
conststd::unique_ptr<QLowEnergyController>leController(QLowEnergyController::createPeripheral());
autoerrorHandler= [&leController, &errorOccurred](QLowEnergyController::Error errorCode) {
qWarning().noquote().nospace() << errorCode << " occurred: "
<< leController->errorString();
if(errorCode!=QLowEnergyController::RemoteHostClosedError) {
qWarning("Heartrate-server quitting due to the error.");
errorOccurred= true;
QCoreApplication::quit();
}
};
QObject::connect(leController.get(), &QLowEnergyController::errorOccurred,errorHandler);
std::unique_ptr<QLowEnergyService>service(leController->addService(serviceData));
leController->startAdvertising(QLowEnergyAdvertisingParameters(),advertisingData,
advertisingData);
if(errorOccurred)
return-1;现在,潜在客户端可以连接到我们的设备,发现提供的服务,并注册以接收特性值变化的通知。API 的这一部分已在上述章节中介绍过。
在外设上实现服务
第一步是定义服务、其特征及描述符。这通过QLowEnergyServiceData 、QLowEnergyCharacteristicData 和QLowEnergyDescriptorData 类来实现。这些类作为容器或构建模块,用于存储构成待定义蓝牙低功耗服务的基本信息。下面的代码片段定义了一个简单的HeartRate服务,该服务发布测得的每分钟心跳次数。 此类服务的一个应用示例是腕表。
QLowEnergyCharacteristicData charData;
charData.setUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement);
charData.setValue(QByteArray(2, 0));
charData.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfig(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charData.addDescriptor(clientConfig);
QLowEnergyServiceData serviceData;
serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceData.setUuid(QBluetoothUuid::ServiceClassUuid::HeartRate);
serviceData.addCharacteristic(charData);生成的serviceData 对象可按照上文“广播服务”部分所述进行发布。尽管QLowEnergyServiceData 和QLowEnergyAdvertisingData 封装的信息存在部分重叠,但这两个类承担着截然不同的任务。广播数据会发布给附近的设备,且由于 29 字节的大小限制,其范围通常受到限制。 因此,这些数据并不总是100%完整的。相比之下,QLowEnergyServiceData 中包含的服务数据提供了完整的服务数据集,且仅在与正在进行服务发现的活跃服务建立连接后,才会对连接的客户端可见。
下一节将演示该服务如何更新心率值。 根据服务的性质,它可能需要符合https://www.bluetooth.org 上定义的官方服务规范。其他服务则可能是完全自定义的。本次采用了心率服务,其规范可参见https://www.bluetooth.com/specifications/adopted-specifications。
QTimer heartbeatTimer;
quint8 currentHeartRate = 60;
enum ValueChange { ValueUp, ValueDown } valueChange = ValueUp;
const auto heartbeatProvider = [&service, ¤tHeartRate, &valueChange]() {
QByteArray value;
value.append(char(0)); // Flags that specify the format of the value.
value.append(char(currentHeartRate)); // Actual value.
QLowEnergyCharacteristic characteristic
= service->characteristic(QBluetoothUuid::CharacteristicType::HeartRateMeasurement);
Q_ASSERT(characteristic.isValid());
service->writeCharacteristic(characteristic, value); // Potentially causes notification.
if (currentHeartRate == 60)
valueChange = ValueUp;
else if (currentHeartRate == 100)
valueChange = ValueDown;
if (valueChange == ValueUp)
++currentHeartRate;
else
--currentHeartRate;
};
QObject::connect(&heartbeatTimer, &QTimer::timeout, heartbeatProvider);
heartbeatTimer.start(1000);通常情况下,外设设备上的特征值和描述符值的更新使用与连接蓝牙低功耗设备相同的方法。
注意:要在 Qt Bluetooth (无论处于中心角色还是外设角色),必须提供一个包含使用描述的 Info.plist 文件。根据 CoreBluetooth 文档:如果应用的 Info.plist 中未包含其需要访问的数据类型对应的使用描述键,应用将会崩溃。 要在 iOS 13 及更高版本上链接的应用程序中访问 Core Bluetooth API,请包含 NSBluetoothAlwaysUsageDescription 键。在 iOS 12 及更早版本中,请包含 NSBluetoothPeripheralUsageDescription 键以访问蓝牙外设数据。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.