Мінімальний приклад ESP32 NimBLE власного BLE сервісу

У нашому попередньому дописі Мінімальний приклад ESP32 BLE Device Information Service (DIS) ми показали, як використовувати попередньо визначений DIS (Device Information Service) з пакетом espressif/ble_services використовуючи 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-бітні 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 = "Привіт, 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, "Читання власної характеристики");
            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, "Запис власної характеристики");
            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, "Власний рядок оновлено на: %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, "З'єднання %s; статус=%d",
                    event->connect.status == 0 ? "встановлено" : "невдале",
                    event->connect.status);
            if (event->connect.status == 0) {
                conn_handle = event->connect.conn_handle;
            }
            break;

        case BLE_GAP_EVENT_DISCONNECT:
            ESP_LOGI(TAG, "Роз'єднання; причина=%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, "Не вдалося підрахувати GATT-сервіси: %d", rc);
        return;
    }

    rc = ble_gatts_add_svcs(custom_gatt_svcs);
    if (rc != 0) {
        ESP_LOGE(TAG, "Не вдалося додати GATT-сервіси: %d", rc);
        return;
    }

    ESP_LOGI(TAG, "Власний NimBLE сервіс ініціалізовано");
}

void UpdateCustomString(const std::string& value) {
    custom_string_value = value;
    ESP_LOGI(TAG, "Значення власного рядка оновлено на: %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, "Не вдалося надіслати сповіщення: %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()

Це добре інтегрується з BLE.cpp з нашого попереднього прикладу Мінімальний приклад ESP32 BLE Device Information Service (DIS). Ось функція InitBLE(), яка ініціалізує як власний сервіс, так і Device Information Service.

Єдине, що вам потрібно додати тут — це

init_ble_snippet.cpp
SetCustomBLEGapHandler();
InitCustomBLE(); // Ініціалізувати власний BLE сервіс

після esp_nimble_init() і перед запуском BLE-хоста через esp_ble_conn_start().

Повна функція 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-скрипт для виведення списку та читання характеристик BLE-пристрою за допомогою Python (Bleak):

Частковий приклад виводу

ble_partial_example_output.txt
Сервіс: 12345678-9abc-def0-1234-56789abcdef0
Опис: Невідомо
Дескриптор: 1
    Характеристики (1):
    ----------------------------------------------------------------------------
        UUID: 87654321-fedc-ba98-8765-4321fedcba98
        Опис: Невідомо
        Дескриптор: 2
        Властивості: читання, запис, сповіщення
        Значення (рядок): Привіт, NimBLE!

Дивіться схожі статті за категоріями: Bluetooth, ESP32, ESP-IDF