4. GAP与GATT:蓝牙协议栈的两大支柱

做蓝牙开发,你迟早会碰到两个缩写:GAP 和 GATT。很多人一开始容易搞混,觉得它们差不多。其实差远了。

我打个比方你就明白了。GAP 就像社交软件里的「发现附近的人」和「加好友」功能——它管的是设备怎么被发现、怎么建立连接、怎么保证安全。而 GATT 呢,更像加了好友之后,你们怎么聊天、发什么消息、消息格式是什么——它管的是连接建立之后的数据交互。

说白了,GAP 负责「连接前」,GATT 负责「连接后」。两者分工明确,缺一不可。

核心记忆点:GAP = 设备发现 + 连接管理 + 安全;GATT = 服务 + 特征 + 属性协议。

BLE 协议栈:GAP 与 GATT 分层关系 GAP 层(通用访问规范) 连接前 · 设备级 设备发现(广播/扫描) 连接管理(建立/断开/参数) 安全管理(配对/绑定/加密) GATT 层(通用属性规范) 连接后 · 数据级 服务(Service) 特征(Characteristic) 属性协议(ATT) 连接建立后 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 是这样配合的:

  1. GAP 阶段: 设备上电后进入广播模式,广播包携带设备名称和温湿度服务 UUID。
  2. GAP 阶段: 手机扫描到设备,发起连接。连接间隔设为 500ms,降低功耗。
  3. GATT 阶段: 连接建立后,手机发现温湿度服务,读取温度特征和湿度特征。
  4. GATT 阶段: 使能通知,设备每 5 秒主动上报一次数据。
  5. GAP 阶段: 手机断开连接,设备回到广播模式。

你看,GAP 和 GATT 不是割裂的,它们通过连接事件衔接。GAP 负责「找到你、连上你、保证安全」,GATT 负责「连上之后怎么传数据」。两者配合好了,产品才能稳定可靠。

我的习惯:设计新项目时,我会先画一张 GAP 状态机图,再画一张 GATT 服务树。两张图画完,整个蓝牙交互逻辑就清楚了。代码写起来也快,不容易漏东西。

嗯,GAP 和 GATT 就讲到这里。记住:GAP 管连接前,GATT 管连接后。这个分界线清楚了,后面学具体 Profile 和实现细节就顺了。