GATT特征概念:它到底是什么?
GATT特征,英文叫
特征内部有啥?
一个GATT特征由这么几部分组成:
- 句柄(Handle):16位的数字,相当于特征在GATT服务器中的门牌号。客户端操作时靠它定位。
- UUID:16位(蓝牙SIG标准)或128位(自定义),标识特征类型。比如电池电量是0x2A19。
- 值(Value):实际数据,长度可变。比如一个字节的电池百分比。
- 属性(Properties):告诉客户端你能对这个特征做什么——读、写、通知、指示等等。这是权限的集合。
- 描述符(Descriptors):附加信息。最典型的是客户端特征配置描述符(CCCD),用来开启通知/指示。还有用户描述描述符,比如“电量百分比”。
我个人习惯把特征理解成一个“带属性的迷你数据对象”。你想想看,如果没有描述符,客户端根本不知道该怎么跟这个数据交互。
特征属性详解:读、写、通知、指示
下面这张表是我从项目经验里总结出来的,建议你收藏。
| 属性 | 含义 | 典型场景 | 我踩过的坑 |
|---|---|---|---|
| 读(Read) | 客户端发起请求,服务器返回当前值 | 读取设备型号、固件版本 | 读操作是同步的,如果值很长,记得用MTU协商,不然分片很麻烦。 |
| 写(Write) | 客户端把新值发给服务器 | 设置网络密码、电机转速 | 写有带响应和不带响应两种。不带响应更快,但容易丢包,适合控制类小命令。 |
| 通知(Notify) | 服务器主动推值,客户端无需确认 | 心率数据、陀螺仪角度 | 我曾经犯过一个错:通知没有流量控制,如果发送频率太高,Android手机直接断连。后来我在代码里加了队列和优先级。 |
| 指示(Indicate) | 服务器推值,客户端必须确认 | 关键报警、电量耗尽通知 | 指示比通知慢,但可靠。如果丢包了,服务器会收到超时错误,重传机制要做好。 |
核心要点:通知不需要应用层确认,指示需要——这就是为什么蓝牙Core Spec把指示称为“有确认的通知”。选哪个,取决于你对可靠性和实时性的权衡。
使用场景:什么时候该用哪个?
我参与过一个血压计项目,里面有好几个特征,每个用的属性都不一样:
- 设备名称特征:只读(Read),APP一连接就能读到。
- 测量控制点:只写(Write),用来触发测量开始/停止。
- 血压测量值:通知(Notify),每次测完自动推送数据。
- 中间血压值:通知(Notify),实时显示测量过程中的值。
- 电池电量:读+通知(Read & Notify),既可以主动查,也能在变化时收到更新。
你看,不同属性组合能解决不同需求。我在选属性时,会问自己三个问题:
- 数据是设备主动生成的,还是客户端询问的?→ 主动则用通知/指示。
- 是否允许丢包?→ 不允许用指示,允许用通知。
- 数据量大不大?→ 大小超过MTU时,要考虑分段或者用长读/长写。
避坑指南:CCCD与描述符
很多新手不理解:为什么我写了个特征,上电后客户端却收不到通知?
嗯,这里要注意——通知/指示功能的启用,依赖客户端往CCCD里写0x0001(通知)或0x0002(指示)。CCCD是特征下面一个固定的描述符(UUID 0x2902),必须声明。
我曾经有一次忘了加CCCD描述符,结果测试App死活没反应。查了两天才发现,是代码里没把这个描述符加进去。你想想看,如果没有CCCD,服务器收到“配置通知”的命令时根本找不到存储位置,自然就不会发数据。
⚠️ 重要警告:蓝牙Core Spec明确要求,如果你声明了通知或指示属性,就必须同时包含CCCD描述符。否则协议栈可能拒绝连接,或者行为未定义。我见过某款芯片固件没加CCCD,结果iOS设备直接认为特征无效。
知识结构图:特征内部关系
下面这张图是我画的特征结构以及客户端-服务器交互流程,方便你理清脉络。
总结一下
GATT特征其实不复杂。你只要记住:它由句柄、UUID、值、属性、描述符组成。属性决定了交互方式——读、写、通知、指示。每种方式都有适合的场景。我在项目里习惯先把特征清单列出来,给每个特征定好属性组合,再问自己“通知需要可靠吗?”然后决定用通知还是指示。另外,别忘了CCCD,否则你的通知变成死通知,客户端永远收不到。
💡 实用技巧:调试时,我常用蓝牙抓包工具(比如Ellisys或sniffer)看实际交互。如果客户端发了配置通知命令(0x2902写值),服务器没回应,基本就是CCCD没加或者权限不对。百试百灵。