3、GATT层避坑:Service/Characteristic设计、MTU大小协商、通知与指示的选择
GATT层,说白了就是BLE协议栈里最贴近应用的那一层。你写的业务逻辑,最终都要通过Service和Characteristic暴露给对端。这一层要是设计不好,后面联调起来会非常痛苦。我这些年踩过的坑,有一半都跟GATT有关。
咱们直接进入正题,聊聊三个最核心的问题:怎么设计Service和Characteristic、MTU怎么协商、通知和指示到底选哪个。
3.1 Service/Characteristic设计:别把结构搞复杂了
很多人一上来就喜欢设计一个巨大的Service,里面塞十几个Characteristic。嗯,我刚开始做项目时也这么干过。结果呢?对端设备扫描服务列表时,耗时特别长,而且有些低端手机直接解析失败。
核心原则:一个Service只干一件事。
举个例子,如果你要做个智能灯,那就拆成两个Service:
- Light Control Service:开关、亮度、色温
- Device Information Service:固件版本、序列号、电量
这样设计,对端可以按需读取,不用一次性拉完所有数据。
Characteristic的设计也有讲究。我建议你遵循以下几条:
- 一个Characteristic只代表一个属性。比如“亮度”和“色温”不要放在同一个Characteristic里,否则解析逻辑会变得很复杂。
- 合理设置属性。Read、Write、Notify、Indicate这些属性别乱加。只读的就别给Write权限,否则对端写进来你还要做错误处理。
- 长度控制。单个Characteristic的值长度,尽量控制在20字节以内。为什么?因为MTU协商之前,默认MTU只有23字节,扣掉3字节的ATT头,一次最多传20字节。超过这个长度,就要走分片,效率低还容易出错。
3.2 MTU大小协商:别用默认值,但别贪心
MTU(Maximum Transmission Unit)是GATT层最重要的参数之一。默认MTU是23字节,其中ATT协议头占3字节,实际有效载荷只有20字节。如果你不做MTU协商,每次读写都只能传20字节,效率非常低。
MTU协商的流程:
- 客户端(通常是手机)发起MTU请求,告诉服务端“我能支持的最大MTU是X”。
- 服务端回复MTU响应,告诉客户端“我能支持的最大MTU是Y”。
- 双方取较小值作为本次连接的MTU。
举个例子:手机说我能支持512,设备说我能支持247,那最终MTU就是247。有效载荷变成244字节,比默认的20字节大了12倍。
MTU协商的关键点:
- MTU协商必须在连接建立后尽快发起,最好在发现服务之前。
- MTU值不是越大越好。MTU越大,单次传输的数据越多,但出错重传的代价也越大。
- 不同芯片的MTU上限不同。比如Nordic nRF52系列最大支持247,TI CC2640最大支持255,有些老芯片只支持128。
我个人的建议是:MTU设到247就够了。为什么?因为BLE 4.0/4.1的规范里,MTU最大就是247。虽然BLE 4.2以后支持更大的MTU(比如512),但很多老设备不支持。设到247,兼容性最好。
3.3 通知与指示的选择:别搞混了,后果很严重
通知(Notification)和指示(Indication)是GATT层最常用的两种数据推送方式。很多人觉得它们差不多,其实差别很大。
| 特性 | 通知(Notification) | 指示(Indication) |
|---|---|---|
| 是否需要确认 | 不需要 | 需要(对端必须回复确认) |
| 传输速度 | 快(无确认,连续发送) | 慢(必须等确认才能发下一条) |
| 可靠性 | 低(可能丢包) | 高(确认机制保证送达) |
| 适用场景 | 实时数据流(心率、加速度计) | 关键事件(报警、配置变更) |
| 对端负载 | 低 | 高(需要处理确认逻辑) |
你想想看,如果你在做心率监测,每秒需要发送10次数据,用指示的话,每次都要等确认,速度根本跟不上。这时候必须用通知。
反过来,如果你在做门锁报警,报警事件必须确保送达,丢了就出大事了。这时候必须用指示。
还有一个容易忽略的点:CCCD(Client Characteristic Configuration Descriptor)。无论是通知还是指示,对端都必须先写入CCCD来使能。很多新手忘了这一步,结果数据发不出去。
CCCD的写入值:
- 使能通知:写入0x0001
- 使能指示:写入0x0002
- 禁用:写入0x0000
我建议你在协议栈初始化时,就把CCCD的初始值设为0x0000。等对端主动写入使能后,再开始发送数据。这样最安全。
3.4 知识体系总览
下面这张图,把GATT层这三个核心问题的关系梳理清楚了。你可以把它当作设计时的检查清单。
说白了,GATT层的设计没有银弹。每个项目都要根据实际需求做取舍。但只要你把Service结构设计清晰、MTU协商做对、通知和指示选对场景,至少能避开80%的坑。