Ejemplo minimal de ESP32 Nimble de un servicio BLE personalizado
En nuestro post anterior Ejemplo minimal de servicio de información de dispositivo (DIS) BLE para ESP32 mostramos cómo usar el DIS (Device Information Service) predefinido con el paquete espressif/ble_services usando NimBLE como backend.
Basado en esto, aquí se muestra cómo crear un servicio BLE completamente personalizado:
CustomBLE.hpp
#pragma once
#include <string>
/**
* @brief Inicializar servicio NimBLE personalizado con una característica de cadena
*/
void InitCustomBLE();
/**
* @brief Actualizar el valor de cadena de la característica personalizada
* @param value El nuevo valor de cadena a establecer
*/
void UpdateCustomString(const std::string& value);
/**
* @brief Obtener el valor de cadena actual de la característica personalizada
* @return Valor de cadena actual
*/
std::string GetCustomString();
/**
* @brief Establecer manejador de eventos GAP para gestión de conexiones
* Llamar a esto antes de iniciar el host NimBLE
*/
void SetCustomBLEGapHandler();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";
// UUIDs de servicio y característica personalizados (UUIDs de 128-bit generados aleatoriamente)
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;
// Estructura del listener de eventos GAP
static struct ble_gap_event_listener gap_event_listener;
// Función de acceso a característica 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;
}
}
// Definición del servicio 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 - end of characteristics
}
},
},
{
0, // type - end of services
}
};
// Manejador de eventos 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;
// Inicializar servicios 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());
// Si está conectado, enviar notificación
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() {
// Configurar el listener de eventos 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);
}Cómo integrar con InitBLE()
Esto se integra bien con BLE.cpp de nuestro ejemplo anterior Ejemplo minimal de servicio de información de dispositivo (DIS) BLE para ESP32. Aquí está la función InitBLE() que inicializa tanto el servicio personalizado como el Device Information Service.
Lo único que necesitas añadir aquí es
SetCustomBLEGapHandler();
InitCustomBLE(); // Initialize custom BLE service
después de esp_nimble_init() y antes de iniciar el host BLE vía esp_ble_conn_start().
Función InitBLE() completa:
#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;
// Inicializar NVS
esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler, NULL);
esp_ble_conn_init(&config);
/**
* Inicializar servicio de información de dispositivo (DIS)
*/
app_ble_dis_init();
SetCustomBLEGapHandler();
InitCustomBLE(); // Inicializar servicio BLE personalizado
/**
* Iniciar BLE (en hilo separado)
*/
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);
}
}Cómo probarlo
Usa nuestro script de Script minimal de Python para listar y leer características de dispositivos BLE usando bleak:
Ejemplo parcial de salida
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!