ESP32 NimBLE : Exemple minimal d'un service BLE personnalisé

Dans notre article précédent ESP32 minimal BLE Device Information Service (DIS) example nous avons montré comment utiliser le service prédéfini DIS (Device Information Service) avec le paquet espressif/ble_services en utilisant NimBLE comme backend.

Sur cette base, voici comment créer un service BLE entièrement personnalisé :

CustomBLE.hpp

CustomBLE.hpp
#pragma once

#include <string>

/**
 * @brief Initialiser le service NimBLE personnalisé avec une caractéristique de chaîne
 */
void InitCustomBLE();

/**
 * @brief Mettre à jour la valeur de chaîne de la caractéristique personnalisée
 * @param value La nouvelle valeur de chaîne à définir
 */
void UpdateCustomString(const std::string& value);

/**
 * @brief Obtenir la valeur de chaîne actuelle de la caractéristique personnalisée
 * @return Valeur de chaîne actuelle
 */
std::string GetCustomString();

/**
 * @brief Définir le gestionnaire d'événements GAP pour la gestion des connexions
 * À appeler avant de démarrer l'hôte 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 du service et de la caractéristique personnalisés (UUID 128-bit générés aléatoirement)
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;

// Structure d'écouteur d'événements GAP
static struct ble_gap_event_listener gap_event_listener;

// Fonction d'accès à la caractéristique 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, "Lecture de la caractéristique personnalisée");
            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, "Écriture sur la caractéristique personnalisée");
            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, "Chaîne personnalisée mise à jour : %s", custom_string_value.c_str());
                }
            }
            return 0;
        }

        default:
            return BLE_ATT_ERR_UNLIKELY;
    }
}

// Définition du service 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 - fin des caractéristiques
            }
        },
    },
    {
        0, // type - fin des services
    }
};

// Gestionnaire d'événements GAP
static int custom_gap_event(struct ble_gap_event *event, void *arg) {
    switch (event->type) {
        case BLE_GAP_EVENT_CONNECT:
            ESP_LOGI(TAG, "Connexion %s; statut=%d",
                    event->connect.status == 0 ? "établie" : "échouée",
                    event->connect.status);
            if (event->connect.status == 0) {
                conn_handle = event->connect.conn_handle;
            }
            break;

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

        default:
            break;
    }
    return 0;
}

void InitCustomBLE() {
    int rc;

    // Initialiser les services GATT
    rc = ble_gatts_count_cfg(custom_gatt_svcs);
    if (rc != 0) {
        ESP_LOGE(TAG, "Échec du comptage des services GATT : %d", rc);
        return;
    }

    rc = ble_gatts_add_svcs(custom_gatt_svcs);
    if (rc != 0) {
        ESP_LOGE(TAG, "Échec de l'ajout des services GATT : %d", rc);
        return;
    }

    ESP_LOGI(TAG, "Service NimBLE personnalisé initialisé");
}

void UpdateCustomString(const std::string& value) {
    custom_string_value = value;
    ESP_LOGI(TAG, "Valeur de chaîne personnalisée mise à jour : %s", custom_string_value.c_str());

    // Si connecté, envoyer une notification
    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, "Échec de l'envoi de la notification : %d", rc);
            }
        }
    }
}

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

void SetCustomBLEGapHandler() {
    // Configurer l'écouteur d'événements 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);
}

Comment intégrer avec InitBLE()

Cela s’intègre bien avec BLE.cpp de notre exemple précédent ESP32 minimal BLE Device Information Service (DIS) example. Voici la fonction InitBLE() qui initialise à la fois le service personnalisé et le Device Information Service.

La seule chose que vous devez ajouter ici est de

init_ble_snippet.cpp
SetCustomBLEGapHandler();
InitCustomBLE(); // Initialiser le service BLE personnalisé

après esp_nimble_init() et avant de démarrer l’hôte BLE via esp_ble_conn_start().

Fonction InitBLE() complète :

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;

    // Initialiser NVS
    esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler, NULL);

    esp_ble_conn_init(&config);

    /**
     * Initialiser le service d'informations sur le périphérique (DIS)
     */
    app_ble_dis_init();

    SetCustomBLEGapHandler();
    InitCustomBLE(); // Initialiser le service BLE personnalisé

    /**
     * Démarrer BLE (dans un thread séparé)
     */
    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);
    }
}

Comment tester

Utilisez notre script depuis Minimal Python script to list & read BLE device characteristics using Python (Bleak) :

Exemple de sortie partiel

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