ESP32 NimBLE 自定义 BLE 服务的最小示例

在我们之前的文章ESP32 最小 BLE 设备信息服务 (DIS) 示例中,我们展示了如何使用 espressif/ble_services 包中预定义的 DIS(设备信息服务),并以 NimBLE 作为后端。

在此基础上,以下是创建完全自定义 BLE 服务的方法:

CustomBLE.hpp

CustomBLE.hpp
#pragma once

#include <string>

/**
 * @brief 初始化带字符串特征值的自定义 NimBLE 服务
 */
void InitCustomBLE();

/**
 * @brief 更新自定义特征值的字符串值
 * @param value 要设置的新字符串值
 */
void UpdateCustomString(const std::string& value);

/**
 * @brief 获取自定义特征值的当前字符串值
 * @return 当前字符串值
 */
std::string GetCustomString();

/**
 * @brief 设置用于连接管理的 GAP 事件处理函数
 * 在启动 NimBLE 主机之前调用此函数
 */
void SetCustomBLEGapHandler();

CustomBLE.cpp

CustomBLE.cpp
#include "CustomBLE.hpp"

#include "esp_log.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "host/ble_gatt.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"

#include <string>
#include <cstring>

static const char *TAG = "CustomBLE";

// 自定义服务和特征值 UUID(随机生成的 128-bit UUID)
static const ble_uuid128_t custom_service_uuid =
    BLE_UUID128_INIT(0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12,
                     0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12);

static const ble_uuid128_t custom_char_uuid =
    BLE_UUID128_INIT(0x98, 0xBA, 0xDC, 0xFE, 0x21, 0x43, 0x65, 0x87,
                     0x98, 0xBA, 0xDC, 0xFE, 0x21, 0x43, 0x65, 0x87);

static std::string custom_string_value = "Hello, NimBLE!";
static uint16_t custom_char_handle;
static uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE;

// GAP 事件监听器结构
static struct ble_gap_event_listener gap_event_listener;

// GATT 特征值访问函数
static int custom_char_access(uint16_t conn_handle, uint16_t attr_handle,
                             struct ble_gatt_access_ctxt *ctxt, void *arg) {
    int rc;

    switch (ctxt->op) {
        case BLE_GATT_ACCESS_OP_READ_CHR:
            ESP_LOGI(TAG, "Custom characteristic read");
            rc = os_mbuf_append(ctxt->om, custom_string_value.c_str(), custom_string_value.length());
            return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;

        case BLE_GATT_ACCESS_OP_WRITE_CHR: {
            ESP_LOGI(TAG, "Custom characteristic write");
            uint16_t om_len = OS_MBUF_PKTLEN(ctxt->om);
            if (om_len > 0) {
                char buffer[om_len + 1];
                rc = ble_hs_mbuf_to_flat(ctxt->om, buffer, sizeof(buffer) - 1, NULL);
                if (rc == 0) {
                    buffer[om_len] = '\0';
                    custom_string_value = std::string(buffer);
                    ESP_LOGI(TAG, "Custom string updated to: %s", custom_string_value.c_str());
                }
            }
            return 0;
        }

        default:
            return BLE_ATT_ERR_UNLIKELY;
    }
}

// GATT 服务定义
static const struct ble_gatt_svc_def custom_gatt_svcs[] = {
    {
        BLE_GATT_SVC_TYPE_PRIMARY,
        &custom_service_uuid.u,
        NULL, // includes
        (struct ble_gatt_chr_def[]) {
            {
                &custom_char_uuid.u,
                custom_char_access,
                NULL, // arg
                NULL, // descriptors
                BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY,
                0, // min_key_size
                &custom_char_handle,
                NULL, // cpfd
            },
            {
                NULL, // uuid - 特征值结束
            }
        },
    },
    {
        0, // type - 服务结束
    }
};

// GAP 事件处理函数
static int custom_gap_event(struct ble_gap_event *event, void *arg) {
    switch (event->type) {
        case BLE_GAP_EVENT_CONNECT:
            ESP_LOGI(TAG, "Connection %s; status=%d",
                    event->connect.status == 0 ? "established" : "failed",
                    event->connect.status);
            if (event->connect.status == 0) {
                conn_handle = event->connect.conn_handle;
            }
            break;

        case BLE_GAP_EVENT_DISCONNECT:
            ESP_LOGI(TAG, "Disconnect; reason=%d", event->disconnect.reason);
            conn_handle = BLE_HS_CONN_HANDLE_NONE;
            break;

        default:
            break;
    }
    return 0;
}

void InitCustomBLE() {
    int rc;

    // 初始化 GATT 服务
    rc = ble_gatts_count_cfg(custom_gatt_svcs);
    if (rc != 0) {
        ESP_LOGE(TAG, "Failed to count GATT services: %d", rc);
        return;
    }

    rc = ble_gatts_add_svcs(custom_gatt_svcs);
    if (rc != 0) {
        ESP_LOGE(TAG, "Failed to add GATT services: %d", rc);
        return;
    }

    ESP_LOGI(TAG, "Custom NimBLE service initialized");
}

void UpdateCustomString(const std::string& value) {
    custom_string_value = value;
    ESP_LOGI(TAG, "Custom string value updated to: %s", custom_string_value.c_str());

    // 如果已连接,发送通知
    if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
        struct os_mbuf *om;
        om = ble_hs_mbuf_from_flat(value.c_str(), value.length());
        if (om != NULL) {
            int rc = ble_gattc_notify_custom(conn_handle, custom_char_handle, om);
            if (rc != 0) {
                ESP_LOGE(TAG, "Failed to send notification: %d", rc);
            }
        }
    }
}

std::string GetCustomString() {
    return custom_string_value;
}

void SetCustomBLEGapHandler() {
    // 设置 GAP 事件监听器
    gap_event_listener.fn = custom_gap_event;
    gap_event_listener.arg = NULL;
    ble_gap_event_listener_register(&gap_event_listener, custom_gap_event, NULL);
}

如何与 InitBLE() 集成

这与我们之前示例ESP32 最小 BLE 设备信息服务 (DIS) 示例中的 BLE.cpp 集成得很好。以下是同时初始化自定义服务和设备信息服务的 InitBLE() 函数。

你唯一需要添加的是:

init_ble_snippet.cpp
SetCustomBLEGapHandler();
InitCustomBLE(); // 初始化自定义 BLE 服务

esp_nimble_init() 之后、通过 esp_ble_conn_start() 启动 BLE 主机之前调用。

完整的 InitBLE() 函数:

init_ble_full.cpp
#include "CustomBLE.hpp"

/* ... */

void InitBLE(void)
{
    esp_nimble_init();

    GenerateDeviceName();

    esp_ble_conn_config_t config;
    strncpy((char*)config.device_name, device_name.c_str(), sizeof(config.device_name) - 1);
    strncpy((char*)config.broadcast_data, "Metexon", sizeof(config.broadcast_data) - 1);

    esp_err_t ret;

    // 初始化 NVS
    esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler, NULL);

    esp_ble_conn_init(&config);

    /**
     * 初始化设备信息服务 (DIS)
     */
    app_ble_dis_init();

    SetCustomBLEGapHandler();
    InitCustomBLE(); // 初始化自定义 BLE 服务

    /**
     * 启动 BLE(在单独的线程中)
     */
    if (esp_ble_conn_start() != ESP_OK) {
        esp_ble_conn_stop();
        esp_ble_conn_deinit();
        esp_event_handler_unregister(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler);
    }
}

如何测试

使用我们最小 Python 脚本:使用 Python (Bleak) 列出和读取 BLE 设备特征值中的脚本:

部分示例输出

ble_partial_example_output.txt
Service: 12345678-9abc-def0-1234-56789abcdef0
Description: Unknown
Handle: 1
    Characteristics (1):
    ----------------------------------------------------------------------------
        UUID: 87654321-fedc-ba98-8765-4321fedcba98
        Description: Unknown
        Handle: 2
        Properties: read, write, notify
        Value (string): Hello, NimBLE!

Check out similar posts by category: Bluetooth, ESP32, ESP-IDF