3. BLE GATT服务与特征值设计:服务UUID规划、读写属性、通知与指示配置

3.1 本章知识结构

GATT(Generic Attribute Profile)是BLE应用层核心协议,定义了数据交换的层级结构:服务(Service) → 特征值(Characteristic) → 描述符(Descriptor)。本章聚焦服务UUID规划、特征值读写属性选择以及通知/指示机制配置。

层级作用关键参数
Service逻辑功能分组UUID(16bit / 128bit)、Type(Primary / Secondary)
Characteristic数据交换单元UUID、Properties(Read / Write / Notify / Indicate)、Value
Descriptor附加配置元数据CCCD(Client Characteristic Configuration Descriptor)
核心原则:服务UUID必须唯一标识功能模块;特征值的属性(Properties)直接决定通信模式;CCCD是启停通知/指示的唯一通道。

3.2 UUID规划:标准 vs 自定义

BLE规范预定义了大量标准16-bit UUID(如心率服务 0x180D、电池服务 0x180F),使用标准UUID可提升兼容性。当标准UUID无法覆盖业务时,需使用128-bit自定义UUID。

// 标准UUID示例(Bluetooth SIG)
#define UUID_SERVICE_DEVICE_INFORMATION  0x180A
#define UUID_CHAR_MANUFACTURER_NAME      0x2A29

// 自定义128-bit UUID(厂商特定)
#define UUID_SERVICE_MY_CUSTOM           "0000F001-0000-1000-8000-00805F9B34FB"
#define UUID_CHAR_TEMPERATURE            "0000F101-0000-1000-8000-00805F9B34FB"
UUID类型长度使用场景注意事项
标准16-bit2字节已认证Profile(如HRS、BAS)需检查SIG列表避免冲突
自定义128-bit16字节私有业务、自定义传感器推荐使用UUID生成器保证全局唯一

3.3 特征值读写属性配置

每个特征值的Properties字段决定GATT客户端(手机/网关)可以执行的操作。常用属性组合:

  • Read + Notify:传感器数据上报(如温度、电量)
  • Write:控制指令下发(如LED开关)
  • Write Without Response:高速写入,无ACK确认
  • Indicate:确认型通知,常用于关键事件
// Nordic nRF5 SDK 特征值属性定义
BLE_GATT_CHAR_PROPERTIES_READ       // 0x02
BLE_GATT_CHAR_PROPERTIES_WRITE      // 0x08
BLE_GATT_CHAR_PROPERTIES_NOTIFY     // 0x10
BLE_GATT_CHAR_PROPERTIES_INDICATE   // 0x20

// 实际组合示例(可读+通知)
ble_gatts_char_attr_t char_attr;
char_attr.p_char_props = (ble_gatts_char_props_t){
    .read = 1,
    .notify = 1,
    .write = 0
};
常见错误:同时开启Notify和Indicate会造成CCCD配置混乱。二者只能选其一,或通过应用层协议区分。

3.4 通知与指示配置(CCCD)

CCCD(Client Characteristic Configuration Descriptor)是特征值下唯一的0x2902描述符,客户端通过写入0x0001(开启通知)或0x0002(开启指示)控制数据推送。服务端必须在GATT数据库中正确注册该描述符并处理写请求。

// ESP-IDF 添加CCCD示例
esp_ble_gatts_add_char_descr(
    char_handle,
    BLE_UUID_DESC_CLIENT_CHAR_CONFIG,   // 0x2902
    ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
    NULL,
    NULL
);

// 处理CCCD写回调(伪代码)
void on_cccd_write(uint16_t conn_handle, uint16_t value) {
    if (value == 0x0001) {
        enable_notify(conn_handle);  // 开启通知
    } else if (value == 0x0002) {
        enable_indicate(conn_handle); // 开启指示
    } else {
        disable_both(conn_handle);
    }
}

3.5 GATT服务注册完整流程

  1. 初始化GATT层:调用esp_ble_gatts_app_register()
  2. 创建服务:esp_ble_gatts_create_service(),传入UUID、服务类型
  3. 添加特征值:esp_ble_gatts_add_char(),设置属性和权限
  4. 添加CCCD描述符:esp_ble_gatts_add_char_descr(),设置ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE
  5. 启动服务:esp_ble_gatts_start_service()
// ESP-IDF 完整注册代码片段(关键步骤)
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
                                        esp_gatt_if_t gatts_if,
                                        esp_ble_gatts_cb_param_t *param) {
    switch (event) {
        case ESP_GATTS_REG_EVT:
            esp_ble_gatts_create_service(gatts_if, &service_id, 3); // 3个句柄
            break;
        case ESP_GATTS_CREAT_SERVICE_EVT:
            esp_ble_gatts_add_char(service_handle, &char_uuid,
                                   ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
                                   ESP_GATT_CHAR_PROP_BIT_NOTIFY,
                                   &char_val, NULL);
            esp_ble_gatts_add_char_descr(service_handle, &cccd_uuid,
                                          ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
                                          NULL, NULL);
            esp_ble_gatts_start_service(service_handle);
            break;
        // 其他事件处理...
    }
}

3.6 调试与验证

  • 工具推荐:nRF Connect(手机应用)、LightBlue、Wireshark(抓BLE HCI日志)
  • 检查点:确认服务UUID在广播包中被正确暴露;特征值属性显示为Read/Notify等;CCCD可读写
  • 常见日志:
// 正确注册的日志片断
I (38950) BT_GATTS: Service started, handle=0x0030
I (38950) BT_GATTS: Characteristic added, handle=0x0032, properties=0x12
I (38950) BT_GATTS: Descriptor added, handle=0x0034, uuid=0x2902

// 常见错误:CCCD权限缺失
E (39200) GATTS: attribute write permission check failed, handle=0x0034

3.7 性能与稳定性优化

优化方向具体措施
减少MTU分片协商更大的MTU(如247字节),减少数据包数量
通知限速避免高速连续Notify,增加应用层间隔或使用GATT流控
CCCD状态管理在断开连接后清除通知状态,避免重连后保留错误配置
服务数量控制单设备建议≤10个Service,避免GATT表过大导致存储溢出
设计经验:对于多BLE设备同步场景,优先使用Notify + Read组合,并配合连接间隔协商(Connection Interval)保证数据实时性与功耗平衡。

3.8 GATT服务与特征值层级结构(内联SVG)

BLE GATT 服务层级结构 GATT Server(设备) Service (Primary) Service (Secondary) Characteristic A Characteristic B Characteristic C CCCD (0x2902) User Desc (0x2901) 特征值属性示例: Char A → Read + Notify | Char B → Write + Notify Char C → Indicate | CCCD控制通知/指示启停

3.9 常见错误与避坑

  • UUID冲突:与现有服务UUID重复导致GATT表注册失败。使用SIG数据库检查或生成128-bit随机UUID。
  • CCCD未注册:客户端无法打开通知/指示,特征值属性必须包含NOTIFY或INDICATE标志。
  • 权限不足:特征值值(Value)的权限需与Properties匹配(如Read属性需要ESP_GATT_PERM_READ)。
  • 句柄泄漏:每个特征值及其描述符消耗一个句柄,超出资源限制会报NO_RESOURCES错误。
特别提醒:在多设备同步场景中,如果多个设备使用相同自定义UUID,务必确认每个设备的GATT表完全一致,否则手机端缓存可能导致特征值访问错位。

3.10 小结