蓝牙通信基础与GATT协议
各位同学好,我是你们的老朋友。今天咱们正式开始《美容仪APP联调与指令对接实战》的第一章。
说实话,做蓝牙开发这么多年,我见过太多人一上来就急着写代码,结果连设备都搜不到,或者连上了发指令没反应。为什么?说白了,就是没搞懂底层的通信机制。今天这一章,咱们就把BLE蓝牙低功耗协议栈的底裤扒开,重点讲讲GATT协议——这个在美容仪开发中你天天都要打交道的东西。
1.1 BLE协议栈长什么样?
先看整体。BLE协议栈分两层:Controller和Host。中间有个HCI接口隔开。
Controller负责物理层和链路层,说白了就是管怎么发无线电波、怎么组包、怎么重传。这部分一般由芯片厂家搞定,咱们APP开发者基本不用碰。
Host层才是咱们的主战场。它包含L2CAP、ATT、GATT、SM、GAP等模块。嗯,这里要特别注意GATT和GAP的区别——GAP管的是设备怎么被发现、怎么连接;GATT管的是连接之后怎么交换数据。
我个人习惯把GATT比作一个「文件系统」。你想想看,手机连上美容仪之后,总得知道怎么读写数据吧?GATT就是定义了这套读写规则。
1.2 GATT的核心结构
GATT的结构其实很简单,就三层:
- Service(服务):相当于一个文件夹
- Characteristic(特征):相当于文件夹里的文件
- UUID(通用唯一标识符):相当于文件名
我在项目中遇到过不少新手,一上来就问「老师,我该用哪个UUID?」。其实UUID就是身份证号,每个Service和Characteristic都有一个唯一的UUID来标识。
重要概念:一个Service包含多个Characteristic,每个Characteristic包含一个Value和若干个Descriptor。Value就是实际的数据,Descriptor描述这个Value的属性(比如能不能读、能不能写、有没有通知)。
1.3 美容仪中的典型GATT结构
拿咱们的美容仪举例。假设设备有温度控制、射频强度和电量显示三个功能。那GATT结构大概长这样:
| Service UUID | Service名称 | Characteristic UUID | 功能 | 属性 |
|---|---|---|---|---|
| 0xFFE0 | 温度服务 | 0xFFE1 | 读取当前温度 | Read / Notify |
| 0xFFE2 | 射频服务 | 0xFFE3 | 设置射频强度 | Write |
| 0xFFE4 | 电量服务 | 0xFFE5 | 读取电量百分比 | Read |
你看,每个功能对应一个Service,每个Service里放一个Characteristic。APP要读温度,就找到0xFFE1这个Characteristic,然后读它的Value。
我的经验:实际项目中,很多厂商会使用自定义的16位UUID(比如0xFFE0),而不是标准的128位UUID。这样做的好处是省流量、好记忆。但要注意,自定义UUID不能和标准服务冲突。
1.4 Characteristic的四种操作模式
Characteristic有四种基本操作,咱们必须搞清楚:
- Read:APP主动读取设备数据。比如查电量。
- Write:APP向设备写数据。比如设置温度。
- Notify:设备主动通知APP。比如温度变化时,设备推送给APP。
- Indicate:类似Notify,但需要APP确认收到。
我曾经踩过一个坑:美容仪的温度变化很快,如果用Read模式,APP需要不断轮询,既费电又容易丢数据。后来改成Notify模式,设备一有变化就主动推,体验好多了。
注意:Notify和Indicate的区别在于确认机制。Notify不保证送达,Indicate保证送达但速度慢。实时性要求高的场景(比如温度监控)用Notify;关键指令(比如关机)用Indicate。
1.5 实战:如何发现Service和Characteristic
APP连上设备后,第一步就是发现Service和Characteristic。流程是这样的:
- 调用 discoverServices() 发现所有Service
- 遍历每个Service,调用 discoverCharacteristics() 发现所有Characteristic
- 找到目标Characteristic后,设置通知或开始读写
代码示例(Android端):
// 发现服务
bluetoothGatt.discoverServices();
// 在回调中处理
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
for (BluetoothGattService service : gatt.getServices()) {
// 找到温度服务
if (service.getUuid().toString().equals("0000ffe0-0000-1000-8000-00805f9b34fb")) {
BluetoothGattCharacteristic tempChar =
service.getCharacteristic(UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
// 开启通知
gatt.setCharacteristicNotification(tempChar, true);
}
}
}
}
这段代码我几乎每个项目都会用到。你想想看,不管是什么蓝牙设备,只要连上了,第一步永远是发现服务。这是铁律。
1.6 避坑指南
最后分享几个我踩过的坑:
- UUID大小写问题:Android和iOS对UUID的大小写处理不一样。我建议统一用小写,省得排查半天。
- MTU限制:默认MTU只有23字节,实际数据只有20字节。如果要发长数据,记得先协商MTU。
- 通知开关:开启Notify后,别忘了写CCCD(客户端特征配置描述符)。很多新手漏了这一步,导致收不到数据。
好了,第一章就讲到这里。GATT协议是蓝牙开发的基石,搞懂了它,后面写指令对接就是水到渠成的事。下一章咱们聊聊「如何设计美容仪的指令协议」,到时候我会拿出一个真实项目的协议文档给大家拆解。
记住:蓝牙开发,七分靠协议,三分靠代码。协议搞懂了,代码怎么写都顺。