ESP-IDF v3.0蓝牙GATT Server多服务实现技术求助
嘿,我太懂你这种困惑了——ESP-IDF自带的GATT Server示例基本都只展示单个服务,刚上手的时候确实摸不着头脑不知道怎么扩展。其实添加多个服务的逻辑和单个服务完全一致,就是把创建服务、添加特征的流程多走几遍就行,我给你理个清晰的步骤和代码片段,你可以直接参考:
核心逻辑:每个服务独立完成创建流程
不管是Device Info Service(DIS)还是Battery Service(BAS),每个GATT服务都需要走完定义UUID→创建服务→添加特征→启动服务这一套流程,ESP-IDF的蓝牙栈完全支持同时注册多个服务,只要按顺序处理就行。
1. 先把多个服务的UUID和特征定义好
首先你需要把标准服务的UUID(DIS和BAS都是蓝牙SIG定义的标准服务)和对应的特征UUID明确下来,比如:
#include "esp_gatts_api.h" #include "esp_bt_defs.h" // 设备信息服务(DIS)标准UUID #define SERVICE_UUID_DIS 0x180A // 电池服务(BAS)标准UUID #define SERVICE_UUID_BAS 0x180F // DIS特征:制造商名称 #define CHAR_UUID_MANUFACTURER 0x2A29 // BAS特征:电池电量 #define CHAR_UUID_BATTERY_LEVEL 0x2A19 // 为每个服务定义独立的服务ID结构体 static esp_gatt_svc_id_t gatt_svc_dis = { .is_primary = true, .id = { .uuid = { .len = ESP_UUID_LEN_16, .uuid.uuid16 = SERVICE_UUID_DIS, }, .inst_id = 0, }, }; static esp_gatt_svc_id_t gatt_svc_bas = { .is_primary = true, .id = { .uuid = { .len = ESP_UUID_LEN_16, .uuid.uuid16 = SERVICE_UUID_BAS, }, .inst_id = 0, }, }; // 定义特征配置(以制造商名称和电池电量为例) static esp_gatts_char_prop_t char_prop_manufacturer = ESP_GATT_CHAR_PROP_READ; static esp_attr_value_t char_val_manufacturer = { .attr_max_len = 32, .attr_len = strlen("Your Manufacturer"), .attr_value = (uint8_t *)"Your Manufacturer", }; static esp_ble_gatts_char_def_t char_manufacturer = { .uuid = { .len = ESP_UUID_LEN_16, .uuid.uuid16 = CHAR_UUID_MANUFACTURER, }, .char_prop = char_prop_manufacturer, .permissions = ESP_GATT_PERM_READ_ENCRYPTED, .descriptor = NULL, .value = &char_val_manufacturer, }; static esp_gatts_char_prop_t char_prop_battery = ESP_GATT_CHAR_PROP_READ | ESP_GATT_CHAR_PROP_NOTIFY; static esp_attr_value_t char_val_battery = { .attr_max_len = 1, .attr_len = 1, .attr_value = (uint8_t *)"100", // 初始电量100% }; static esp_ble_gatts_char_def_t char_battery_level = { .uuid = { .len = ESP_UUID_LEN_16, .uuid.uuid16 = CHAR_UUID_BATTERY_LEVEL, }, .char_prop = char_prop_battery, .permissions = ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED, .descriptor = NULL, .value = &char_val_battery, };
2. 在GATT事件回调里处理多个服务的创建
接下来要在gatts_profile_event_handler里,注册成功后同时创建两个服务,然后在服务创建完成的事件里分别添加特征并启动服务:
#define GATT_NUM_HANDLE_DIS 4 // 根据DIS的特征数量调整 #define GATT_NUM_HANDLE_BAS 4 // 根据BAS的特征数量调整 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: // 注册成功后,依次创建DIS和BAS服务 esp_err_t err = esp_gatts_create_service(gatts_if, &gatt_svc_dis, GATT_NUM_HANDLE_DIS); if (err != ESP_OK) { ESP_LOGE(TAG, "Create DIS service failed: %s", esp_err_to_name(err)); return; } err = esp_gatts_create_service(gatts_if, &gatt_svc_bas, GATT_NUM_HANDLE_BAS); if (err != ESP_OK) { ESP_LOGE(TAG, "Create BAS service failed: %s", esp_err_to_name(err)); return; } break; case ESP_GATTS_CREATE_EVT: // 判断当前创建的是哪个服务,然后启动并添加特征 if (param->create.service_id.id.uuid.uuid16 == SERVICE_UUID_DIS) { // 启动DIS服务 esp_gatts_start_service(gatts_if, param->create.service_handle); // 添加制造商名称特征 esp_ble_gatts_add_char(gatts_if, param->create.service_handle, &char_manufacturer, NULL); } else if (param->create.service_id.id.uuid.uuid16 == SERVICE_UUID_BAS) { // 启动BAS服务 esp_gatts_start_service(gatts_if, param->create.service_handle); // 添加电池电量特征 esp_ble_gatts_add_char(gatts_if, param->create.service_handle, &char_battery_level, NULL); } break; // 其他事件处理:比如特征读写、通知、连接断开等,按需添加 case ESP_GATTS_READ_EVT: // 处理特征读请求,比如读取电池电量时返回当前值 if (param->read.handle == char_battery_level.handle) { esp_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &char_val_battery); } break; default: break; } }
3. 更新广播数据,让设备同时广播多个服务
如果需要让扫描设备直接看到这两个服务,可以把它们的UUID加到广播数据里:
static uint8_t adv_data[] = { 0x02, 0x01, 0x06, // 广播标志:可连接、BR/EDR不支持 0x03, 0x03, 0x0A, 0x18, // 广播DIS服务UUID(16位) 0x03, 0x03, 0x0F, 0x18, // 广播BAS服务UUID(16位) 0x08, 0x09, 'E', 'S', 'P', '-', 'M', 'U', 'L', 'T', // 设备名称(可选) }; static uint8_t scan_rsp_data[] = { // 可以在这里添加更多设备信息,比如完整的服务列表 }; // 初始化广播 esp_ble_gap_config_adv_data(adv_data, sizeof(adv_data), scan_rsp_data, sizeof(scan_rsp_data));
几个关键注意事项
- 不要复用结构体:每个服务和特征都要有独立的
esp_gatt_svc_id_t和esp_ble_gatts_char_def_t结构体,避免内存覆盖问题。 - 服务启动顺序不影响:不管先创建哪个服务,最终都会被蓝牙栈正确注册,扫描设备能发现所有服务。
- 标准服务用标准UUID:DIS和BAS都是蓝牙SIG定义的标准服务,直接用16位UUID即可,不需要自定义128位UUID,兼容性更好。
- 调整句柄数量:
GATT_NUM_HANDLE_DIS和GATT_NUM_HANDLE_BAS要根据你添加的特征数量调整,每个特征会占用至少1个句柄,加上服务本身的句柄,留足余量就行。
你可以基于ESP-IDF自带的gatt_server示例来修改,把上面的代码片段整合进去,很快就能实现多服务的GATT Server了。如果还有具体的细节问题,比如特征权限配置、通知触发逻辑,随时可以再细化~
内容的提问来源于stack exchange,提问作者EGibson
相关产品推荐
相关产品推荐

