4. GAP与GATT:蓝牙协议栈的两大支柱
做蓝牙开发,你迟早会碰到两个缩写:GAP 和 GATT。很多人一开始容易搞混,觉得它们差不多。其实差远了。
我打个比方你就明白了。GAP 就像社交软件里的「发现附近的人」和「加好友」功能——它管的是设备怎么被发现、怎么建立连接、怎么保证安全。而 GATT 呢,更像加了好友之后,你们怎么聊天、发什么消息、消息格式是什么——它管的是连接建立之后的数据交互。
说白了,GAP 负责「连接前」,GATT 负责「连接后」。两者分工明确,缺一不可。
核心记忆点:GAP = 设备发现 + 连接管理 + 安全;GATT = 服务 + 特征 + 属性协议。
4.1 GAP 层:设备发现、连接管理与安全
GAP 的全称是 Generic Access Profile,通用访问规范。它定义了 BLE 设备在「还没连上」的时候该怎么工作。
4.1.1 设备发现:广播与扫描
BLE 设备怎么让别人发现自己?靠广播。广播就像你在广场上举着牌子喊「我在这里,我支持这些功能」。我做过一个智能门锁项目,门锁作为 peripheral 设备,每 100ms 发一次广播包,包里携带设备名称和服务 UUID。手机端扫描到广播后,才能发起连接。
- 广播者(Advertiser): 周期性发送广播包,包含设备信息、服务 UUID、设备名称等。
- 扫描者(Scanner): 监听广播包,发现周围设备。
- 广播类型: 可连接广播、不可连接广播、可扫描广播、定向广播。
我的经验:广播间隔别设太短。我见过有人设 20ms,结果功耗飙升,纽扣电池两天就没电了。一般建议 100ms-1s,根据实时性需求调整。
4.1.2 连接管理:建立、断开与参数更新
扫描到设备后,central 发起连接请求。连接建立后,双方进入连接状态。这时候 GAP 还管着连接参数:连接间隔、延迟、超时时间。
| 参数 | 说明 | 典型值 |
|---|---|---|
| 连接间隔 | 两个连接事件之间的时间 | 7.5ms - 4s |
| 从机延迟 | 从机可跳过的连接事件数 | 0 - 499 |
| 监督超时 | 连接丢失判定时间 | 100ms - 32s |
连接间隔越小,数据吞吐越高,但功耗也越大。我做过一个心率带项目,需要实时传输数据,连接间隔设了 30ms。但如果是温湿度传感器,几分钟传一次数据,连接间隔可以拉到 1s 以上。
4.1.3 安全管理:配对、绑定与加密
GAP 还管安全。配对(Pairing)是双方交换密钥的过程,绑定(Bonding)是把密钥存下来,下次重连不用再配对。加密(Encryption)是数据传输时加锁。
BLE 安全分几个等级:
- Just Works: 无用户交互,安全性最低。
- Passkey Entry: 输入 6 位数字 PIN 码。
- Numeric Comparison: 显示数字,用户确认。
- Out of Band: 通过 NFC 等带外方式交换密钥。
避坑提醒:我曾经在一个医疗项目中用了 Just Works 配对,客户审计时直接打回。医疗设备至少要用 Passkey Entry 或 Numeric Comparison。选安全等级时,别只看开发方便,要看产品场景。
4.2 GATT 层:服务、特征与属性协议
连接建立后,GATT 上场了。GATT 的全称是 Generic Attribute Profile,通用属性规范。它定义了连接之后的数据交互方式。
4.2.1 属性协议(ATT)
GATT 底层跑的是 ATT(Attribute Protocol)。ATT 把数据抽象成一个个「属性」,每个属性有:
- 句柄(Handle): 属性的唯一 ID,类似内存地址。
- 类型(UUID): 标识属性是什么,16 位标准 UUID 或 128 位自定义 UUID。
- 权限(Permissions): 可读、可写、可通知等。
- 值(Value): 实际数据。
ATT 的操作很简单:读、写、通知、指示。你想想看,这跟文件系统是不是有点像?句柄就是文件描述符,UUID 就是文件名,值就是文件内容。
4.2.2 服务(Service)与特征(Characteristic)
GATT 把属性组织成服务和特征。一个设备可以有多个服务,一个服务可以有多个特征,一个特征包含一个值以及若干描述符。
举个例子,一个心率计设备:
- 服务: 心率服务(Heart Rate Service,UUID: 0x180D)
- 特征: 心率测量特征(Heart Rate Measurement,UUID: 0x2A37)
- 特征值: 心率数值(如 72 bpm)
- 描述符: 客户端特征配置描述符(CCCD),用于使能通知
// 伪代码:GATT 服务结构示例
Service: 0x180D (Heart Rate Service)
├── Characteristic: 0x2A37 (Heart Rate Measurement)
│ ├── Value: [uint8 flags, uint8 heartRate]
│ ├── Descriptor: 0x2902 (CCCD) - 使能通知
│ └── Descriptor: 0x2901 (User Description) - "心率"
└── Characteristic: 0x2A38 (Body Sensor Location)
└── Value: 0x01 (Chest)
4.2.3 Profile 与 Service 的关系
很多人问:Profile 和 Service 到底什么关系?
Profile 是规范文档,它定义了一个应用场景需要哪些 Service、哪些 Characteristic、以及它们的行为规则。而 Service 是具体实现,是设备里跑的一个个功能模块。
打个比方:Profile 是菜谱,Service 是食材。菜谱告诉你做宫保鸡丁需要鸡肉、花生、辣椒,但鸡肉本身不是菜谱,它是食材。一个 Profile 可以引用多个 Service,一个 Service 也可以被多个 Profile 复用。
| 概念 | 层级 | 举例 |
|---|---|---|
| Profile | 应用层规范 | 心率计 Profile (HRP) |
| Service | 功能模块 | 心率服务 (0x180D) |
| Characteristic | 数据点 | 心率测量 (0x2A37) |
| Descriptor | 属性描述 | CCCD (0x2902) |
一句话总结:Profile 是「规范」,Service 是「实现」。Profile 告诉你怎么做,Service 是实际跑起来的代码。
4.3 实战中的 GAP 与 GATT 配合
我做一个蓝牙温湿度计项目时,GAP 和 GATT 是这样配合的:
- GAP 阶段: 设备上电后进入广播模式,广播包携带设备名称和温湿度服务 UUID。
- GAP 阶段: 手机扫描到设备,发起连接。连接间隔设为 500ms,降低功耗。
- GATT 阶段: 连接建立后,手机发现温湿度服务,读取温度特征和湿度特征。
- GATT 阶段: 使能通知,设备每 5 秒主动上报一次数据。
- GAP 阶段: 手机断开连接,设备回到广播模式。
你看,GAP 和 GATT 不是割裂的,它们通过连接事件衔接。GAP 负责「找到你、连上你、保证安全」,GATT 负责「连上之后怎么传数据」。两者配合好了,产品才能稳定可靠。
我的习惯:设计新项目时,我会先画一张 GAP 状态机图,再画一张 GATT 服务树。两张图画完,整个蓝牙交互逻辑就清楚了。代码写起来也快,不容易漏东西。
嗯,GAP 和 GATT 就讲到这里。记住:GAP 管连接前,GATT 管连接后。这个分界线清楚了,后面学具体 Profile 和实现细节就顺了。