GATT(Generic Attribute Profile)是 Bluetooth 中建立在 ATT 之上的服务框架,用 GATT Service、GATT Characteristic 和 GATT Descriptor 把设备的状态、能力和控制入口组织成可发现、可读写、可通知的数据模型。
核心问题
两个 BLE 设备建立连接后,低层链路只负责传字节,应用仍然需要回答:
- 对端有哪些能力?
- 哪些数据可以读?
- 哪些状态可以写入控制?
- 哪些数据变化时可以主动推送?
- 字节数组应该按什么语义解释?
GATT 解决的是“设备如何把能力暴露成标准化数据表,并让对端按统一过程发现和访问”的问题。它让心率带、温度计、门锁、传感器和自定义设备都能用同一种发现、读、写、通知流程组织数据。
核心对象
| 对象 | 含义 | 工程关注点 |
|---|---|---|
| GATT Server | 持有 attribute database 的一方 | 暴露服务、维护状态、检查权限 |
| GATT Client | 发起发现、读写和订阅的一方 | 缓存 handle、处理断连、订阅通知 |
| ATT Attribute | GATT 的底层存储单元 | handle、UUID、value、permissions |
| GATT Service | 一组相关特征的上下文 | primary/secondary、服务 UUID、handle range |
| GATT Characteristic | 一个可读写或可通知的数据项 | properties、value handle、value UUID |
| GATT Descriptor | 特征的元数据或配置项 | CCCD、展示格式、扩展属性 |
| Bluetooth UUID | 标识 service/characteristic/descriptor 的类型 | 16-bit SIG UUID 或 128-bit vendor UUID |
GATT client/server 和 GAP central/peripheral 不是同一组角色。常见手机是 GAP central + GATT client,传感器是 GAP peripheral + GATT server;但设备可以同时实现 client 和 server。
核心机制
1. Server 先构造 attribute table
GATT 的真实底层是 ATT attribute table。一个简化的电池服务可以表示为:
handle type value
0x0001 0x2800 0x180F # Primary Service: Battery Service
0x0002 0x2803 props=read|notify,
value_handle=0x0003,
uuid=0x2A19 # Characteristic declaration
0x0003 0x2A19 85 # Battery Level value
0x0004 0x2902 0x0000 # CCCD: notification disabled这里的 handle 是 server 内部给每个 attribute 分配的索引;type 是 attribute 自己的类型;value 是该 attribute 的值。GATT 用特定 UUID 解释这些 attribute:0x2800 表示 Primary Service,0x2803 表示 Characteristic Declaration,0x2902 表示 Client Characteristic Configuration Descriptor。
2. Client 通过 ATT 程序发现结构
典型 discovery 流程是:
- client 用 ATT discovery 请求查找 Primary Service。
- server 返回服务的 handle range 和 service UUID。
- client 在某个 service 的 handle range 内查找 characteristic declarations。
- client 得到 characteristic properties、value handle 和 characteristic UUID。
- client 根据 properties 和 permissions 选择 read、write、notify 或 indicate。
- 如果要接收 notification/indication,client 通常写对应 characteristic 后面的 CCCD descriptor。
这解释了为什么 BLE 调试工具经常显示 Service -> Characteristic -> Descriptor 树,但抓包里看到的是 ATT PDU 和 handle。GATT 是语义层,ATT 是实际访问 attribute table 的协议层。
3. 操作语义由 properties 和 permissions 共同决定
Characteristic properties 表示“这个 characteristic 在协议上允许哪些 GATT 过程”,例如 read、write、notify、indicate。Attribute permissions 则表示“访问该值需要什么权限和安全条件”,例如是否可读、是否可写、是否要求加密或认证。
因此:
allowed = characteristic_properties permit operation
trusted = attribute_permissions satisfied by current link security
actual access = allowed AND trusted这不是数学公式,而是工程判断规则:properties 只说明操作类型是否存在,permissions 才决定当前连接是否真的能访问。一个 characteristic 可以声明支持 write,但未配对或未加密时仍返回权限相关错误。
4. Notification 和 Indication 用于状态推送
GATT 有两种常见主动上报方式:
- Notification:server 主动发 characteristic value,不要求 client 用 ATT 响应确认。
- Indication:server 主动发 characteristic value,client 必须返回 confirmation,可靠性更强但吞吐更低。
两者通常都要 client 先写 CCCD 订阅。没有写 CCCD 时,server 即使本地状态变化也不应把该 characteristic 推给这个 client。
工程用途
- 传感器数据:电池电量、温度、心率、IMU 数据、设备状态。
- 设备控制:写入开关、模式、阈值、Wi-Fi 配网参数或命令 characteristic。
- 设备识别:读取 Device Information Service、Firmware Revision、Manufacturer Name。
- 边缘设备调试:用手机 BLE 工具或 BlueZ 工具发现服务、读写 handle、订阅通知。
- 自定义协议:用 vendor-specific service UUID + 自定义 characteristic 承载小数据控制面。
调试 GATT 时,重点看连接是否建立、service discovery 是否完整、handle cache 是否过期、characteristic properties 是否包含目标操作、CCCD 是否写成功、ATT_MTU 是否足够、链路是否满足安全权限。
边界与常见坑
- GATT 不是广播协议:广播和发现主要由 GAP 管;GATT 典型工作在连接建立后的 attribute table 访问。
- GATT 不是任意大数据通道:单个 ATT attribute value 有长度限制,大数据传输要考虑 ATT_MTU、分片、吞吐和重传策略。
- handle 不是稳定 API:handle 是 server 本地表索引,固件升级或服务变化后可能改变;客户端应按 UUID 发现并处理 Service Changed。
- UUID 不是实例 ID:Bluetooth UUID 标识类型,不标识某个设备上的某个实例;同一 service UUID 可以出现多次。
- properties 不等于权限:read/write/notify bit 只是能力声明,实际访问还受 attribute permissions 和链路安全影响。
- notification 不等于可靠交付到应用层:底层链路可能确认包到达协议栈,但应用是否处理成功是另一回事。
- 不要混淆 GAP 与 GATT 角色:central/peripheral 管连接建立,client/server 管数据访问方向。
- 缓存要处理 Service Changed:客户端缓存 services 和 handles 后,如果 server 数据库变化而不通知,可能读写到错误 handle。