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-bit | 2字节 | 已认证Profile(如HRS、BAS) | 需检查SIG列表避免冲突 |
| 自定义128-bit | 16字节 | 私有业务、自定义传感器 | 推荐使用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服务注册完整流程
- 初始化GATT层:调用
esp_ble_gatts_app_register() - 创建服务:
esp_ble_gatts_create_service(),传入UUID、服务类型 - 添加特征值:
esp_ble_gatts_add_char(),设置属性和权限 - 添加CCCD描述符:
esp_ble_gatts_add_char_descr(),设置ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE - 启动服务:
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)
3.9 常见错误与避坑
- UUID冲突:与现有服务UUID重复导致GATT表注册失败。使用SIG数据库检查或生成128-bit随机UUID。
- CCCD未注册:客户端无法打开通知/指示,特征值属性必须包含
NOTIFY或INDICATE标志。 - 权限不足:特征值值(Value)的权限需与Properties匹配(如Read属性需要
ESP_GATT_PERM_READ)。 - 句柄泄漏:每个特征值及其描述符消耗一个句柄,超出资源限制会报
NO_RESOURCES错误。
特别提醒:在多设备同步场景中,如果多个设备使用相同自定义UUID,务必确认每个设备的GATT表完全一致,否则手机端缓存可能导致特征值访问错位。