You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:57:57