1. GATT协议基础:GATT与GAP的关系、GATT角色与Attribute Protocol
各位同学,咱们今天正式进入GATT的世界。说实话,我刚接触蓝牙协议栈那会儿,最头疼的就是分不清GATT和GAP到底谁管谁。后来被坑了几次才明白——GAP管的是“能不能连”,GATT管的是“连上以后能干啥”。
打个比方:GAP就像门卫,负责发现设备、建立连接、管理安全。GATT呢,就像进了门以后的服务员,告诉你“这里有温度计、有开关、有电池信息,你想看哪个?”
嗯,这个比喻我用了好多年,每次带新人我都这么讲。今天咱们就把GATT的底裤扒干净。
1.1 GATT与GAP:分工明确的兄弟俩
先看一张图,把这两个协议栈层的关系理清楚:
你看,GAP在底层把连接通道建好,GATT在上层用这个通道做数据交换。我见过不少新手在调试时发现GATT读写失败,结果查了半天是GAP的连接参数没配好——连接间隔设得太长,导致数据交互超时。所以这两个层虽然分工不同,但调试时得一起看。
核心记忆点:
- GAP = 能不能连、怎么连、安全不安全
- GATT = 连上以后能读什么、写什么、收什么通知
- GATT 依赖 GAP 建立的连接,但 GAP 不关心 GATT 的数据内容
1.2 GATT 角色:Server 与 Client
GATT定义了两个角色,说白了就是“谁提供数据,谁消费数据”。
| 角色 | 职责 | 典型设备 | 我踩过的坑 |
|---|---|---|---|
| GATT Server | 存储属性数据,响应读写请求,主动发送通知 | 传感器、心率带、温控器 | Server 端忘记配置 CCC,导致 Client 收不到 Notify |
| GATT Client | 发现服务、读写属性、订阅通知 | 手机 App、网关、中心节点 | Client 端没有等 Service Discovery 完成就发读写,直接失败 |
这里有个容易混淆的点:GAP的角色(Central/Peripheral)和GATT的角色(Server/Client)是正交的。什么意思?
- 一个 Peripheral 设备可以是 GATT Server(最常见,比如蓝牙温度计)
- 一个 Peripheral 设备也可以是 GATT Client(比如一个蓝牙遥控器去读电视的状态)
- 一个设备可以同时是 Server 和 Client(比如手机既是 Server 提供电量服务,又是 Client 读手环数据)
我记得有一次做智能门锁项目,门锁作为 Peripheral + GATT Server,手机作为 Central + GATT Client。一切正常。后来客户要求门锁也能读取云端配置——好嘛,门锁得同时当 Client 去连手机。这时候角色就乱了,一个连接里只能有一个 Server 和一个 Client,所以得建两个连接。嗯,这个坑我替你们踩过了。
我的调试习惯:在代码里用宏定义明确标注当前设备在 GAP 和 GATT 中的角色,比如:
#define GAP_ROLE PERIPHERAL
#define GATT_ROLE SERVER
这样看代码时一目了然,不会搞混。
1.3 Attribute Protocol:GATT 的基石
GATT 不是凭空造出来的,它建立在 Attribute Protocol(ATT) 之上。说白了,ATT 定义了“怎么读写一个个属性”,GATT 定义了“这些属性怎么组织成服务”。
每个属性(Attribute)由四个要素组成:
| 要素 | 说明 | 我见过的翻车现场 |
|---|---|---|
| Attribute Handle | 属性的唯一编号,16位,0x0001~0xFFFF | Handle 重复导致服务发现失败,芯片直接复位 |
| Attribute Type (UUID) | 标识属性是什么,16位/128位 UUID | UUID 写错一个字节,手机 App 识别不出服务 |
| Attribute Permissions | 读/写/加密/认证权限 | 权限设成“仅加密可读”,但连接没加密,Client 读回来全是 0 |
| Attribute Value | 实际数据,最长 512 字节(单包) | Value 超过 MTU 没做分包,直接丢包 |
ATT 的通信模式很简单:Client 发请求,Server 回响应。但也有例外——Server 可以主动发 Notification 或 Indication,不需要 Client 先问。这个机制在低功耗场景下特别有用,传感器数据变了直接推,不用轮询。
注意:ATT 的 MTU(最大传输单元)默认只有 23 字节,其中 3 字节是 ATT 头部,实际数据只有 20 字节。如果你要传超过 20 字节的数据,要么协商更大的 MTU(最高 512 字节),要么做分包。我曾经有个项目没做 MTU 协商,直接传 100 字节,结果 Client 端只收到前 20 个字节,后面全丢了。查了两天才发现是 MTU 的问题。
1.4 属性分组:从 Attribute 到 Service
GATT 在 ATT 的基础上做了分组。单个 Attribute 太零散了,GATT 把它们组织成三级结构:
这个结构你想想看,是不是很像文件系统?Service 是文件夹,Characteristic 是文件,Descriptor 是文件的属性。手机 App 连上设备后,第一步就是遍历这个树形结构——也就是 Service Discovery。
我个人习惯在开发阶段把 Service 和 Characteristic 的 UUID 打印出来,对照蓝牙规范手册检查。有一次我发现心率服务的 UUID 写成了 0x180F(电池服务),结果手机 App 怎么都读不到心率数据。嗯,这种低级错误,犯过一次就长记性了。
GATT 分组的好处:
- 可发现性:Client 不需要提前知道设备有什么数据,连上后遍历就行
- 可扩展性:新增一个 Service 不影响已有的 Service
- 标准化:蓝牙 SIG 定义了大量标准 Service,不同厂家的设备可以互操作
1.5 本章小结
咱们这一章把 GATT 的骨架搭起来了:
- GAP vs GATT:一个管连接,一个管数据,别搞混
- Server vs Client:谁提供数据,谁消费数据,角色可以叠加但一个连接里只能各有一个
- Attribute Protocol:Handle + UUID + 权限 + Value,四个要素缺一不可
- 属性分组:Service → Characteristic → Descriptor,树形结构方便发现和管理