1. GATT 协议基础与特征结构:服务 UUID、特征声明、描述符、属性权限

1.1 协议栈定位与层级关系

Bluetooth Low Energy(BLE)协议栈中,GATT(Generic Attribute Profile) 位于 HCI(Host Controller Interface) 之上的应用层,负责定义属性(Attribute)的发现、读取、写入和通知机制。GATT 协议并非独立存在,它依赖 ATT(Attribute Protocol) 进行数据包传输,而 GAP(Generic Access Profile) 则管理连接建立与广播。三层协作关系如图所示:

关键分层: GAP(连接/广播)→ ATT(数据包收发)→ GATT(属性组织与业务语义)
GATT 协议栈层级与属性结构 协议栈层级 GAP (Generic Access Profile) GATT (Generic Attribute Profile) ATT (Attribute Protocol) L2CAP + HCI + Link Layer 属性结构(一课一特征) 服务 (Service UUID: 0x180D) 特征声明 (Characteristic Declaration) 特征值 (Value) UUID: 0x2A37 客户端特征配置描述符 (CCCD) 用户描述描述符 (User Description) 说明: 每个特征由声明 + 值 + 0..N 个描述符组成。CCC描述符用于使能通知/指示。 属性权限掩码控制读、写、加密、认证和授权。

1.2 服务(Service)与特征(Characteristic)

GATT 的核心抽象是属性(Attribute),按层次组织为服务(Service)和特征(Characteristic)。每个服务由一个或多个特征组成,每个特征包含一个值以及零个或多个描述符。特征是实际数据传输的载体。

构件16位 UUID 示例作用
主服务 (Primary Service)0x180D (Heart Rate)声明一组相关功能
特征声明 (Characteristic Declaration)0x2803特征元数据:属性句柄、UUID、权限
特征值 (Characteristic Value)0x2A37 (Heart Rate Measurement)存储应用程序数据
客户端特征配置描述符 (CCCD)0x2902使能/关闭通知或指示
用户描述描述符 (User Description)0x2901可读字符串,如“心率测量”

1.3 特征声明结构详解

每个特征必须包含一个特征声明(UUID 0x2803),其数据格式固定为 5 字节(不带扩展型)或 7 字节(带扩展型)。常见 5 字节格式:

Byte 0-0: 特征属性 (Properties)     — 如 0x0A (通知 + 读)
Byte 1-2: 特征值句柄 (Handle)       — 指向特征值属性的句柄
Byte 3-4: 特征值 UUID (16位) 或 扩展型取前16位
实战经验: 使用 nRF Connect 或 Bluetooth Inspector 读取服务后,特征声明通常序号为 0x2803 的 handle,其值可直接解析出属性和值句柄。

1.4 属性权限(Attribute Permissions)

属性权限决定 GATT 客户端能否执行读、写、通知等操作,以及是否需要加密、认证或授权。权限由 GATT Server 端配置,在 ATT 层通过属性协议头中的 AuthSig 标志和加密链路共同实现。常用权限分为三类:

  • 访问权限: 读 (read)、写 (write)、通知 (notify)、指示 (indicate)
  • 加密要求: 无加密 (open)、加密连接 (encrypted)、带 MITM 保护 (authenticated)
  • 授权要求: 需要应用层授权 (authorization)

在 Zephyr RTOS 中,属性权限通过 BT_GATT_PERM_READ、BT_GATT_PERM_WRITE 等宏组合定义。下面是一个典型的心率测量特征注册代码片段:

// Zephyr 示例:心率测量特征注册
BT_GATT_SERVICE_DEFINE(hrs_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_HRS),
    BT_GATT_CHARACTERISTIC(BT_UUID_HRS_MEASUREMENT,
                           BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_NONE,
                           NULL, NULL, NULL),
    BT_GATT_CCC(hrs_ccc_cfg,
                BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);

属性权限位掩码(常见值):

权限宏值说明
BT_GATT_PERM_READ0x001允许不经加密读取
BT_GATT_PERM_WRITE0x002允许不经加密写入
BT_GATT_PERM_READ_ENCRYPT0x004需要加密链路读取
BT_GATT_PERM_WRITE_ENCRYPT0x008需要加密链路写入
BT_GATT_PERM_READ_AUTHEN0x010需要 MITM 保护读取

1.5 描述符(Descriptor)与 CCCD

描述符用于扩展特征的元数据。最关键的描述符是客户端特征配置描述符(CCCD,UUID 0x2902)。它允许 GATT 客户端通过写入 2 字节值来启用或禁用通知(Notification)或指示(Indication)。CCCD 值定义:

  • 0x0000 — 禁用通知/指示
  • 0x0001 — 使能通知(Server 主动发送,无需确认)
  • 0x0002 — 使能指示(Server 主动发送,需应用层确认)
常见错误: 忘记注册 CCC 描述符将导致客户端无法使能通知。在 nRF Connect 中表现为特征属性虽含有 “Notify” 标记,但无法勾选通知复选框。解决:在服务定义中加入 BT_GATT_CCC() 宏并实现回调函数。

1.6 实现流程概览

  1. 定义服务 UUID — 使用标准 16 位 UUID(如 0x180D)或自定义 128 位 UUID。
  2. 声明特征 — 使用 BT_GATT_CHARACTERISTIC 宏,指定特征 UUID、属性、权限。
  3. 注册值访问回调 — 实现 read/write 回调处理实际数据。
  4. 添加描述符 — 至少添加 CCCD,也可能添加用户描述或演示格式描述符。
  5. 设置属性权限 — 根据安全需求选择加密 / 认证级别。
  6. 调试验证 — 使用 BLE 抓包工具或 nRF Connect 读取服务特征列表,检查声明与值对齐。

1.7 调试与验证命令

在 Zephyr 环境下,可通过 Shell 命令遍历已注册的服务:

uart:~$ bt gatt-show-db
[0] Handle 0x0003: primary service (0x180D)
[1] Handle 0x0005: characteristic (0x2A37) props: 0x10 (Notify) value handle 0x0006
[2] Handle 0x0007: client characteristic config (0x2902) read write

在 Linux 上使用 gatttool 或 bluetoothctl 手动读写:

# 连接设备并列出特征
bluetoothctl
menu gatt
list-attributes  // 显示所有服务与特征
select-attribute 0x0006   // 选择特征值句柄
read                          // 读取特征值
性能注意: 每次属性读取/写入都经过 L2CAP(默认 MTU 23 字节),若数据量较大需协商 MTU 到 247+ 以提升吞吐。特征声明本身的长度很短,不影响性能,但属性权限加密会引入额外的配对与加密握手耗时。

1.8 本章小结

掌握 GATT 属性层级是进行读写封装的基础。服务 UUID 标识一组功能,特征声明携带属性和值句柄,描述符提供配置与元数据,属性权限则决定安全边界。代码实现中,Zephyr 的宏定义提供了清晰的模型,但务必注意每个特征至少需要一个 CCCD 描述符才能支持通知/指示。下阶段将进入实际特征值的读写回调实现。