213 lines
8.4 KiB
C
213 lines
8.4 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
|
*
|
|
* ESP-BLE-UART example. Backend (NimBLE / Bluedroid) is picked
|
|
* by the host-stack Kconfig at compile time. Whatever the central
|
|
* writes to the RX characteristic is echoed back over TX.
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
|
|
#include "esp_log.h"
|
|
#include "esp_mac.h"
|
|
#include "nvs_flash.h"
|
|
#include "sdkconfig.h"
|
|
|
|
#include "ble_uart.h"
|
|
|
|
#if CONFIG_EXAMPLE_CUSTOM_ADV_DATA
|
|
/* Sample advertising payload demonstrating ble_uart_config_t::adv_data.
|
|
* Replace these bytes with whatever your product needs (a different
|
|
* Local Name, Manufacturer Specific Data, custom Service Data,
|
|
* additional Service UUIDs, ...) — ble_uart broadcasts them verbatim.
|
|
*
|
|
* Format: a sequence of standard BT Core "AD structure" triplets,
|
|
* [length(1)] [AD type(1)] [value(length-1)].
|
|
*
|
|
* Length budget: total ≤ BLE_UART_ADV_DATA_MAX (28). The mandatory
|
|
* 3-byte Flags AD is prepended by ble_uart and does NOT count against
|
|
* this budget; oversize fails ble_uart_install() with EINVAL.
|
|
*
|
|
* The current contents (purely illustrative — edit freely):
|
|
*
|
|
* Layout bytes
|
|
* -------------------------------------- -----
|
|
* Complete Local Name AD "BleUart" 1 + 1 + 7 = 9
|
|
* Complete 128-bit UUID AD 1 + 1 + 16 = 18
|
|
* -------------------------------------- -----
|
|
* total 27 (≤ 28)
|
|
*/
|
|
static const uint8_t example_adv_payload[] = {
|
|
/* AD type 0x09: Complete Local Name */
|
|
0x08, 0x09, 'B', 'l', 'e', 'U', 'a', 'r', 't',
|
|
|
|
/* AD type 0x07: Complete List of 128-bit Service UUIDs.
|
|
* UUID bytes are in over-the-air (little-endian) order, matching
|
|
* ble_uart_service_uuid.bytes[]. */
|
|
0x11, 0x07,
|
|
0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0,
|
|
0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e,
|
|
};
|
|
#endif
|
|
|
|
static const char *TAG = "app";
|
|
|
|
static void ble_uart_on_rx(const uint8_t *data, size_t len)
|
|
{
|
|
ESP_LOGI(TAG, "rx len: %u bytes", (unsigned)len);
|
|
if (data == NULL || len == 0) {
|
|
return;
|
|
}
|
|
ESP_LOG_BUFFER_HEX(TAG, data, len);
|
|
ble_uart_tx(data, len); /* echo back */
|
|
}
|
|
|
|
/* Lifecycle / link-state event sink. Runs on the BLE host task —
|
|
* keep it short, never call ble_uart_close()/uninstall() from here.
|
|
*
|
|
* For production code: gate any sensitive TX on
|
|
* BLE_UART_EVT_LINK_SECURE (encrypted+authenticated) instead of just
|
|
* "connected"; ble_uart_is_connected() returns true while the link is
|
|
* still plaintext during the pairing window. */
|
|
static void ble_uart_on_event(const ble_uart_evt_t *e)
|
|
{
|
|
switch (e->id) {
|
|
case BLE_UART_EVT_CONNECTED: {
|
|
const uint8_t *b = e->connected.peer.bytes;
|
|
ESP_LOGI(TAG,
|
|
"evt: connected peer=%02x:%02x:%02x:%02x:%02x:%02x type=%u",
|
|
b[0], b[1], b[2], b[3], b[4], b[5], e->connected.peer.type);
|
|
break;
|
|
}
|
|
case BLE_UART_EVT_DISCONNECTED:
|
|
ESP_LOGI(TAG, "evt: disconnected reason=0x%x",
|
|
e->disconnected.reason);
|
|
break;
|
|
case BLE_UART_EVT_SUBSCRIBED:
|
|
ESP_LOGI(TAG, "evt: %ssubscribed",
|
|
e->subscribed.subscribed ? "" : "un");
|
|
break;
|
|
case BLE_UART_EVT_LINK_SECURE:
|
|
ESP_LOGI(TAG, "evt: link_secure enc=%d auth=%d bond=%d ks=%u",
|
|
e->link_secure.encrypted, e->link_secure.authenticated,
|
|
e->link_secure.bonded, e->link_secure.key_size);
|
|
break;
|
|
case BLE_UART_EVT_PASSKEY_DISPLAY:
|
|
ESP_LOGI(TAG, "evt: passkey=%06" PRIu32, e->passkey.passkey);
|
|
break;
|
|
case BLE_UART_EVT_PASSKEY_REQUEST:
|
|
/* Fires only when cfg.security.io_cap is KEYBOARD_ONLY or
|
|
* KEYBOARD_DISPLAY (this example leaves io_cap at AUTO →
|
|
* DisplayOnly, so it should not fire). For a real keypad
|
|
* product, prompt the user for the 6 digits the central
|
|
* displayed and feed them in:
|
|
*
|
|
* ble_uart_passkey_reply(digits);
|
|
*
|
|
* See PORTING.md §5.6.1 for the full pattern. */
|
|
ESP_LOGW(TAG, "evt: passkey entry requested — no UI wired in this "
|
|
"example (see PORTING.md §5.6.1)");
|
|
break;
|
|
case BLE_UART_EVT_NUMERIC_COMPARE:
|
|
/* Fires only when cfg.security.io_cap is DISPLAY_YES_NO or
|
|
* KEYBOARD_DISPLAY (likewise dormant in this example). For a
|
|
* product with a yes/no control, surface the digits to the
|
|
* user and resolve the comparison:
|
|
*
|
|
* ble_uart_compare_reply(user_says_match);
|
|
*
|
|
* See PORTING.md §5.6.1. */
|
|
ESP_LOGW(TAG, "evt: numeric compare %06" PRIu32
|
|
" — no yes/no UI wired (see PORTING.md §5.6.1)",
|
|
e->numeric_compare.passkey);
|
|
break;
|
|
case BLE_UART_EVT_PAIRING_FAILED:
|
|
ESP_LOGW(TAG, "evt: pairing failed reason=0x%x",
|
|
e->pairing_failed.reason);
|
|
break;
|
|
case BLE_UART_EVT_CLOSED:
|
|
/* Only after ble_uart_close_async(). This example does not use
|
|
* close_async; do not ble_uart_uninstall() here — defer to an
|
|
* app task (PORTING.md §5.3.2). Kept for -Wswitch. */
|
|
if (e->closed.status == BLE_UART_OK) {
|
|
ESP_LOGI(TAG, "evt: closed (async-close succeeded)");
|
|
} else {
|
|
ESP_LOGW(TAG, "evt: closed async-close failed status=%d",
|
|
e->closed.status);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void app_main(void)
|
|
{
|
|
/* NVS is required by the BT controller (PHY calibration) and the
|
|
* bond store, so it must be live before ble_uart_install(). */
|
|
esp_err_t err = nvs_flash_init();
|
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
|
err = nvs_flash_init();
|
|
}
|
|
ESP_ERROR_CHECK(err);
|
|
|
|
/* Device name = "<prefix>-XXXX" with XXXX = last two MAC bytes.
|
|
* If esp_read_mac() fails, mac stays zero and the suffix degrades
|
|
* to "0000" — log so the operator notices uniqueness was lost. */
|
|
uint8_t mac[6] = {0};
|
|
esp_err_t mac_err = esp_read_mac(mac, ESP_MAC_BT);
|
|
if (mac_err != ESP_OK) {
|
|
ESP_LOGW(TAG, "esp_read_mac(BT) failed (%s); device name suffix will be 0000",
|
|
esp_err_to_name(mac_err));
|
|
}
|
|
char name[BLE_UART_DEVICE_NAME_MAX + 1];
|
|
snprintf(name, sizeof(name), "%s-%02X%02X",
|
|
CONFIG_BLE_UART_DEVICE_NAME_PREFIX, mac[4], mac[5]);
|
|
|
|
ESP_ERROR_CHECK(ble_uart_install(&(ble_uart_config_t){
|
|
.encrypted = true,
|
|
.device_name = name,
|
|
#if CONFIG_EXAMPLE_CUSTOM_ADV_DATA
|
|
/* Hand the application-defined bytes to ble_uart. Whatever
|
|
* the array contains is broadcast verbatim; what `device_name`
|
|
* (above) holds is exposed via the GAP service for connected
|
|
* centrals to read — independent paths. */
|
|
.adv_data = example_adv_payload,
|
|
.adv_data_len = sizeof(example_adv_payload),
|
|
/* scan_rsp_data is left at its default (NULL) → ble_uart still
|
|
* sends its built-in scan response. Override it the same way
|
|
* if you want to control those bytes too. */
|
|
#endif
|
|
.ble_uart_on_rx = ble_uart_on_rx,
|
|
.on_event = ble_uart_on_event,
|
|
}));
|
|
|
|
/* Demonstrate the bond-management API: list every bonded peer
|
|
* already on flash. Replace the log with `ble_uart_clear_bonds()`
|
|
* to wipe them at boot (e.g. when a "factory reset" GPIO is held);
|
|
* use `ble_uart_remove_peer(&list[i])` to target one specifically. */
|
|
size_t total = 0;
|
|
ble_uart_addr_t list[8];
|
|
int rc = ble_uart_get_bonded_peers(list, sizeof(list) / sizeof(list[0]),
|
|
&total);
|
|
if (rc == 0) {
|
|
ESP_LOGI(TAG, "%u peer(s) currently bonded", (unsigned)total);
|
|
size_t shown = total < sizeof(list) / sizeof(list[0])
|
|
? total : sizeof(list) / sizeof(list[0]);
|
|
for (size_t i = 0; i < shown; i++) {
|
|
const uint8_t *b = list[i].bytes;
|
|
ESP_LOGI(TAG, " [%u] %02x:%02x:%02x:%02x:%02x:%02x type=%u",
|
|
(unsigned)i,
|
|
b[0], b[1], b[2], b[3], b[4], b[5], list[i].type);
|
|
}
|
|
if (total > shown) {
|
|
ESP_LOGI(TAG, " (%u more not shown)",
|
|
(unsigned)(total - shown));
|
|
}
|
|
}
|
|
|
|
ESP_ERROR_CHECK(ble_uart_open());
|
|
}
|