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!If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow