2. 蓝牙协议基础与GATT结构

蓝牙低功耗(BLE)协议栈采用分层架构,其中 GATT(Generic Attribute Profile) 是应用层与数据交互的核心接口。理解 GATT 的层次模型——服务(Service)、特征(Characteristic)、描述符(Descriptor)——是构建与测试 Mock 蓝牙设备的基石。本章从协议栈原理出发,深入拆解 GATT 结构,并给出可落地的实现、调试与优化指南。

2.1 蓝牙协议栈层次

BLE 协议栈按功能划分为三大层,GATT 位于 Host 层顶部,直接面向应用:

  • Controller:物理层(PHY)+ 链路层(LL),负责射频收发、连接状态管理。
  • Host:包含 L2CAP、SMP、ATT 和 GATT。其中 ATT(属性协议) 提供属性读写框架,GATT 在其上定义属性组织方式。
  • Application:用户业务逻辑,通过 GATT API 与对端交互。

在 Mock 测试中,通常模拟 Host 层以上的 GATT 行为,以验证应用层的正确性。

2.2 GATT 结构核心概念

GATT 将数据组织为树形层次结构,从顶层到底层依次为:

  1. 服务(Service):逻辑功能单元,例如“电池服务”、“设备信息服务”。由 UUID 唯一标识。
  2. 特征(Characteristic):具体数据项,包含一个值和若干属性(读、写、通知等)。每个服务可包含多个特征。
  3. 描述符(Descriptor):特征的元数据或配置项,例如“客户端特征配置描述符(CCCD)”用于开启通知。每个特征可包含多个描述符。

所有元素均通过 UUID 区分,标准 UUID(16 位)由 Bluetooth SIG 定义,自定义 UUID(128 位)用于私有协议。

GATT 层次结构图 GATT 层次结构图 Application(应用层) GATT(通用属性协议) Service(服务) Characteristic(特征) Descriptor(描述符) UUID 标识 GATT 采用层次化组织: Service 包含多个 Characteristic,Characteristic 包含多个 Descriptor,每个元素由 UUID 唯一标识。 层级 1 层级 2 层级 3

2.3 服务、特征、描述符对比

元素作用包含关系UUID 类型典型示例
Service逻辑功能分组包含 1 个或多个 Characteristic16 位 / 128 位0x180F(电池服务)
Characteristic数据承载单元包含值 + 属性 + 多个 Descriptor16 位 / 128 位0x2A19(电池电量)
Descriptor特征配置/元数据隶属于单个 Characteristic16 位 / 128 位0x2902(CCCD)

2.4 UUID 与蓝牙规范

UUID 是 GATT 元素的唯一标识。Bluetooth SIG 定义的 16 位标准 UUID 对应通用服务/特征,128 位自定义 UUID 用于厂商私有扩展。Mock 设备应优先复用标准 UUID 以提高互操作性,仅在必要时定义私有 UUID。

// 标准 UUID 示例(16 位)
#define UUID_BATTERY_SERVICE  0x180F
#define UUID_BATTERY_LEVEL    0x2A19

// 自定义 UUID 示例(128 位,用于 Mock 私有服务)
#define UUID_MOCK_SERVICE     "12345678-1234-5678-1234-56789abcdef0"
#define UUID_MOCK_DATA_CHAR   "12345678-1234-5678-1234-56789abcdef1"
测试建议:在 Mock 设备中使用标准 UUID 可降低协议分析工具(如 nRF Connect、LightBlue)的解析难度,便于快速验证结构正确性。

2.5 实现流程与代码示例

实现 GATT 结构通常遵循以下步骤:

  1. 规划服务、特征、描述符的 UUID 和权限。
  2. 注册服务到 GATT 数据库。
  3. 为每个特征添加回调函数(读、写、通知)。
  4. 配置描述符(如 CCCD)以支持通知/指示。
  5. 启动广播或连接等待对端访问。

以下示例基于 Zephyr BLE 栈,展示一个包含“模拟传感器服务”的 GATT 结构定义:

/* 服务与特征 UUID */
#define BT_UUID_MOCK_SERVICE_VAL \
    BT_UUID_128_ENCODE(0x00001234, 0x1234, 0x5678, 0x9abc, 0xdef012345678)
#define BT_UUID_MOCK_DATA_VAL \
    BT_UUID_128_ENCODE(0x00001234, 0x1234, 0x5678, 0x9abc, 0xdef012345679)

static struct bt_gatt_attr mock_attrs[] = {
    BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_128(BT_UUID_MOCK_SERVICE_VAL)),
    BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(BT_UUID_MOCK_DATA_VAL),
                           BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ,
                           mock_data_read_cb, NULL, &mock_data_val),
    BT_GATT_CCCD(&mock_ccc_cfg, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    BT_GATT_DESCRIPTOR(BT_UUID_DECLARE_16(0x2901),
                       BT_GATT_PERM_READ,
                       mock_user_desc_read_cb, NULL, "Mock Sensor Data"),
};

static struct bt_gatt_service mock_svc = BT_GATT_SERVICE(mock_attrs);
bt_gatt_service_register(&mock_svc);

客户端侧(Python + bleak)扫描并发现上述服务:

import asyncio
from bleak import BleakScanner, BleakClient

async def discover_gatt():
    device = await BleakScanner.find_device_by_name("MockSensor")
    async with BleakClient(device) as client:
        for service in client.services:
            print(f"Service: {service.uuid}")
            for char in service.characteristics:
                print(f"  Characteristic: {char.uuid}  properties: {char.properties}")
                for desc in char.descriptors:
                    print(f"    Descriptor: {desc.uuid}")

asyncio.run(discover_gatt())

2.6 调试 GATT 结构

  • 协议分析仪:使用 nRF Connect(手机端)或 Wireshark(配合 BLE 嗅探器)查看完整 GATT 树。
  • ATT 错误码:返回 0x02(无效句柄)通常表示 UUID 或句柄不匹配;0x08(权限不足)检查回调权限配置。
  • 日志输出:在服务注册、读写回调中增加日志,确认 GATT 操作流向。
// 启用 Zephyr BLE 日志(prj.conf)
CONFIG_BT_DEBUG_ATT=y
CONFIG_BT_DEBUG_GATT=y
常见陷阱:CCCD(0x2902)必须独立配置为 Descriptor,且 CCCD 写回调需要正确处理订阅/取消订阅状态,否则通知功能无法正常工作。

2.7 常见错误与解决方案

错误现象可能原因解决措施
对端无法发现服务服务未注册 / UUID 错误确认 bt_gatt_service_register 被调用,UUID 字节序正确
特征读写无响应权限与特征属性不匹配检查 BT_GATT_CHRC_READ 与 BT_GATT_PERM_READ 是否同时设置
通知不触发CCCD 未配置或配置回调缺失添加 BT_GATT_CCCD 并实现 bt_gatt_ccc_cfg_changed
多个服务顺序混乱ATT 句柄分配冲突使用 BT_GATT_SERVICE 宏保证句柄连续,或显式指定句柄范围

2.8 性能与稳定性优化

  • 精简结构:单个 Service 包含 3~5 个 Characteristic 为宜,避免单个服务过深或过宽,降低 ATT 遍历耗时。
  • 合并通知:多条数据可通过同一个 Characteristic 分批次发送,减少连接间隔内的数据包数量。
  • MTU 适配:在连接后协商 MTU(最大传输单元),使单次 GATT 操作承载更多数据,减少交互次数。
  • 描述符复用:多个特征共享同一描述符配置时(如相同的 CCCD 处理逻辑),使用 BT_GATT_CCC_CACHING 减少内存占用。
// MTU 协商示例(Zephyr)
void mtu_updated(struct bt_conn *conn, uint16_t mtu) {
    printk("MTU updated: %d\n", mtu);
}
// 注册回调
BT_CONN_CB_DEFINE(conn_callbacks) = {
    .mtu_updated = mtu_updated,
};
Mock 测试价值:通过 Mock 设备模拟极端 GATT 结构(如包含 20 个特征的服务),可提前发现性能瓶颈与协议栈边界问题,避免在真实硬件上反复烧录调试。