GATT 协议基础:GAP 与 GATT 的关系、角色与层级
聊蓝牙开发,GATT 是绕不开的核心。很多人一上来就啃协议栈,结果被各种术语搞晕。我个人习惯先把 GATT 和 GAP 的关系理清楚,再往下看角色和层级,这样思路会顺很多。
GAP 与 GATT:一个管连接,一个管数据
GAP(Generic Access Profile)和 GATT(Generic Attribute Profile)是蓝牙协议栈里两个最基础的概念。简单说:GAP 负责设备怎么被发现、怎么建立连接;GATT 负责连接建立之后,数据怎么交换。
我在项目中遇到过不少新手,把这两个混为一谈。调试时发现设备连上了但读不到数据,就怀疑是 GATT 的问题。其实很多时候是 GAP 层的广播参数没配好,导致连接不稳定。嗯,这里要注意:GAP 是前提,GATT 是后续。
| 维度 | GAP | GATT |
|---|---|---|
| 职责 | 设备发现、连接建立、安全配对 | 连接后的数据读写、通知、指示 |
| 角色 | Broadcaster / Observer / Peripheral / Central | Server / Client |
| 数据模型 | 广播数据包(ADV) | Attribute 表(Service / Characteristic) |
| 典型场景 | 手机扫描到蓝牙手环 | 手机读取手环的心率值 |
Server / Client 角色:谁提供数据,谁消费数据
GATT 定义了两种角色:Server(服务端)和Client(客户端)。Server 持有数据,Client 发起读写请求。说白了,Server 是数据提供方,Client 是数据消费方。
举个例子:你做一个蓝牙温湿度计。温湿度计本身是 GATT Server,它把温度和湿度数据放在特定的 Characteristic 里。手机 App 是 GATT Client,它主动去读这些数据。反过来,如果手机想设置温湿度计的采样间隔,手机作为 Client 写数据到 Server 的某个 Characteristic 里。
我曾经踩过一个坑:把 Server 和 Client 的角色跟 GAP 的 Peripheral / Central 搞混了。Peripheral 通常是 Server,Central 通常是 Client,但这不是绝对的。有些设备既是 Peripheral 又是 Server,这很常见。但理论上,一个设备可以同时是 Server 和 Client,比如手机同时连接多个外设时。
Profile / Service / Characteristic 层级
这是 GATT 最核心的数据模型。我习惯把它比作一个文件系统:
- Profile —— 相当于一个“应用规范”,它定义了一组 Service 的集合。比如心率 Profile(HRS)包含心率 Service 和设备信息服务。
- Service —— 相当于一个“文件夹”,把相关的 Characteristic 归类在一起。每个 Service 有唯一的 UUID。
- Characteristic —— 相当于一个“文件”,里面存放具体的数据值。Characteristic 也有 UUID,并且有属性(读、写、通知等)。
你想想看,蓝牙协议栈为什么要搞这么复杂?直接传数据不就行了?其实是为了标准化。不同厂商的设备只要遵循同一个 Profile,手机 App 就能通用。比如所有心率计都使用标准的心率 Service,App 不需要为每个品牌写不同的代码。
层级关系图
下面这张图展示了 GATT 的完整层级结构。我建议你把它存下来,开发时对照着看。
实际开发中的层级操作
在代码层面,你通常需要做这几件事:
- 注册 Service —— 调用 API 创建一个 Service 实例,指定 UUID。
- 添加 Characteristic —— 在 Service 下创建 Characteristic,设置 UUID、属性(读/写/通知)和初始值。
- 配置 Descriptor —— 尤其是 CCCD(Client Characteristic Configuration Descriptor),它控制通知和指示的开关。
- 处理读写请求 —— 实现回调函数,响应 Client 的读写操作。
举个例子,创建一个简单的心率 Service:
// 伪代码示例
service = gatt_server_create_service(HEART_RATE_SERVICE_UUID);
// 添加心率测量 Characteristic
char_hr = gatt_server_add_characteristic(
service,
HEART_RATE_MEASUREMENT_UUID,
GATT_PROP_NOTIFY, // 属性:支持通知
GATT_PERM_READ // 权限:可读
);
// 添加 CCCD Descriptor
gatt_server_add_descriptor(
char_hr,
CLIENT_CHARACTERISTIC_CONFIG_UUID,
GATT_PERM_READ | GATT_PERM_WRITE
);
// 启动服务
gatt_server_start_service(service);
标准 Service 与自定义 Service
蓝牙 SIG 定义了很多标准 Service,比如:
- 心率 Service(0x180D) —— 用于传输心率数据
- 电池 Service(0x180F) —— 用于报告电池电量
- 设备信息服务(0x180A) —— 包含设备名称、序列号等
如果你的产品不属于这些标准类别,可以定义自定义 Service。自定义 Service 使用 128 位 UUID,格式如 0000xxxx-0000-1000-8000-00805F9B34FB,其中 xxxx 是自定义的 16 位值。
我个人建议:能用标准 Service 就用标准的。这样你的设备可以被更多通用 App 识别,兼容性更好。只有标准 Service 无法满足需求时,才考虑自定义。