Minimales ESP32 NimBLE-Beispiel für einen benutzerdefinierten BLE-Service

In unserem vorherigen Post ESP32 minimales BLE Device Information Service (DIS) Beispiel haben wir gezeigt, wie man den vordefinierten DIS (Device Information Service) mit dem espressif/ble_services-Paket unter Verwendung von NimBLE als Backend verwendet.

Darauf aufbauend, hier ist, wie man einen komplett benutzerdefinierten BLE-Service erstellt:

CustomBLE.hpp

CustomBLE.hpp
#pragma once

#include <string>

/**
 * @brief Initialize custom NimBLE service with a string characteristic
 */
void InitCustomBLE();

/**
 * @brief Update the string value of the custom characteristic
 * @param value The new string value to set
 */
void UpdateCustomString(const std::string& value);

/**
 * @brief Get the current string value of the custom characteristic
 * @return Current string value
 */
std::string GetCustomString();

/**
 * @brief Set GAP event handler for connection management
 * Call this before starting NimBLE host
 */
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";

// Custom service and characteristic UUIDs (randomly generated 128-bit UUIDs)
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 event listener structure
static struct ble_gap_event_listener gap_event_listener;

// GATT characteristic access function
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 service definition
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
    }
};

// GAP event handler
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;

    // Initialize GATT services
    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 connected, send 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, "Failed to send notification: %d", rc);
            }
        }
    }
}

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

void SetCustomBLEGapHandler() {
    // Set up the GAP event listener
    gap_event_listener.fn = custom_gap_event;
    gap_event_listener.arg = NULL;
    ble_gap_event_listener_register(&gap_event_listener, custom_gap_event, NULL);
}

Integration mit InitBLE()

Dies integriert sich gut mit BLE.cpp aus unserem vorherigen Beispiel ESP32 minimales BLE Device Information Service (DIS) Beispiel. Hier ist die InitBLE()-Funktion, die sowohl den benutzerdefinierten Service als auch den Device Information Service initialisiert.

Das Einzige, was Sie hier hinzufügen müssen, ist

init_ble_snippet.cpp
SetCustomBLEGapHandler();
InitCustomBLE(); // Initialize custom BLE service

nach esp_nimble_init() und vor dem Starten des BLE-Hosts über esp_ble_conn_start().

Vollständige InitBLE()-Funktion:

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;

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

    esp_ble_conn_init(&config);

    /**
     * Initialize device information service (DIS)
     */
    app_ble_dis_init();

    SetCustomBLEGapHandler();
    InitCustomBLE(); // Initialize custom BLE service

    /**
     * Start BLE (in separate thread)
     */
    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);
    }
}

Testen

Verwenden Sie unser Skript aus Minimales Python-Skript zum Auflisten & Lesen von BLE-Geräte-Charakteristiken mit Python (Bleak):

Partielle Beispielausgabe

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