ESP32 minimal BLE Device Information Service (DIS) example

This example demonstrates how to implement a minimal BLE Device Information Service (DIS) on the ESP32 using the ESP-IDF framework and the espressif/ble_services component.

Prerequisites

Add the following managed components to your project:

idf.py add-dependency "espressif/ble_services^1.0.0"
idf.py add-dependency "espressif/ble_conn_mgr:^1.0.0"

Also, you need to configure the following options in your sdkconfig (or use idf.py menuconfig):

CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
CONFIG_BT_NIMBLE_LOG_LEVEL_INFO=y
CONFIG_BT_NIMBLE_LOG_LEVEL=1
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=3
CONFIG_BT_NIMBLE_MAX_BONDS=3
CONFIG_BT_NIMBLE_MAX_CCCDS=8
CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM=0
CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y
CONFIG_BT_NIMBLE_PINNED_TO_CORE=0
CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096
CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y
CONFIG_BT_NIMBLE_ROLE_BROADCASTER=y
CONFIG_BT_NIMBLE_GATT_SERVER=y
CONFIG_BT_NIMBLE_PRINT_ERR_NAME=y
CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="nimble"
CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=517
CONFIG_BT_NIMBLE_ATT_MAX_PREP_ENTRIES=64
CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE=0
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=12
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_SIZE=256
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_COUNT=24
CONFIG_BT_NIMBLE_MSYS_2_BLOCK_SIZE=320
CONFIG_BT_NIMBLE_TRANSPORT_ACL_FROM_LL_COUNT=24
CONFIG_BT_NIMBLE_TRANSPORT_ACL_SIZE=255
CONFIG_BT_NIMBLE_TRANSPORT_EVT_SIZE=70
CONFIG_BT_NIMBLE_TRANSPORT_EVT_COUNT=30
CONFIG_BT_NIMBLE_TRANSPORT_EVT_DISCARD_COUNT=8
CONFIG_BT_NIMBLE_L2CAP_COC_SDU_BUFF_COUNT=1
CONFIG_BT_NIMBLE_GATT_MAX_PROCS=4
CONFIG_BT_NIMBLE_RPA_TIMEOUT=900
CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=y
CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=2000
CONFIG_BT_NIMBLE_ENABLE_CONN_REATTEMPT=y
CONFIG_BT_NIMBLE_MAX_CONN_REATTEMPT=3
CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y
CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_2M_PHY=y
CONFIG_BT_NIMBLE_LL_CFG_FEAT_LE_CODED_PHY=y
CONFIG_BT_NIMBLE_WHITELIST_SIZE=12
CONFIG_BT_NIMBLE_USE_ESP_TIMER=y
CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE=y
CONFIG_BT_NIMBLE_BLE_GATT_BLOB_TRANSFER=y
CONFIG_BT_NIMBLE_PROX_SERVICE=y
CONFIG_BT_NIMBLE_ANS_SERVICE=y
CONFIG_BT_NIMBLE_CTS_SERVICE=y
CONFIG_BT_NIMBLE_HTP_SERVICE=y
CONFIG_BT_NIMBLE_IPSS_SERVICE=y
CONFIG_BT_NIMBLE_TPS_SERVICE=y
CONFIG_BT_NIMBLE_IAS_SERVICE=y
CONFIG_BT_NIMBLE_LLS_SERVICE=y
CONFIG_BT_NIMBLE_SPS_SERVICE=y
CONFIG_BT_NIMBLE_HR_SERVICE=y
CONFIG_BT_NIMBLE_DIS_SERVICE=y
CONFIG_BT_NIMBLE_SVC_DIS_MANUFACTURER_NAME=y
CONFIG_BT_NIMBLE_SVC_DIS_SERIAL_NUMBER=y
CONFIG_BT_NIMBLE_SVC_DIS_HARDWARE_REVISION=y
CONFIG_BT_NIMBLE_SVC_DIS_FIRMWARE_REVISION=y
CONFIG_BT_NIMBLE_GAP_SERVICE=y
CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM=0
CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ENC=0
CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHN=0
CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHR=0
CONFIG_BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP=y
CONFIG_BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION=-1
CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL=0
CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL=0
CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SLAVE_LATENCY=0
CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO=0
CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM=0
CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_ENC=0
CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHEN=0
CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHOR=0
CONFIG_BT_NIMBLE_SVC_GAP_GATT_SECURITY_LEVEL=y
CONFIG_BT_NIMBLE_HCI_UART_FLOW_CTRL=0
CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=19
CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23
CONFIG_BT_NIMBLE_EATT_CHAN_NUM=0
CONFIG_BT_CTRL_BLE_MAX_ACT=6
CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=6
CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0
CONFIG_BT_BLE_CCA_MODE_NONE=y
CONFIG_BT_BLE_CCA_MODE=0
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
CONFIG_BT_CTRL_BLE_SCAN_DUPL=y
CONFIG_BT_CTRL_BLE_MASTER=y
CONFIG_BT_CTRL_BLE_SCAN=y
CONFIG_BT_CTRL_BLE_SECURITY_ENABLE=y
CONFIG_BT_CTRL_BLE_ADV=y
CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y
CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y
CONFIG_ESP_PHY_ENABLE_USB=y
CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y
CONFIG_ESP_WIFI_ENABLE_SAE_PK=y
CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y
CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y
CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y
CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y
CONFIG_UNITY_ENABLE_FLOAT=y
CONFIG_UNITY_ENABLE_DOUBLE=y
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y
CONFIG_WIFI_PROV_BLE_SEC_CONN=y
CONFIG_BLE_CONN_MGR_ROLE_PERIPHERAL=y
CONFIG_BLE_CONN_MGR_ROLE=0
CONFIG_BLE_CONN_MGR_WAIT_DURATION=31
CONFIG_BLE_DIS=y
CONFIG_BLE_DIS_STR_MAX=32
CONFIG_BLE_DIS_MODEL="ESP"
CONFIG_BLE_DIS_MANUF="Manufacturer"
CONFIG_BLE_DIS_SYSTEM_ID="System"
CONFIG_BLE_DIS_PNP=y
CONFIG_BLE_DIS_PNP_VID_SRC=1
CONFIG_BLE_DIS_PNP_VID=0
CONFIG_BLE_DIS_PNP_PID=0
CONFIG_BLE_DIS_PNP_VER=1
CONFIG_NIMBLE_ENABLED=y
CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
CONFIG_NIMBLE_MAX_CONNECTIONS=3
CONFIG_NIMBLE_MAX_BONDS=3
CONFIG_NIMBLE_MAX_CCCDS=8
CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0
CONFIG_NIMBLE_PINNED_TO_CORE_0=y
CONFIG_NIMBLE_PINNED_TO_CORE=0
CONFIG_NIMBLE_TASK_STACK_SIZE=4096
CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096
CONFIG_NIMBLE_ROLE_PERIPHERAL=y
CONFIG_NIMBLE_ROLE_BROADCASTER=y
CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble"
CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31
CONFIG_NIMBLE_ATT_PREFERRED_MTU=517
CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0
CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12
CONFIG_BT_NIMBLE_ACL_BUF_COUNT=24
CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70
CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30
CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8
CONFIG_NIMBLE_RPA_TIMEOUT=900
CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS=y
CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y

main.cpp

In main.c or main.cpp, include the BLE header file:

#include "BLE.hpp"

In the app_main() function, you need to add

ESP_ERROR_CHECK(esp_event_loop_create_default());

to the beginning of the function, unless you already create an event loop!

Only after creating the event loop, you can call InitBLE() to initialize the BLE stack and start advertising.

#include <stdio.h>

## `BLE.hpp`

```hpp
#pragma once

void InitBLE();

BLE.cpp

#include "BLE.hpp"

#include "esp_log.h"
#include "nvs_flash.h"

#include "esp_idf_version.h"
#include "esp_random.h"
#include "esp_mac.h"
#include "esp_system.h"

#include "esp_ble_conn_mgr.h"
#include "esp_dis.h"

static const char *TAG = "BLE";

static void app_ble_dis_init(void)
{
    esp_ble_dis_init();
    esp_ble_dis_pnp_t pnp_id = {
        .src = CONFIG_BLE_DIS_PNP_VID_SRC,
        .vid = CONFIG_BLE_DIS_PNP_VID,
        .pid = CONFIG_BLE_DIS_PNP_PID,
        .ver = CONFIG_BLE_DIS_PNP_VER
    };
    static char mac_str[19] = {0};
    uint8_t mac[6];
    ESP_ERROR_CHECK(esp_read_mac((uint8_t *)mac, ESP_MAC_BT));
    sprintf(mac_str, MACSTR, MAC2STR(mac));
    esp_ble_dis_set_model_number("MyModel");
    esp_ble_dis_set_serial_number(mac_str);
    esp_ble_dis_set_firmware_revision("R1.2.3");
    esp_ble_dis_set_hardware_revision("Rev2");
    esp_ble_dis_set_software_revision("v0.1.2");
    esp_ble_dis_set_manufacturer_name("MyCompany");
    esp_ble_dis_set_system_id("My system ID");
    esp_ble_dis_set_pnp_id(&pnp_id);
}

static void app_ble_conn_event_handler(void *handler_args, esp_event_base_t base, int32_t id, void *event_data)
{
    if (base != BLE_CONN_MGR_EVENTS) {
        return;
    }

    esp_ble_dis_pnp_t pnp_id;
    const char *model_number = NULL;
    const char *serial_number = NULL;
    const char *firmware_revision = NULL;
    const char *hardware_revision = NULL;
    const char *software_revision = NULL;
    const char *manufacturer = NULL;
    const char *system_id = NULL;
    switch (id) {
    case ESP_BLE_CONN_EVENT_CONNECTED:
        ESP_LOGI(TAG, "ESP_BLE_CONN_EVENT_CONNECTED");
        esp_ble_dis_get_pnp_id(&pnp_id);
        esp_ble_dis_get_model_number(&model_number);
        esp_ble_dis_get_serial_number(&serial_number);
        esp_ble_dis_get_firmware_revision(&firmware_revision);
        esp_ble_dis_get_hardware_revision(&hardware_revision);
        esp_ble_dis_get_software_revision(&software_revision);
        esp_ble_dis_get_manufacturer_name(&manufacturer);
        esp_ble_dis_get_system_id(&system_id);
        ESP_LOGI(TAG, "Model name %s", model_number);
        ESP_LOGI(TAG, "Serial Number %s", serial_number);
        ESP_LOGI(TAG, "Firmware revision %s", firmware_revision);
        ESP_LOGI(TAG, "Hardware revision %s", hardware_revision);
        ESP_LOGI(TAG, "Software revision %s", software_revision);
        ESP_LOGI(TAG, "Manufacturer name %s", manufacturer);
        ESP_LOGI(TAG, "System ID %s", system_id);
        ESP_LOGI(TAG, "PnP ID Vendor ID Source 0x%0x, Vendor ID 0x%02x, Product ID 0x%02x, Product Version 0x%02x",
                 pnp_id.src, pnp_id.vid, pnp_id.pid, pnp_id.ver);
        break;
    case ESP_BLE_CONN_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "ESP_BLE_CONN_EVENT_DISCONNECTED");
        break;
    default:
        break;
    }
}

void InitBLE(void)
{
    esp_ble_conn_config_t config = {
        .device_name = "MTX-ZR",
        .broadcast_data = "Metexon",
    };

    esp_err_t ret;

    // Initialize NVS
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    esp_event_handler_register(BLE_CONN_MGR_EVENTS, ESP_EVENT_ANY_ID, app_ble_conn_event_handler, NULL);

    esp_ble_conn_init(&config);
    app_ble_dis_init();
    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);
    }
}