ESP32 NimBLE 通过 BLE 执行自定义命令的最小示例
在我们之前的示例ESP32 NimBLE 自定义 BLE 服务的最小示例中,我们展示了如何在 ESP32 上使用 NimBLE 创建自定义 BLE 服务和特征值。本示例在此基础上演示如何通过 BLE 执行自定义命令。
自定义命令的工作原理
BLE 本身并不真正了解命令,所以它只是一个我们可以写入的自定义特征值。
在此示例中,我们将使用一个单一特征值,你可以向其写入字符串。如果字符串是 RESTART,ESP32 将重启。
注意,除了 CustomBLE.cpp(与ESP32 NimBLE 自定义 BLE 服务的最小示例相比只有少量更改)之外,所有代码都与之前的示例相同,此处省略重复代码。
CustomBLE.cpp
CustomBLE.cpp
#include "CustomBLE.hpp"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.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";
// Custom service and characteristic UUIDs (随机生成的 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 command_char_uuid =
BLE_UUID128_INIT(0x98, 0xBA, 0xDC, 0xFE, 0x21, 0x43, 0x65, 0x87,
0x98, 0xBA, 0xDC, 0xFE, 0x21, 0x43, 0x65, 0x87);
static uint16_t command_char_handle;
static uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE;
// Command constants
#define RESTART_COMMAND "RESTART"
// GAP 事件监听器结构
static struct ble_gap_event_listener gap_event_listener;
// 命令的 GATT 特征值访问函数
static int command_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, "Command characteristic read - returning status");
const char* status_msg = "Command interface ready";
rc = os_mbuf_append(ctxt->om, status_msg, strlen(status_msg));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
ESP_LOGI(TAG, "Command characteristic write received");
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';
ESP_LOGI(TAG, "Received command: %s", buffer);
// 处理命令
if (strcmp(buffer, RESTART_COMMAND) == 0) {
ESP_LOGI(TAG, "Restart command received - restarting ESP32...");
// 添加小延迟以允许 BLE 响应发送
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
} else {
ESP_LOGW(TAG, "Unknown command: %s", buffer);
}
}
}
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[]) {
{
&command_char_uuid.u,
command_char_access,
NULL, // arg
NULL, // descriptors
BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE,
0, // min_key_size
&command_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 command service initialized");
}
void ExecuteBLECommand(const std::string& command) {
ESP_LOGI(TAG, "Executing BLE command: %s", command.c_str());
if (command == RESTART_COMMAND) {
ESP_LOGI(TAG, "Restart command executed - restarting ESP32...");
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
} else {
ESP_LOGW(TAG, "Unknown command: %s", command.c_str());
}
}
std::string GetCommandStatus() {
return "Command interface ready. Supported commands: " RESTART_COMMAND;
}
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);
}如何测试
使用我们Python 脚本:使用 Bleak 写入 BLE 特征值中的脚本,将命令 RESTART 写入特征值。
注意,由于 ESP32 会立即重启,写入会失败,但命令仍会被执行。
ble_write_test.sh
python write_ble_characteristic.py 24:EC:4A:76:00:32 87654321-fedc-ba98-8765-4321fedcba98 RESTART示例输出
ble_write_test_output.txt
Using MAC address: 24:EC:4A:76:00:32
Characteristic UUID: 87654321-fedc-ba98-8765-4321fedcba98
Value: RESTART (utf-8)
Attempting to connect to 24:EC:4A:76:00:32 ...
Successfully connected to 24:EC:4A:76:00:32
Connected at: 2025-08-04 03:07:02
Writing to characteristic 87654321-fedc-ba98-8765-4321fedcba98 ...
Failed to write value: [org.bluez.Error.Failed] Operation failed with ATT error: 0x0e (Unlikely Error)
Write operation failed.If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow