低功耗蓝牙比经典蓝牙复杂些,需要了解一些协议的基础知识。
此部分参考博客GATT Profile 简介-CSDN博客
GATT详细介绍-CSDN博客
Introduction | Introduction to Bluetooth Low Energy | Adafruit Learning System
蓝牙 (四) GATT profile-CSDN博客
低功耗蓝牙(BLE),有时被称为“智能蓝牙”,是经典蓝牙的轻量级子集,作为蓝牙4.0核心规范的一部分引入。虽然与传统蓝牙有一些重叠,但BLE实际上有一个完全不同的血统,在被蓝牙技术联盟采用之前,它是由诺基亚作为一个内部项目开始的,名为“Wibree”。
支持蓝牙4.0和蓝牙低功耗(这是BT 4.0的一个子集)在大多数主要平台上可用,如下所列的版本:
GAP(Generic Access Profile),它在用来控制设备连接和广播。外围设备发出广播,中心设备扫描接收到附近外围设备发出的广播,可选择连接。外围设备和中心设备是GAP定义的角色,一个外围设备只能连接一个中心设备,但是一个中心设备可连接多个外围设备。
GAP 给设备定义了若干角色,其中主要的两个是:外围设备(Peripheral)和中心设备(Central)。
外围设备(Peripheral):这一般就是非常小或者简单的低功耗设备,用来提供数据,并连接到一个更加相对强大的中心设备。例如小米手环。
中心设备(Central):中心设备相对比较强大,用来连接其他外围设备。例如手机等。
GATT 的全名是 Generic Attribute Profile(通用属性协议,基本属性协议, ATT 指Attribute-属性),它定义了 两个 BLE 设备互相传输数据进行通信的方法,也就是说中心设备和外围设备通过GAP创建连接后,然后通过GATT协议进行通信。该方法用了两个概念, Service 和 Characteristic 。GATT 使用 ATT(Attribute Protocol)协议,ATT 协议把 Service, Characteristic以及相关的数据存储在一个简单的有索引的表中,这个索引表使用 16 位的 ID 作为表中每一项条目的索引。
GATT 通信的双方是 C/S 关系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义。中心设备是 GATT 客户端(Client),它向 Server 发起请求。需要注意的是,所有的通信事件,都是由客户端(也叫主设备,Master)发起,并且接收服务端(也叫从设备,Slave)的响应。
GATT 事务是建立在嵌套的Profiles, Services 和 Characteristics之上的的,如下图所示:
Profile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。例如心率Profile(Heart Rate Profile)就是结合了 Heart Rate Service 和 Device Information Service。所有官方通过 GATT Profile 的列表可以从这里找到。
Service 是把数据分成一个个的独立逻辑项,它包含一个或者多个 Characteristic。每个 Service 有一个 UUID 唯一标识。 UUID 有 16 bit 的,或者 128 bit 的。16 bit 的 UUID 是官方通过认证的,需要花钱购买,128 bit 是自定义的,这个就可以自己随便设置。
Characteristic(特征)在 GATT 事务中的最低界别的是 Characteristic,Characteristic 是最小的逻辑数据单元,与 Service 类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一标识。你可以免费使用 Bluetooth SIG 官方定义的标准 Characteristic,使用官方定义的,可以确保 BLE 的软件和硬件能相互理解。当然,你可以自定义 Characteristic,这样的话,就只有你自己的软件和外设能够相互理解。
Characteristic(特征)其实是个集合,包含以下子元素:
其中描述符是可选项,可能包含一个或多个描述符,也可能不包含描述符。
Characteristic(特征)是BLE 设备与外界通信的接口,当手机与BLE 设备通信,其实都是与某个具体的特征进行读写。其中读写的就是Characteristic Value(特征值)。
规定数据按照一定规则存放,这个规则就是属性。
服务(Services)、特性(Characteristics)和描述符(Descriptors)都是属性类别,因此也就有了通用属性配置文件(Generic Attribute Profile)、属性表(Attribute Table)和属性协议(Attribute Protocol)等。具体是哪一个类别的属性,由“通用唯一标识符(Universally Unique Identifier,简称UUID)”来定义。
服务(Service)、特性(Characteristics)和描述符(Descriptors)也有层级之分:服务包括一项或多项特性;一项特性可能没有、拥有一个或拥有多个描述符
Atrribute规则结构:
属性句柄(2个字节) + 属性类型(2个字节) + 属性值(0-512个字节) + 属性权限
0x1800~0x26FF 用于服务类 UUID
0x2700~0x27FF 用于标识计量单位
0x2800~0x28FF 用于区分属性类型
0x2900~0x29FF 用于特性描述
0x2A00~0x7FFF 用于区分特性类型
服务通用唯一识别码(UUID) // 1800
单位
属性类型
特性描述符
特性类型
在了解完以上的知识后,根据需求(在安卓系统平板上开发,收发数据),我需要开发的角色是中心设备(Central),工作流程如下:
m_pDevDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(m_pDevDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &BluetoothDevice::onDeviceDiscovered);
connect(m_pDevDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &BluetoothDevice::onDiscoverFinished);
connect(m_pDevDiscoveryAgent, QOverload::of(&QBluetoothDeviceDiscoveryAgent::error),
this, &BluetoothDevice::onDiscoverFinished);
//开始查找BLE设备
m_pDevDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
//因为我做的连接逻辑是只要没连接上,就不停的尝试连接,
//注意的是即使没连接上也是需要关闭连接,释放内存的。
close();
//获取本地设备地址信息,创建低功耗控制器需要
QList localdevices = QBluetoothLocalDevice::allDevices();
if(localdevices.size() == 0)
{
return;
}
QBluetoothAddress localAddr = localdevices[0].address();
//初始化 strAddr为需要连接的外围设备的MAC地址
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
m_BLEController = new QLowEnergyController(QBluetoothAddress(strAddr), localAddr);
#else
m_BLEController = QLowEnergyController::createCentral(QBluetoothAddress(strAddr), localAddr);
#endif
connect(m_BLEController, &QLowEnergyController::connected, m_BLEController, &QLowEnergyController::discoverServices);
connect(m_BLEController, QOverload::of(&QLowEnergyController::error), this, &BluetoothConnection::onErrorOccurred);
connect(m_BLEController, &QLowEnergyController::serviceDiscovered, this, &BluetoothConnection::BLEC_onServiceDiscovered);
//尝试连接
m_BLEController->connectToDevice();
void BluetoothConnection::BLEC_onServiceDiscovered(const QBluetoothUuid &serviceUUID)
{
if(serviceUUID == QBluetoothUuid(TxServerUUID))
{//write
m_BLETxService = m_BLEController->createServiceObject(serviceUUID,this);
connect(m_BLETxService, &QLowEnergyService::stateChanged, this, &BluetoothConnection::BLEC_onServiceDetailDiscovered);
m_BLETxService->discoverDetails();
}
else if(serviceUUID == QBluetoothUuid(RxServerUUID))
{//notify
m_BLERxService = m_BLEController->createServiceObject(serviceUUID,this);
connect(m_BLERxService, &QLowEnergyService::stateChanged, this, &BluetoothConnection::BLEC_onServiceDetailDiscovered);
m_BLERxService->discoverDetails();
}
}
读特征需要监听数据、状态改变和错误信息(中心设备监听外围设备发来的数据);
写特征需要保存下指针,便于后续的写操作(中心设备将数据发送给外围设备),
在以上的操作执行完成后,才算连接成功了。
void BluetoothConnection::BLEC_onServiceDetailDiscovered(QLowEnergyService::ServiceState newState)
{
bool deleteService = true;
auto service = qobject_cast(sender());
if(newState == QLowEnergyService::ServiceDiscovered)
{
deleteService = true;
const QList chars = service->characteristics();
// delete unused service
if(service != m_BLERxService && service != m_BLETxService)
{
deleteService = true;
}
else if(service == m_BLERxService)
{
for(auto it = chars.cbegin(); it != chars.cend(); ++it)
{
if(!m_BLERxCharacteristicValid && it->uuid() == QBluetoothUuid(RxBLECharacteristicUUID)
&& it->properties().testFlag(QLowEnergyCharacteristic::Notify))
{
m_BLERxCharacteristicValid = true;
deleteService = false;
}
}
}
else if(service == m_BLETxService)
{
for(auto it = chars.cbegin(); it != chars.cend(); ++it)
{
if(!m_BLETxCharacteristicValid && it->uuid() == QBluetoothUuid(TxBLECharacteristicUUID)
&& it->properties().testFlag(QLowEnergyCharacteristic::Write))
{
m_BLETxCharacteristicValid = true;
deleteService = false;
}
}
}
if(!deleteService)
{
if(m_BLERxCharacteristicValid && m_BLETxCharacteristicValid)
{
// Rx
connect(m_BLERxService, QOverload::of(&QLowEnergyService::error), this, &BluetoothConnection::onErrorOccurred);
connect(m_BLERxService, &QLowEnergyService::characteristicChanged, this, &BluetoothConnection::BLEC_onDataArrived);
connect(m_BLERxService, &QLowEnergyService::characteristicRead, this, &BluetoothConnection::BLEC_onDataArrived);
QLowEnergyDescriptor desc = m_BLERxService->characteristic(QBluetoothUuid(RxBLECharacteristicUUID)).descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
m_BLERxService->writeDescriptor(desc, QByteArray::fromHex("0100"));
// Tx
connect(m_BLETxService, QOverload::of(&QLowEnergyService::error), this, &BluetoothConnection::onErrorOccurred);
m_BLETxCharacteristic = m_BLETxService->characteristic(QBluetoothUuid(TxBLECharacteristicUUID));
onConnected();
}
}
else
{
if(service == m_BLERxService) // characteristic not found
{
m_BLERxService = nullptr;
onDisconnect();
}
else if(service == m_BLETxService) // characteristic not found
{
m_BLETxService = nullptr;
onDisconnect();
}
service->deleteLater();
}
}
}
读:
QByteArray m_buf ;
void BluetoothConnection::onReadyRead()
{
qDebug()<<"BluetoothConnection::onReadyRead";
m_buf += m_pSocket->readAll();
emit readyRead();
}
写:
qint64 BluetoothConnection::write(const char *data, qint64 len)
{
if(m_BLETxService == nullptr)
{
return 0;
}
m_BLETxService->writeCharacteristic(m_BLETxCharacteristic,
QByteArray::fromRawData(data, len));
return len;
}
报错后的处理:
void BluetoothConnection::onErrorOccurred()
{
if(sender() == m_BLEController)
{
QLowEnergyController::Error error;
error = m_BLEController->error();
qDebug() << "BLE Central Controller Error:" << error << m_BLEController->errorString();
qDebug() << "State:" << m_BLEController->state();
if(error == QLowEnergyController::NoError)
;
else
{
if(m_BLEController->state() == QLowEnergyController::ConnectingState)
{
emit connectFailed(tr("Controller Error: ")+m_BLEController->errorString());
}
close();
}
}
else if(sender() == m_BLERxService || sender() == m_BLETxService)
{
QLowEnergyService* service = qobject_cast(sender());
QLowEnergyService::ServiceError error;
error = service->error();
// service->errorString() doesn't exist
qDebug() << "BLE Central Service Error:" << error;
qDebug() << "State:" << service->state();
if(error == QLowEnergyService::NoError)
;
else if(error == QLowEnergyService::CharacteristicReadError || error == QLowEnergyService::CharacteristicWriteError || error == QLowEnergyService::DescriptorReadError || error == QLowEnergyService::DescriptorWriteError)
;
else
{
if(service->state() == QLowEnergyService::DiscoveringServices)
emit connectFailed(tr("Service Error: ")
+ QString::fromUtf8(QMetaEnum::fromType().valueToKey(error)));
close();
}
}
}
void BluetoothConnection::close()
{
if(m_BLEController == nullptr)
return;
if(m_BLEController->state() == QLowEnergyController::UnconnectedState)
{
return;
}
m_BLERxCharacteristicValid = false;
m_BLETxCharacteristicValid = false;
if(m_BLERxService != nullptr)
{
QLowEnergyDescriptor desc = m_BLERxService->characteristic(QBluetoothUuid(RxBLECharacteristicUUID))
.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration);
m_BLERxService->writeDescriptor(desc, QByteArray::fromHex("0000"));
m_BLERxService->deleteLater();
m_BLERxService = nullptr;
}
if(m_BLETxService != nullptr)
{
m_BLETxService->deleteLater();
m_BLETxService = nullptr;
}
if(m_BLEController != nullptr)
{
m_BLEController->disconnectFromDevice();
m_BLEController->deleteLater();
m_BLEController = nullptr;
}
onDisconnect();
}
在连接成功或者关闭后,向外发送信号
void BluetoothConnection::onConnected()
{
emit connected();
}
void BluetoothConnection::onDisconnect()
{
emit disconnected();
}
Android BLE 开发,GATT报错 status 133全面解析_gatt 133-CSD博客