mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
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:
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user