feat(bt/ble_uart): Support ble uart more interfaces

- Added tagged event API (ble_uart_evt_t / on_event)
- Added bond management APIs
- Supported custom adv_data / scan_rsp_data
- Validate device_name length synchronously
- Added ble_uart_close_async() and EVT_CLOSED
- Added granular security config (security struct)
- Supported Passkey Entry and Numeric Comparison


(cherry picked from commit f1d9c994d2)

Co-authored-by: zhiweijian <zhiweijian@espressif.com>
This commit is contained in:
Zhi Wei Jian
2026-06-26 09:07:56 +08:00
parent 63329dbeb2
commit c6bbef1dc5
10 changed files with 3381 additions and 234 deletions
+157 -23
View File
@@ -24,12 +24,19 @@ ble_uart_install(&cfg); // NimBLE host + BLE UART GATT service
ble_uart_open(); // start advertising + auto-encrypt
```
…and two matching tear-down calls if your app ever needs to power
BLE off at runtime:
If your app powers BLE off at runtime, use **one** of the release paths
in [PORTING.md §5.3](../common/ble_uart/PORTING.md#53-lifecycle--bring-up-and-release)
(this example uses Path A from `app_main`):
| Path | When | Calls |
| --- | --- | --- |
| **A — sync** (default) | Shutdown from a normal task (button, Wi-Fi, `app_main`) | `ble_uart_close()` → `ble_uart_uninstall()` |
| **B — async** | Shutdown triggered inside `on_event` / `on_rx` | `close_async()` in callback → `CLOSED` sets flag → **`uninstall()` on a separate app task** (not inside `CLOSED`) |
```c
ble_uart_close(); // stop advertising / disconnect / halt host
ble_uart_uninstall(); // free the NimBLE port + reset state
/* Path A — this example style */
ble_uart_close();
ble_uart_uninstall();
```
When a central connects, the firmware automatically initiates LE Secure
@@ -47,49 +54,135 @@ back with `ble_uart_tx()`.
| TX (out) | `6e400003-b5a3-f393-e0a9-e50e24dcca9e` | Notify (auto-CCCD) | encrypted, authenticated |
The `_ENC | _AUTHEN` flags are turned on only when `cfg.encrypted = true`
(the default in this example).
(the default in this example). The two flags can be controlled
independently via `cfg.security.mitm` (drops `_AUTHEN`) and the
combined `cfg.security.{sc,bonding,mitm}` set (all OFF drops `_ENC`
too) — see PORTING.md §5.6.
## Files
| File | Lines | Role |
| --- | ---: | --- |
| `main/main.c` | ~70 | NVS init, MAC-derived device name, install + open, RX echo handler. Identical for both backends. |
| `main/main.c` | ~200 | NVS init, install + open with `<prefix>-XXXX` device name (Kconfig prefix + BT MAC suffix), RX echo handler, lifecycle/link-state event sink, bonded-peer dump on boot. Identical for both backends. |
| `main/Kconfig.projbuild` | ~50 | Example-local `EXAMPLE_CUSTOM_ADV_DATA` switch — toggles the `ble_uart_config_t::adv_data` demo path in `main.c`. |
| `CMakeLists.txt` (root) | ~15 | `list(APPEND EXTRA_COMPONENT_DIRS .../common/ble_uart)` before `project()` so `main` can `REQUIRES ble_uart`. |
| `../common/ble_uart/ble_uart.h` | ~155 | Stack-agnostic public API: 3-field config + 4 lifecycle functions + TX/status + UUID + `BLE_UART_E*` return codes. No NimBLE / Bluedroid types leak through. |
| `../common/ble_uart/ble_uart_nimble.c` | ~650 | NimBLE backend: host bring-up, BLE UART GATT service via `ble_gatts_add_svcs`, advertising, pairing, install/open/close/uninstall. Active when `CONFIG_BT_NIMBLE_ENABLED=y`. |
| `../common/ble_uart/ble_uart_bluedroid.c` | ~1020 | Bluedroid backend: controller + host enable, BLE UART GATT service via `esp_ble_gatts_create_attr_tab` (service-table API), advertising, pairing, full PREP/EXEC long-write reassembly, install/open/close/uninstall. Active when `CONFIG_BT_BLUEDROID_ENABLED=y`. |
| `../common/ble_uart/Kconfig` | ~30 | Device-name prefix + RX scratch size (`menuconfig → Component configuration → ESP-BLE-UART library`). |
| `../common/ble_uart/PORTING.md` | ~724 | Porting and API guide (integration, CMake, sdkconfig, thread safety). |
| `../common/ble_uart/ble_uart.h` | ~640 | Stack-agnostic public API: configuration struct (preset + per-feature security overrides + custom adv payload + RX/event callbacks) + lifecycle (install/open/close/close_async/uninstall) + TX + pairing replies + bond-management + status + UUID + `BLE_UART_E*` return codes. No NimBLE / Bluedroid types leak through. |
| `../common/ble_uart/ble_uart_nimble.c` | ~1290 | NimBLE backend: host bring-up, BLE UART GATT service via `ble_gatts_add_svcs`, advertising (default + raw), pairing (incl. Passkey Entry / Numeric Comparison), bond store, async close, install/open/close/uninstall. Active when `CONFIG_BT_NIMBLE_ENABLED=y`. |
| `../common/ble_uart/ble_uart_bluedroid.c` | ~1660 | Bluedroid backend: controller + host enable, BLE UART GATT service via `esp_ble_gatts_create_attr_tab` (service-table API), advertising (default + raw), pairing (incl. Passkey Entry / Numeric Comparison), bond store, async close, full PREP/EXEC long-write reassembly, install/open/close/uninstall. Active when `CONFIG_BT_BLUEDROID_ENABLED=y`. |
| `../common/ble_uart/Kconfig` | ~30 | Device name prefix + RX scratch size (`menuconfig → Component configuration → ESP-BLE-UART library`). |
| `../common/ble_uart/PORTING.md` | ~1300 | Porting and API guide (integration, CMake, sdkconfig, security model, custom advertising, bond management, thread safety). |
| `sdkconfig.defaults` | — | Default: NimBLE backend, MTU 512, SC + bonding + persistent NVS. |
| `sdkconfig.bluedroid` | — | Overlay: switch to Bluedroid backend (used via `-D SDKCONFIG_DEFAULTS=...`, see "Choosing the host stack" below). |
## Public API
```c
typedef void (*ble_uart_rx_cb_t)(const uint8_t *data, size_t len);
typedef void (*ble_uart_rx_cb_t) (const uint8_t *data, size_t len);
typedef void (*ble_uart_evt_cb_t)(const ble_uart_evt_t *evt);
typedef struct {
bool encrypted; /* SC + Bonding + MITM in one knob */
const char *device_name;
ble_uart_rx_cb_t ble_uart_on_rx;
ble_uart_sec_t sc; /* AUTO / OFF / ON */
ble_uart_sec_t bonding;
ble_uart_sec_t mitm;
ble_uart_io_cap_t io_cap; /* AUTO / NO_INPUT_OUTPUT / DISPLAY_ONLY /
KEYBOARD_ONLY / DISPLAY_YES_NO /
KEYBOARD_DISPLAY */
} ble_uart_security_t;
typedef struct {
bool encrypted; /* preset: SC + Bonding + MITM + DisplayOnly */
ble_uart_security_t security; /* per-feature overrides; see PORTING.md §5.6 */
const char *device_name; /* ≤ BLE_UART_DEVICE_NAME_MAX (26) */
/* Optional: raw advertising / scan-response bytes (NULL → defaults).
* Limits: adv_data_len ≤ BLE_UART_ADV_DATA_MAX (28),
* scan_rsp_data_len ≤ BLE_UART_SCAN_RSP_DATA_MAX (31).
* The 3-byte Flags AD element is prepended automatically — don't
* include it in adv_data. See PORTING.md §5.9 for examples. */
const uint8_t *adv_data;
size_t adv_data_len;
const uint8_t *scan_rsp_data;
size_t scan_rsp_data_len;
ble_uart_rx_cb_t ble_uart_on_rx;
ble_uart_evt_cb_t on_event; /* lifecycle / link-state events; NULL drops */
} ble_uart_config_t;
typedef struct {
uint8_t bytes[6]; /* big-endian: bytes[0] is the MSB (AA:BB:CC:DD:EE:FF) */
uint8_t type; /* BLE_UART_ADDR_TYPE_PUBLIC | _RANDOM */
} ble_uart_addr_t;
/* Lifecycle */
int ble_uart_install(const ble_uart_config_t *cfg); /* NimBLE host + GATT */
int ble_uart_install(const ble_uart_config_t *cfg); /* host + GATT */
int ble_uart_open(void); /* host task + advertising */
int ble_uart_close(void); /* stop adv / disconnect / halt host */
int ble_uart_uninstall(void); /* free NimBLE port + reset state */
int ble_uart_close_async(void); /* same, fire-and-forget; safe from inside on_event/on_rx */
int ble_uart_uninstall(void); /* free port + reset state */
/* Data path */
int ble_uart_tx(const uint8_t *data, size_t len);
/* Pairing replies (call from on_event for input-capable IO caps) */
int ble_uart_passkey_reply(uint32_t passkey); /* answer PASSKEY_REQUEST */
int ble_uart_compare_reply(bool match); /* answer NUMERIC_COMPARE */
/* Status (best-effort snapshot) */
bool ble_uart_is_connected(void);
bool ble_uart_is_subscribed(void);
/* Bond management (works after install()) */
int ble_uart_get_bond_count(size_t *out_count);
int ble_uart_get_bonded_peers(ble_uart_addr_t *out, size_t cap, size_t *out_count);
int ble_uart_remove_peer(const ble_uart_addr_t *peer);
int ble_uart_clear_bonds(void);
extern const ble_uart_uuid128_t ble_uart_service_uuid;
```
### Event callback
`on_event` is invoked on the BLE host task (same context as `ble_uart_on_rx`)
with a tagged `ble_uart_evt_t`. Use `LINK_SECURE` — not `is_connected()` —
to gate any application logic that requires the channel to be encrypted /
authenticated:
| `evt->id` | Payload | Fires when |
| ------------------------------- | ------------------------------------------------------- | ---------- |
| `BLE_UART_EVT_CONNECTED` | `connected.peer` | Physical link up |
| `BLE_UART_EVT_DISCONNECTED` | `disconnected.reason` (int, stack-specific) | Physical link down — Bluedroid: `esp_gatt_conn_reason_t`; NimBLE: BLE host return code (`BLE_HS_HCI_ERR()` for HCI) |
| `BLE_UART_EVT_SUBSCRIBED` | `subscribed.subscribed` | Central writes CCCD on TX (edge-triggered) |
| `BLE_UART_EVT_LINK_SECURE` | `link_secure.{encrypted,authenticated,bonded,key_size}` | Pairing or bonded reconnect succeeds |
| `BLE_UART_EVT_PASSKEY_DISPLAY` | `passkey.passkey` (0..999999) | SM asks the device to show a passkey |
| `BLE_UART_EVT_PASSKEY_REQUEST` | — | SM asks the user to enter a passkey shown by the central — answer with `ble_uart_passkey_reply()` |
| `BLE_UART_EVT_NUMERIC_COMPARE` | `numeric_compare.passkey` (0..999999) | SM asks the user to confirm both sides display the same value — answer with `ble_uart_compare_reply()` |
| `BLE_UART_EVT_PAIRING_FAILED` | `pairing_failed.reason` (stack-specific) | Pairing rejected or timed out |
| `BLE_UART_EVT_CLOSED` | `closed.status` (`BLE_UART_*`) | `ble_uart_close_async()` worker has finished; `BLE_UART_OK` means tear-down succeeded |
The default passkey UART banner still prints; the callback is additive so
log-scraping tests stay compatible. Don't block in the callback.
**Callback rules:**
- Do **not** call `ble_uart_close()` or `ble_uart_uninstall()` from
`on_event` / `on_rx` (host task — deadlocks).
- To start teardown from a callback, call `ble_uart_close_async()` only.
- Call `ble_uart_uninstall()` from a **normal app task** after
`BLE_UART_EVT_CLOSED` with `closed.status == BLE_UART_OK` (see
[PORTING.md §5.3.2](../common/ble_uart/PORTING.md#532-path-b--release-after-a-ble-event-close_async)).
Path B sketch (full code in PORTING.md):
```c
case BLE_UART_EVT_PAIRING_FAILED:
ble_uart_close_async();
break;
case BLE_UART_EVT_CLOSED:
if (e->closed.status == BLE_UART_OK) {
s_ble_closed_ok = true; /* app task calls uninstall */
}
break;
```
## Choosing the host stack
The same `ble_uart.h` API is implemented twice — once on top of NimBLE
@@ -134,8 +227,10 @@ When neither is enabled the build fails up-front with a clear error.
idf.py set-target esp32c3 # or esp32, esp32s3, esp32c6, esp32h2 ...
idf.py menuconfig # optional
# Component configuration -> ESP-BLE-UART library
# - BLE device name prefix (default: BleUart)
# - BLE device name prefix (default: BleUart; example appends -XXXX from BT MAC)
# - RX scratch buffer size (default: 1024 bytes)
# BLE UART service example
# - Use custom advertising data (default: off)
```
Those `BLE_UART_*` options are defined in **`../common/ble_uart/Kconfig`**
@@ -143,6 +238,27 @@ Those `BLE_UART_*` options are defined in **`../common/ble_uart/Kconfig`**
build (this example pulls it in via `EXTRA_COMPONENT_DIRS` in the root
`CMakeLists.txt`).
`EXAMPLE_CUSTOM_ADV_DATA` is example-local (`main/Kconfig.projbuild`)
and demonstrates `ble_uart_config_t::adv_data` — the field that lets
the application fully control the over-the-air advertising payload
instead of using the library default.
When the option is on, `app_main` hands a static byte array
(`example_adv_payload[]`, top of `main.c`) to `ble_uart_install()`.
The array is just a sequence of `[length][AD type][value]` triplets;
edit it directly to advertise whatever you want — a different Local
Name, Manufacturer Specific Data, custom Service Data, additional
Service UUIDs, etc. The only hard rule is total length ≤
`BLE_UART_ADV_DATA_MAX` (28); the 3-byte Flags AD is added by the
library and does not count against that budget.
The GAP-service Device Name (set via `device_name` in the same
config struct) is independent and is what connected centrals read
post-pair, regardless of `adv_data`.
With the option off the library default is used (Complete Local Name
in the primary packet, 128-bit Service UUID in the scan response).
The two security knobs are set in `sdkconfig.defaults`:
```ini
@@ -154,6 +270,17 @@ Disable `cfg.encrypted` in `main.c` (set it to `false`) for plaintext
operation in the lab — the GATT characteristics drop their `_ENC`
flags accordingly. Production firmware should keep encryption on.
For finer control without going all-or-nothing — e.g. a displayless
gateway that wants encryption + bonding but no passkey UI, or a
device with a keypad that wants Passkey Entry / Numeric Comparison —
keep `cfg.encrypted = true` and override individual bits via
`cfg.security.{sc,bonding,mitm,io_cap}`. The input-capable IO caps
(`KEYBOARD_ONLY`, `DISPLAY_YES_NO`, `KEYBOARD_DISPLAY`) require an
`on_event` handler that wires `BLE_UART_EVT_PASSKEY_REQUEST` /
`NUMERIC_COMPARE` to `ble_uart_passkey_reply()` /
`ble_uart_compare_reply()`. See PORTING.md §5.6 for the full matrix
and worked examples.
### Build & flash
```bash
@@ -170,7 +297,7 @@ I (xxx) ble_uart: registered chr 6e400002-... def=15 val=16
I (xxx) ble_uart: registered chr 6e400003-... def=17 val=18
I (xxx) ble_uart: addr=80:7d:3a:11:22:33
I (xxx) ble_uart: BLE host task started
I (xxx) ble_uart: advertising as 'BleUart-XXXX'
I (xxx) ble_uart: advertising as 'BleUart-2233'
```
Expected boot log (Bluedroid backend):
@@ -186,8 +313,10 @@ I (xxx) ble_uart: advertising started
1. On a phone, install **a BLE GATT client app** that supports scanning,
pairing, characteristic write, and notify/CCCD (many mobile “BLE tools”
or serial-over-BLE utilities qualify).
2. Scan, tap **Connect** on `BleUart-XXXX`. The phone prompts for a
6-digit code.
2. Scan, tap **Connect** on `BleUart-XXXX` (prefix from
`CONFIG_BLE_UART_DEVICE_NAME_PREFIX`, `XXXX` = last two BT MAC
bytes). The phone prompts for a 6-digit
code.
3. The device prints a fresh code in a banner on UART:
```
@@ -204,8 +333,13 @@ I (xxx) ble_uart: advertising started
6. Disconnect and reconnect: no passkey prompt — the bond resumes
automatically.
To wipe the bond and force a fresh passkey, run `idf.py erase-flash`
and re-flash.
To wipe the bond and force a fresh passkey there are three options:
- Call `ble_uart_clear_bonds()` from your app (preserves the rest of NVS)
- Call `ble_uart_remove_peer(&addr)` to drop one peer (use the address
reported in `BLE_UART_EVT_CONNECTED`, or any address you happen to
have stored — Bluedroid matches by address only, NimBLE by identity)
- Run `idf.py erase-flash` and re-flash (also wipes WiFi creds, NVS, etc.)
## Adapting to your application
@@ -0,0 +1,47 @@
menu "BLE UART service example"
config EXAMPLE_CUSTOM_ADV_DATA
bool "Use custom advertising data"
default n
help
Demonstrates `ble_uart_config_t::adv_data` — the field that
lets the application fully control the advertising payload
instead of relying on the library default.
When enabled, the example passes a static byte array
(`example_adv_payload[]` defined at the top of `main.c`) to
`ble_uart_install()`. Edit that array to broadcast anything
you want: a different Local Name, Manufacturer Specific
Data, custom Service Data, multiple Service UUIDs, etc.
Format
The array is a sequence of standard Bluetooth Core "AD
structure" triplets:
[length(1)] [AD type(1)] [value(length-1)]
See the Bluetooth Assigned Numbers (Generic Access
Profile) document for the full type list.
Length budget
Total bytes in the array must be
≤ BLE_UART_ADV_DATA_MAX (28). The 3-byte mandatory
Flags AD element is prepended automatically by
ble_uart and does NOT count against this budget. An
oversized buffer makes `ble_uart_install()` fail with
BLE_UART_EINVAL.
Scope
Only affects the over-the-air advertising payload.
The GAP-service Device Name (UUID 0x2A00, set via
`device_name` in the same struct) is independent and
stays whatever the application configured — connected
centrals read that name regardless of what is in
`adv_data`.
Default value
Off. The library default is used (Complete Local Name
in the primary packet, 128-bit Service UUID in the
scan response).
endmenu
+150 -1
View File
@@ -8,6 +8,7 @@
* writes to the RX characteristic is echoed back over TX.
*/
#include <inttypes.h>
#include <stdio.h>
#include "esp_log.h"
@@ -17,6 +18,41 @@
#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)
@@ -29,6 +65,83 @@ static void ble_uart_on_rx(const uint8_t *data, size_t 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
@@ -49,15 +162,51 @@ void app_main(void)
ESP_LOGW(TAG, "esp_read_mac(BT) failed (%s); device name suffix will be 0000",
esp_err_to_name(mac_err));
}
char name[24];
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());
}
@@ -0,0 +1,24 @@
# CI build overlay: Bluedroid host (sdkconfig.defaults selects NimBLE).
# Mirrors sdkconfig.bluedroid; kept in sync for idf-build-apps CONFIG_NAME=bluedroid.
CONFIG_BT_NIMBLE_ENABLED=n
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=n
CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_BLE_SMP_ENABLE=y
CONFIG_BT_GATTS_ENABLE=y
# CONFIG_BT_GATTC_ENABLE is not set
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
# CONFIG_BT_BLE_42_DTM_TEST_EN is not set
CONFIG_BT_BLE_42_ADV_EN=y
# CONFIG_BT_BLE_42_SCAN_EN is not set
@@ -0,0 +1,7 @@
# CI build overlay: NimBLE host (sdkconfig.defaults is NimBLE-first).
# Explicit config so idf-build-apps builds both nimble and bluedroid in CI.
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_SM_SC=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y