第一章 GATT协议基础:从BLE协议栈到属性读写
做蓝牙开发这么多年,我见过太多工程师一上来就撸GATT代码,结果连Attribute是什么都没搞明白。嗯,今天咱们就把地基打牢。
1.1 BLE协议栈架构:GATT站在哪一层?
先看整体。BLE协议栈分三层:Controller、Host、Application。GATT在Host层的最上面,紧挨着应用。
核心分层关系:
- Controller:物理层+链路层,管收发、广播、连接
- Host:L2CAP、ATT、GATT、GAP、SM、Generic Attribute Profile
- Application:你的业务代码,调用GATT API
说白了,GATT是Host层给应用层开的“后门”。你不需要关心射频怎么调、连接怎么建,GATT帮你把数据包装成“属性”,应用只管读写就行。
个人经验:我刚开始做BLE时,总以为GATT就是全部。后来调试一个心率设备,发现数据老是丢包,查了两天才发现是L2CAP的MTU没配对。记住:GATT只是“翻译官”,底层传输靠L2CAP和Controller。
1.2 GATT与GAP的关系:一个管连接,一个管数据
很多人把GAP和GATT搞混。我简单说:
- GAP(Generic Access Profile):管设备怎么被发现、怎么建立连接。广播、扫描、连接参数,都是GAP的事。
- GATT(Generic Attribute Profile):管连接建立之后,数据怎么读写、怎么通知。服务、特征、属性,都是GATT的事。
你想想看:GAP是“门卫”,负责开门关门;GATT是“管家”,负责屋里东西怎么摆放、怎么取用。
| 维度 | GAP | GATT |
|---|---|---|
| 职责 | 设备发现、连接建立、广播 | 属性读写、通知、服务发现 |
| 角色 | Peripheral / Central | Server / Client |
| 数据模型 | 广播包、扫描响应 | Service、Characteristic、Descriptor |
| 典型场景 | 手机扫描手环、发起连接 | 手机读取手环心率值、订阅通知 |
避坑指南:我曾经在一个项目中,把GAP的广播间隔设得太短,结果GATT通信时频繁断连。后来才意识到:GAP的连接参数(比如连接间隔、延迟)直接影响GATT的吞吐量。调GATT之前,先确认GAP参数是否合理。
1.3 Attribute Protocol基础:GATT的“快递员”
GATT自己不干活,它依赖底层的ATT(Attribute Protocol)。ATT负责把数据打包成“属性”,然后通过L2CAP发出去。
一个属性包含四个要素:
- Attribute Handle:属性的唯一ID,16位整数,0x0001~0xFFFF
- Attribute Type:UUID,标识属性是什么(比如心率测量是0x2A37)
- Attribute Permissions:读写权限(可读、可写、需要加密等)
- Attribute Value:实际数据,最长512字节(受MTU限制)
ATT定义了6种PDU操作:
| 操作 | 方向 | 说明 |
|---|---|---|
| Read Request / Response | Client → Server → Client | 客户端请求读取属性值 |
| Write Request / Response | Client → Server → Client | 客户端写入属性值,需要确认 |
| Write Command | Client → Server | 客户端写入属性值,不需要确认 |
| Notify | Server → Client | 服务器主动通知,不需要确认 |
| Indicate | Server → Client | 服务器主动指示,需要确认 |
| Find Information Request / Response | Client → Server → Client | 客户端发现属性列表 |
关键点:Notify和Indicate的区别在于可靠性。Notify像发——发了就不管了;Indicate像发挂号信——对方必须回“收到了”。我建议:对关键数据(比如血糖报警)用Indicate,对高频数据(比如实时心率)用Notify。
1.4 GATT角色定义:Server与Client
GATT定义了两种角色:
- GATT Server:持有属性数据的一方。通常是传感器设备(如手环、体温计)。
- GATT Client:读写属性数据的一方。通常是手机、平板、网关。
一个设备可以同时是Server和Client。比如手机连接手环时,手机是Client,手环是Server;但手机同时连接云服务器时,手机又变成了Client。
Server内部的数据组织方式:
GATT Server
├── Service 1 (UUID: 0x180D) // 心率服务
│ ├── Characteristic 1 (UUID: 0x2A37) // 心率测量
│ │ ├── Properties: Notify
│ │ ├── Value: [当前心率值]
│ │ └── Descriptor: Client Characteristic Configuration (0x2902)
│ └── Characteristic 2 (UUID: 0x2A38) // 体感位置
│ ├── Properties: Read
│ └── Value: [手腕/胸部]
└── Service 2 (UUID: 0x180F) // 电池服务
└── Characteristic 1 (UUID: 0x2A19) // 电池电量
├── Properties: Read, Notify
└── Value: [80%]
个人习惯:我设计GATT Server时,会先画一张“属性树”,把Service、Characteristic、Descriptor的层级关系画清楚。这样写代码时不容易漏掉权限配置。你想想看,如果Characteristic的权限配错了,Client读不到数据,排查起来很痛苦。
1.5 本章小结
这一章我们理清了:
- GATT在BLE协议栈中的位置——Host层最上面
- GAP管连接,GATT管数据——分工明确
- ATT是GATT的底层协议,负责属性传输
- GATT Server持有数据,GATT Client读写数据