GATT特征概念:它到底是什么?

GATT特征,英文叫,你可以把它想象成蓝牙设备对外暴露的一个数据点。比如一个温度计的温度值、一个心率计的心率值——这些都是特征。我做蓝牙开发那会儿,刚接触GATT时总觉得它很抽象,后来发现,其实就是属性协议(ATT)里的一颗果实,打包了数据、权限、描述信息。

特征内部有啥?

一个GATT特征由这么几部分组成:

  • 句柄(Handle):16位的数字,相当于特征在GATT服务器中的门牌号。客户端操作时靠它定位。
  • UUID:16位(蓝牙SIG标准)或128位(自定义),标识特征类型。比如电池电量是0x2A19。
  • 值(Value):实际数据,长度可变。比如一个字节的电池百分比。
  • 属性(Properties):告诉客户端你能对这个特征做什么——读、写、通知、指示等等。这是权限的集合。
  • 描述符(Descriptors):附加信息。最典型的是客户端特征配置描述符(CCCD),用来开启通知/指示。还有用户描述描述符,比如“电量百分比”。

我个人习惯把特征理解成一个“带属性的迷你数据对象”。你想想看,如果没有描述符,客户端根本不知道该怎么跟这个数据交互。

特征属性详解:读、写、通知、指示

下面这张表是我从项目经验里总结出来的,建议你收藏。

属性含义典型场景我踩过的坑
读(Read)客户端发起请求,服务器返回当前值读取设备型号、固件版本读操作是同步的,如果值很长,记得用MTU协商,不然分片很麻烦。
写(Write)客户端把新值发给服务器设置网络密码、电机转速写有带响应和不带响应两种。不带响应更快,但容易丢包,适合控制类小命令。
通知(Notify)服务器主动推值,客户端无需确认心率数据、陀螺仪角度我曾经犯过一个错:通知没有流量控制,如果发送频率太高,Android手机直接断连。后来我在代码里加了队列和优先级。
指示(Indicate)服务器推值,客户端必须确认关键报警、电量耗尽通知指示比通知慢,但可靠。如果丢包了,服务器会收到超时错误,重传机制要做好。
核心要点:通知不需要应用层确认,指示需要——这就是为什么蓝牙Core Spec把指示称为“有确认的通知”。选哪个,取决于你对可靠性和实时性的权衡。

使用场景:什么时候该用哪个?

我参与过一个血压计项目,里面有好几个特征,每个用的属性都不一样:

  1. 设备名称特征:只读(Read),APP一连接就能读到。
  2. 测量控制点:只写(Write),用来触发测量开始/停止。
  3. 血压测量值:通知(Notify),每次测完自动推送数据。
  4. 中间血压值:通知(Notify),实时显示测量过程中的值。
  5. 电池电量:读+通知(Read & Notify),既可以主动查,也能在变化时收到更新。

你看,不同属性组合能解决不同需求。我在选属性时,会问自己三个问题:

  • 数据是设备主动生成的,还是客户端询问的?→ 主动则用通知/指示。
  • 是否允许丢包?→ 不允许用指示,允许用通知。
  • 数据量大不大?→ 大小超过MTU时,要考虑分段或者用长读/长写。

避坑指南:CCCD与描述符

很多新手不理解:为什么我写了个特征,上电后客户端却收不到通知?

嗯,这里要注意——通知/指示功能的启用,依赖客户端往CCCD里写0x0001(通知)或0x0002(指示)。CCCD是特征下面一个固定的描述符(UUID 0x2902),必须声明。

我曾经有一次忘了加CCCD描述符,结果测试App死活没反应。查了两天才发现,是代码里没把这个描述符加进去。你想想看,如果没有CCCD,服务器收到“配置通知”的命令时根本找不到存储位置,自然就不会发数据。

⚠️ 重要警告:蓝牙Core Spec明确要求,如果你声明了通知或指示属性,就必须同时包含CCCD描述符。否则协议栈可能拒绝连接,或者行为未定义。我见过某款芯片固件没加CCCD,结果iOS设备直接认为特征无效。

知识结构图:特征内部关系

下面这张图是我画的特征结构以及客户端-服务器交互流程,方便你理清脉络。

GATT特征结构及属性交互图 GATT特征 句柄 (Handle) UUID 值 (Value) 属性 (Properties) 描述符 (Descriptors) 含CCCD 客户端 (GATT Client) 读请求 → 写请求 → 启用通知/指示 接收通知 ← 接收指示 (ACK) 读取值 写入值 配置CCCD 通知值 指示+确认 说明 属性决定交互方式; 通知无需确认,指示需ACK。 CCCD必须存在才可用通知/指示。 典型描述符 CCCD (0x2902), 用户描述 (0x2901) 格式、范围、表示等

总结一下

GATT特征其实不复杂。你只要记住:它由句柄、UUID、值、属性、描述符组成。属性决定了交互方式——读、写、通知、指示。每种方式都有适合的场景。我在项目里习惯先把特征清单列出来,给每个特征定好属性组合,再问自己“通知需要可靠吗?”然后决定用通知还是指示。另外,别忘了CCCD,否则你的通知变成死通知,客户端永远收不到。

💡 实用技巧:调试时,我常用蓝牙抓包工具(比如Ellisys或sniffer)看实际交互。如果客户端发了配置通知命令(0x2902写值),服务器没回应,基本就是CCCD没加或者权限不对。百试百灵。