feat(bt): add shared ble_uart component and update ble_uart_service/docs

This commit is contained in:
zhiweijian
2026-05-12 11:15:48 +08:00
parent c1d1ca00c4
commit 1fba5bbc36
15 changed files with 217 additions and 100 deletions
@@ -2,6 +2,10 @@
# CMakeLists in this exact order for cmake to work correctly. # CMakeLists in this exact order for cmake to work correctly.
cmake_minimum_required(VERSION 3.22) cmake_minimum_required(VERSION 3.22)
# Shared `ble_uart` component (must be visible before `project()` so
# `main` can `REQUIRES ble_uart`; path is relative to this example root).
list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../common/ble_uart")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON) idf_build_set_property(MINIMAL_BUILD ON)
project(ble_uart_service) project(ble_uart_service)
+50 -22
View File
@@ -4,9 +4,10 @@
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | | ----------------- | ----- | -------- | -------- | -------- | -------- | -------- |
A turnkey serial-over-BLE peripheral that implements the de-facto A turnkey serial-over-BLE peripheral that implements the de-facto
**Nordic UART Service** GATT layout (RX write, TX notify), so any **BLE UART-over-GATT** layout (RX write, TX notify; fixed 128-bit UUIDs
standard BLE-serial central (nRF Connect, Web Bluetooth examples, your below), so any widely used BLE-serial central (mobile GATT client apps,
own iOS / Android / Linux / Python scripts) can talk to it unchanged. Web Bluetooth examples, your own iOS / Android / Linux / Python scripts)
can talk to it unchanged.
The example ships with **two interchangeable backends** — NimBLE and The example ships with **two interchangeable backends** — NimBLE and
Bluedroid — both implementing the same stack-agnostic Bluedroid — both implementing the same stack-agnostic
@@ -19,7 +20,7 @@ manager, advertising, pairing, GAP event handling — is wrapped behind
**two function calls** in `app_main`: **two function calls** in `app_main`:
```c ```c
ble_uart_install(&cfg); // NimBLE host + NUS GATT service ble_uart_install(&cfg); // NimBLE host + BLE UART GATT service
ble_uart_open(); // start advertising + auto-encrypt ble_uart_open(); // start advertising + auto-encrypt
``` ```
@@ -53,12 +54,14 @@ The `_ENC | _AUTHEN` flags are turned on only when `cfg.encrypted = true`
| File | Lines | Role | | 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` | ~70 | NVS init, MAC-derived device name, install + open, RX echo handler. Identical for both backends. |
| `main/ble_uart.h` | ~260 | Stack-agnostic public API: 3-field config + 4 lifecycle functions + TX/status + UUID + `BLE_UART_E*` return codes. No NimBLE / Bluedroid types leak through. | | `CMakeLists.txt` (root) | ~15 | `list(APPEND EXTRA_COMPONENT_DIRS .../common/ble_uart)` before `project()` so `main` can `REQUIRES ble_uart`. |
| `main/ble_uart_nimble.c` | ~670 | NimBLE backend: host bring-up, NUS 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.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. |
| `main/ble_uart_bluedroid.c` | ~900 | Bluedroid backend: controller + host enable, NUS 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/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`. |
| `main/Kconfig.projbuild` | ~40 | Device-name prefix + RX scratch buffer size knobs. | | `../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 → BLE UART library`). |
| `../common/ble_uart/PORTING.md` | ~724 | Porting and API guide (integration, CMake, sdkconfig, thread safety). |
| `sdkconfig.defaults` | — | Default: NimBLE backend, MTU 512, SC + bonding + persistent NVS. | | `sdkconfig.defaults` | — | Default: NimBLE backend, MTU 512, SC + bonding + persistent NVS. |
| `sdkconfig.ci.bluedroid` | — | Overlay: switch to Bluedroid backend (used via `-D SDKCONFIG_DEFAULTS=...`, see "Choosing the host stack" below). | | `sdkconfig.bluedroid` | — | Overlay: switch to Bluedroid backend (used via `-D SDKCONFIG_DEFAULTS=...`, see "Choosing the host stack" below). |
## Public API ## Public API
@@ -91,10 +94,10 @@ extern const ble_uart_uuid128_t ble_uart_service_uuid;
The same `ble_uart.h` API is implemented twice — once on top of NimBLE The same `ble_uart.h` API is implemented twice — once on top of NimBLE
(`ble_uart_nimble.c`) and once on top of Bluedroid (`ble_uart_nimble.c`) and once on top of Bluedroid
(`ble_uart_bluedroid.c`). `main/CMakeLists.txt` registers both files; (`ble_uart_bluedroid.c`). The shared `ble_uart` component's
each guards its body with `#if CONFIG_BT_NIMBLE_ENABLED` / `#if `CMakeLists.txt` registers both files; each guards its body with
CONFIG_BT_BLUEDROID_ENABLED`, so exactly one becomes live at compile `#if CONFIG_BT_NIMBLE_ENABLED` / `#if CONFIG_BT_BLUEDROID_ENABLED`, so
time. exactly one becomes live at compile time.
Two ways to switch: Two ways to switch:
@@ -103,9 +106,9 @@ Two ways to switch:
idf.py menuconfig idf.py menuconfig
# Component config -> Bluetooth -> Host -> NimBLE / Bluedroid # Component config -> Bluetooth -> Host -> NimBLE / Bluedroid
# B. Apply the Bluedroid overlay non-interactively (great for CI) # B. Apply the Bluedroid overlay non-interactively (scripts / reproducible builds)
idf.py -B build_bd \ idf.py -B build_bd \
-D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.bluedroid" \ -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.bluedroid" \
reconfigure reconfigure
idf.py -B build_bd build flash monitor idf.py -B build_bd build flash monitor
``` ```
@@ -130,10 +133,16 @@ When neither is enabled the build fails up-front with a clear error.
```bash ```bash
idf.py set-target esp32c3 # or esp32, esp32s3, esp32c6, esp32h2 ... idf.py set-target esp32c3 # or esp32, esp32s3, esp32c6, esp32h2 ...
idf.py menuconfig # optional idf.py menuconfig # optional
# Component config -> BLE UART Example # Component configuration -> BLE UART library
# - BLE device name prefix (default: BleUart) # - BLE device name prefix (default: BleUart)
# - RX scratch buffer size (default: 1024 bytes)
``` ```
Those `BLE_UART_*` options are defined in **`../common/ble_uart/Kconfig`**
(the `ble_uart` component); they appear whenever `ble_uart` is part of the
build (this example pulls it in via `EXTRA_COMPONENT_DIRS` in the root
`CMakeLists.txt`).
The two security knobs are set in `sdkconfig.defaults`: The two security knobs are set in `sdkconfig.defaults`:
```ini ```ini
@@ -152,7 +161,7 @@ idf.py build flash monitor
``` ```
Expected boot log (NimBLE backend — the per-characteristic register Expected boot log (NimBLE backend — the per-characteristic register
lines are NimBLE-specific; Bluedroid prints the four NUS handles in a lines are NimBLE-specific; Bluedroid prints the four UART-service handles in a
single line, see below): single line, see below):
``` ```
@@ -174,7 +183,9 @@ I (xxx) ble_uart: advertising started
## Pairing & demo ## Pairing & demo
1. On a phone, install **nRF Connect for Mobile**. 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 2. Scan, tap **Connect** on `BleUart-XXXX`. The phone prompts for a
6-digit code. 6-digit code.
3. The device prints a fresh code in a banner on UART: 3. The device prints a fresh code in a banner on UART:
@@ -187,7 +198,7 @@ I (xxx) ble_uart: advertising started
``` ```
4. Type that code on the phone; pairing completes. The link is now 4. Type that code on the phone; pairing completes. The link is now
AES-CCM-encrypted and the LTK is stored to NVS. AES-CCM-encrypted and the LTK is stored to NVS.
5. Open the *Nordic UART Service*, subscribe to TX (the down-arrow 5. Open the **UART service** (UUID `6e400001-…`), subscribe to TX (the down-arrow
icon), then write any bytes to RX (the up-arrow icon). The device icon), then write any bytes to RX (the up-arrow icon). The device
logs them to UART and **echoes them right back** through TX. logs them to UART and **echoes them right back** through TX.
6. Disconnect and reconnect: no passkey prompt — the bond resumes 6. Disconnect and reconnect: no passkey prompt — the bond resumes
@@ -204,9 +215,26 @@ bytes with no framing assumptions). Send replies with `ble_uart_tx()`.
## Reusing `ble_uart` in your own project ## Reusing `ble_uart` in your own project
Copy `main/ble_uart.h` plus the backend(s) you want — `main/ble_uart_nimble.c` **Recommended (no copy):** register the shared component directory **before**
and/or `main/ble_uart_bluedroid.c` — into your project, add `bt nvs_flash` `project()` so CMake can resolve `REQUIRES ble_uart` from `main/` (same pattern
to your component's `REQUIRES`, then in your `app_main`: as this example's root `CMakeLists.txt`):
```cmake
cmake_minimum_required(VERSION 3.22)
list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../path/to/common/ble_uart")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(my_app)
```
Then in `main/CMakeLists.txt` use `REQUIRES ble_uart nvs_flash` and
`#include "ble_uart.h"`.
Adjust the `EXTRA_COMPONENT_DIRS` path if you vendor `common/ble_uart` elsewhere
(e.g. `${CMAKE_CURRENT_LIST_DIR}/components/ble_uart`).
**Alternative:** copy the whole `examples/bluetooth/common/ble_uart/` directory
into your tree (or only the `.h` / `.c` files into `main/`) and add `bt nvs_flash`
to that component's `REQUIRES`, then in your `app_main`:
```c ```c
nvs_flash_init(); nvs_flash_init();
@@ -1,10 +1,3 @@
# Both backends are listed; each .c file body is wrapped in
# #if CONFIG_BT_NIMBLE_ENABLED / CONFIG_BT_BLUEDROID_ENABLED so only
# the matching backend produces code. This is the standard IDF
# pattern for conditional sources, because sdkconfig isn't loaded
# during the early CMake component-requirement scan.
idf_component_register(SRCS "main.c" idf_component_register(SRCS "main.c"
"ble_uart_nimble.c"
"ble_uart_bluedroid.c"
INCLUDE_DIRS "." INCLUDE_DIRS "."
REQUIRES bt nvs_flash) REQUIRES ble_uart nvs_flash)
@@ -2,12 +2,13 @@
# from the default NimBLE backend to Bluedroid. Use it like: # from the default NimBLE backend to Bluedroid. Use it like:
# #
# idf.py -B build_bd \ # idf.py -B build_bd \
# -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.bluedroid" \ # -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.bluedroid" \
# reconfigure # reconfigure
# idf.py -B build_bd build flash monitor # idf.py -B build_bd build flash monitor
# #
# When this overlay wins, main/CMakeLists.txt links ble_uart_bluedroid.c # When this overlay wins, the ble_uart component compiles
# instead of ble_uart_nimble.c. The public ble_uart.h API is identical # ble_uart_bluedroid.c instead of ble_uart_nimble.c (each backend is
# gated on CONFIG_BT_*_ENABLED). The public ble_uart.h API is identical
# either way. # either way.
CONFIG_BT_ENABLED=y CONFIG_BT_ENABLED=y
@@ -0,0 +1,9 @@
# Both backends are listed; each .c file body is wrapped in
# #if CONFIG_BT_NIMBLE_ENABLED / #if CONFIG_BT_BLUEDROID_ENABLED so only
# the matching backend produces code. This is the standard IDF
# pattern for conditional sources, because sdkconfig isn't loaded
# during the early CMake component-requirement scan.
idf_component_register(SRCS "ble_uart_nimble.c"
"ble_uart_bluedroid.c"
INCLUDE_DIRS "."
REQUIRES bt nvs_flash)
@@ -1,11 +1,13 @@
menu "BLE UART Example" menu "BLE UART library"
config BLE_UART_DEVICE_NAME_PREFIX config BLE_UART_DEVICE_NAME_PREFIX
string "BLE device name prefix" string "BLE device name prefix"
default "BleUart" default "BleUart"
help help
The firmware advertises as `<prefix>-XXXX` where XXXX is Default prefix for examples that advertise as `<prefix>-XXXX`
the last two bytes of the BT MAC in hex. where XXXX is the last two bytes of the BT MAC in hex.
Application code may ignore this and set an explicit name in
ble_uart_config_t.
config BLE_UART_RX_SCRATCH_SIZE config BLE_UART_RX_SCRATCH_SIZE
int "RX scratch buffer size (bytes)" int "RX scratch buffer size (bytes)"
@@ -1,7 +1,20 @@
# BLE UART Porting & API Guide # BLE UART Porting & API Guide
This document lives in **`examples/bluetooth/common/ble_uart/`** next to the
`ble_uart` component sources (`ble_uart.h`, backend `.c` files).
**Reference application:** use the **`examples/bluetooth/ble_uart_service`**
example as the working template. Its root `CMakeLists.txt` appends this
directory to **`EXTRA_COMPONENT_DIRS`** so `main` can `REQUIRES ble_uart`;
`main/main.c` initializes NVS and a MAC-derived GAP name, calls
`ble_uart_install()` / `ble_uart_open()` with the default encrypted UART-over-BLE echo
path, and the tree ships `sdkconfig.defaults` plus the Bluedroid overlay
(`sdkconfig.bluedroid`). Clone or diff that project when adapting to a new
target or host stack.
A complete guide to integrating `ble_uart` into any ESP-IDF project. A complete guide to integrating `ble_uart` into any ESP-IDF project.
**Two or three files plus 5 steps of glue code** are enough to bring an **Either** `EXTRA_COMPONENT_DIRS` pointing at this component **or** a few
copied source files **plus** the glue steps below are enough to bring an
encrypted BLE serial peripheral up in a fresh project — the same encrypted BLE serial peripheral up in a fresh project — the same
`ble_uart.h` API works on top of either NimBLE or Bluedroid; pick the `ble_uart.h` API works on top of either NimBLE or Bluedroid; pick the
host with a Kconfig knob. host with a Kconfig knob.
@@ -18,7 +31,7 @@ flagged inline.
| Capability | Description | | Capability | Description |
| --- | --- | | --- | --- |
| Standard Nordic UART Service GATT (RX/TX) | Interoperates with every generic BLE-serial tool (nRF Connect, Web Bluetooth, custom scripts) | | Widely used BLE UART-over-GATT (RX/TX) | Interoperates with every generic BLE-serial tool (mobile GATT clients, Web Bluetooth, custom scripts) |
| LE Secure Connections + Bonding pairing | Single switch; when enabled, a fresh 6-digit passkey is printed to UART | | LE Secure Connections + Bonding pairing | Single switch; when enabled, a fresh 6-digit passkey is printed to UART |
| Auto-reconnect | After a bonded central disconnects, advertising restarts immediately and the LTK is reused — no passkey prompt | | Auto-reconnect | After a bonded central disconnects, advertising restarts immediately and the LTK is reused — no passkey prompt |
| Raw byte pass-through | RX is delivered via a callback; TX is exposed as `ble_uart_tx` | | Raw byte pass-through | RX is delivered via a callback; TX is exposed as `ble_uart_tx` |
@@ -44,26 +57,45 @@ is entirely up to you**.
## 3. File Inventory ## 3. File Inventory
Files to copy into the target project — pick the backend you want and Canonical sources live under **`$IDF_PATH/examples/bluetooth/common/ble_uart/`**
copy that pair plus the public header: (component name `ble_uart`): `ble_uart.h`, `ble_uart_nimble.c`,
`ble_uart_bluedroid.c`, `CMakeLists.txt`, and `Kconfig` (prefix + RX scratch;
`menuconfig → Component configuration → BLE UART library`). When reusing
outside this tree, copy the whole `common/ble_uart/` directory or at least
merge `Kconfig` into your component so the same `CONFIG_BLE_UART_*` symbols
exist.
**Option A — depend on the in-tree component (no copy):** add the component
directory to **`EXTRA_COMPONENT_DIRS` in the project root `CMakeLists.txt`
before `include($ENV{IDF_PATH}/tools/cmake/project.cmake)` / `project()`**,
then use `REQUIRES ble_uart` from `main/CMakeLists.txt` (see
`examples/bluetooth/ble_uart_service/CMakeLists.txt`). This ensures the
`ble_uart` target exists when CMake expands `main`'s requirements.
Kconfig options appear under
`menuconfig → Component configuration → BLE UART library`.
> A `main/idf_component.yml` path dependency alone is **not** sufficient if
> `main/CMakeLists.txt` lists `REQUIRES ble_uart`: the early requirement scan
> runs before the component manager injects that dependency, so CMake fails
> with *unknown component `ble_uart`*. Prefer `EXTRA_COMPONENT_DIRS` (as in the
> reference example) or copy the sources into a normal project component.
**Option B — copy into your project:** pick the backend you want and
copy that pair plus the public header (or copy both backends; each `.c`
gates on its Kconfig symbol):
``` ```
your_project/main/ your_project/main/
├── ble_uart.h ← copy this (stack-agnostic public API, ~260 lines) ├── ble_uart.h ← copy from .../common/ble_uart/
├── ble_uart_nimble.c ← if you'll set CONFIG_BT_NIMBLE_ENABLED=y (~670 lines) ├── ble_uart_nimble.c ← if you'll set CONFIG_BT_NIMBLE_ENABLED=y
└── ble_uart_bluedroid.c ← if you'll set CONFIG_BT_BLUEDROID_ENABLED=y (~900 lines) └── ble_uart_bluedroid.c ← if you'll set CONFIG_BT_BLUEDROID_ENABLED=y
``` ```
You can also copy *both* `ble_uart_nimble.c` and `ble_uart_bluedroid.c` Optional: copy `Kconfig` from `common/ble_uart/` into your component (or merge
unchanged — each `.c` file gates its body on the matching Kconfig its symbols into your own `Kconfig`) if you want `BLE_UART_*` in `menuconfig`;
symbol, so the inactive one compiles to nothing. This is what the otherwise hard-code the device name and rely on the 1024-byte fallback for RX
example itself does, and it lets you flip stacks without changing the scratch.
source list.
Optional: `Kconfig.projbuild` defines `BLE_UART_DEVICE_NAME_PREFIX`
and `BLE_UART_RX_SCRATCH_SIZE`. Copy it too if you want either to be
tunable from `menuconfig`; otherwise hard-code the name in your
source and rely on the 1024-byte fallback for RX scratch.
--- ---
@@ -75,11 +107,12 @@ Assume you already have an ESP-IDF project (`my_project/`).
```bash ```bash
cd my_project/main cd my_project/main
BLE_UART_SRC="$IDF_PATH/examples/bluetooth/common/ble_uart"
# Stack-agnostic public header — always. # Stack-agnostic public header — always.
cp /path/to/ble_uart_service/main/ble_uart.h . cp "$BLE_UART_SRC/ble_uart.h" .
# Pick one (or copy both — the inactive one compiles to nothing). # Pick one (or copy both — the inactive one compiles to nothing).
cp /path/to/ble_uart_service/main/ble_uart_nimble.c . cp "$BLE_UART_SRC/ble_uart_nimble.c" .
cp /path/to/ble_uart_service/main/ble_uart_bluedroid.c . cp "$BLE_UART_SRC/ble_uart_bluedroid.c" .
``` ```
### 4.2 Edit `main/CMakeLists.txt` ### 4.2 Edit `main/CMakeLists.txt`
@@ -116,6 +149,22 @@ the central must support it.
**Bluedroid backend (drop-in alternative):** **Bluedroid backend (drop-in alternative):**
Use the **`examples/bluetooth/ble_uart_service/sdkconfig.bluedroid`** file as
the authoritative Kconfig overlay: it enables the host stack, SMP, GATTS
(service-table API), and the BLE-only advertising knobs that
`ble_uart_bluedroid.c` expects. Either merge those lines into your own
`sdkconfig.defaults`, or pass them as a second defaults file:
```bash
idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.bluedroid" reconfigure
```
(Paths are relative to the example project root; copy `sdkconfig.bluedroid`
into your tree if you are not starting from `ble_uart_service`.)
A minimal inline sketch (may drift from IDF defaults — **diff against
`sdkconfig.bluedroid` after each IDF upgrade**):
```ini ```ini
CONFIG_BT_ENABLED=y CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=n CONFIG_BT_NIMBLE_ENABLED=n
@@ -124,8 +173,8 @@ CONFIG_BT_BLUEDROID_ENABLED=y
# LE Secure Connections + bonding (Bluedroid persists LTKs by default) # LE Secure Connections + bonding (Bluedroid persists LTKs by default)
CONFIG_BT_BLE_SMP_ENABLE=y CONFIG_BT_BLE_SMP_ENABLE=y
# Optional: bigger MTU # Optional: bigger MTU (when supported by your IDF target / menuconfig)
CONFIG_BT_GATT_MAX_MTU_SIZE=512 # CONFIG_BT_GATT_MAX_MTU_SIZE=512
# BLE-only feature set (saves flash on classic-BT-capable parts) # BLE-only feature set (saves flash on classic-BT-capable parts)
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
@@ -201,7 +250,7 @@ I (xxx) ble_uart: registered service svc_handle=40 rx=42 tx=44 cccd=45
I (xxx) ble_uart: advertising started I (xxx) ble_uart: advertising started
``` ```
nRF Connect on a phone discovers `MyDevice`; connect, enter the A phone GATT client app discovers `MyDevice`; connect, enter the
passkey, subscribe to TX, write to RX, and you will see the echo come passkey, subscribe to TX, write to RX, and you will see the echo come
back. back.
@@ -337,7 +386,7 @@ returns `ENOTCONN` to tell you.
extern const ble_uart_uuid128_t ble_uart_service_uuid; extern const ble_uart_uuid128_t ble_uart_service_uuid;
``` ```
Always `6e400001-b5a3-f393-e0a9-e50e24dcca9e` (the NUS standard). It is Always `6e400001-b5a3-f393-e0a9-e50e24dcca9e` (the de-facto BLE UART service UUID). It is
already inserted into the scan response, so the **application normally already inserted into the scan response, so the **application normally
does not touch it**. You only need it if you take over advertising does not touch it**. You only need it if you take over advertising
yourself (see 6.3). yourself (see 6.3).
@@ -403,7 +452,7 @@ Effect:
- GATT characteristics drop the `_ENC | _AUTHEN` flags. - GATT characteristics drop the `_ENC | _AUTHEN` flags.
- Any central can read/write — no pairing required. - Any central can read/write — no pairing required.
- No passkey prompt. - No passkey prompt.
- Data is sniffable by any nearby nRF dongle. - Data is sniffable by any nearby BLE sniffer or compromised radio in range.
**Do not ship this in production firmware.** **Do not ship this in production firmware.**
@@ -439,7 +488,10 @@ ble_uart_open();
### 6.4 Configuring the device-name prefix via Kconfig ### 6.4 Configuring the device-name prefix via Kconfig
Copy `Kconfig.projbuild` into `main/`, then: If you use the shared `ble_uart` component, options are already in
`menuconfig → Component configuration → BLE UART library`. If you copied only
the `.c` / `.h` files into `main/`, copy `Kconfig` from `common/ble_uart/` as
well (or merge its symbols into your own `Kconfig.projbuild`), then:
```c ```c
char name[24]; char name[24];
@@ -453,8 +505,8 @@ ble_uart_install(&(ble_uart_config_t){
}); });
``` ```
Edit the default through `menuconfig → BLE UART Example → BLE device Edit the default through `menuconfig → Component configuration → BLE UART
name prefix`. library → BLE device name prefix`.
### 6.5 Pushing data proactively ### 6.5 Pushing data proactively
@@ -538,16 +590,17 @@ If you **build directly on top of this example**:
| --- | --- | | --- | --- |
| `main.c` echo template | Replace with your own `on_rx` body | | `main.c` echo template | Replace with your own `on_rx` body |
| `sdkconfig.defaults` | Reuse as-is | | `sdkconfig.defaults` | Reuse as-is |
| `Kconfig.projbuild` | Reuse as-is | | `sdkconfig.bluedroid` | Only if you switch to Bluedroid host — reuse as-is (see §4.3); omit for default NimBLE |
| Root `CMakeLists.txt` (`EXTRA_COMPONENT_DIRS` → `../common/ble_uart`) | Reuse as-is (or follow §3 option B) |
| `CMakeLists.txt` (root + main) | Reuse as-is | | `CMakeLists.txt` (root + main) | Reuse as-is |
If you **start from an empty project**: If you **start from an empty project**:
| What you need to do | Source | | What you need to do | Source |
| --- | --- | | --- | --- |
| Copy `ble_uart.h` + at least one of `ble_uart_nimble.c` / `ble_uart_bluedroid.c` into `main/` | This example | | Add `EXTRA_COMPONENT_DIRS` for `examples/bluetooth/common/ble_uart` in root `CMakeLists.txt`, **or** copy `ble_uart.h` + at least one of `ble_uart_nimble.c` / `ble_uart_bluedroid.c` into `main/` | §3 of this guide |
| Copy the key lines of `sdkconfig.defaults` | §4.3 of this guide | | Copy the key lines of `sdkconfig.defaults` | §4.3 of this guide |
| Add SRC + REQUIRES to `main/CMakeLists.txt` | §4.2 of this guide | | Add `REQUIRES ble_uart nvs_flash` (after `EXTRA_COMPONENT_DIRS`) **or** SRC + `REQUIRES bt nvs_flash` (copied sources) to `main/CMakeLists.txt` | §4.2 of this guide |
| Write `install` + `open` in `app_main` | §4.4 of this guide | | Write `install` + `open` in `app_main` | §4.4 of this guide |
--- ---
@@ -591,8 +644,21 @@ extern const ble_uart_uuid128_t ble_uart_service_uuid;
## 12. Minimal Project Template (ready to flash) ## 12. Minimal Project Template (ready to flash)
A complete flashable project takes 7 files (the inactive backend `.c` A complete flashable project takes a handful of files. The inactive
compiles to nothing, so it costs you nothing to ship both): backend `.c` compiles to nothing if you ship both.
**Using the shared component (fewer copies):**
```
my_ble_uart_project/
├── CMakeLists.txt ← EXTRA_COMPONENT_DIRS → …/common/ble_uart (before project())
├── sdkconfig.defaults
└── main/
├── CMakeLists.txt ← REQUIRES ble_uart nvs_flash; SRCS main.c only
└── main.c
```
**Copying sources into `main/` (classic layout):**
``` ```
my_ble_uart_project/ my_ble_uart_project/
@@ -600,20 +666,28 @@ my_ble_uart_project/
├── sdkconfig.defaults ├── sdkconfig.defaults
└── main/ └── main/
├── CMakeLists.txt ├── CMakeLists.txt
├── ble_uart.h ← copied from this example ├── ble_uart.h ← from $IDF_PATH/examples/bluetooth/common/ble_uart/
├── ble_uart_nimble.c ← copied from this example ├── ble_uart_nimble.c
├── ble_uart_bluedroid.c ← copied from this example (optional) ├── ble_uart_bluedroid.c ← optional second backend
└── main.c └── main.c
``` ```
**Root `CMakeLists.txt`**: **Root `CMakeLists.txt`** (shared `ble_uart` via `EXTRA_COMPONENT_DIRS`):
```cmake ```cmake
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
list(APPEND EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../path/to/common/ble_uart")
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(my_ble_uart) project(my_ble_uart)
``` ```
**`main/CMakeLists.txt`**: **`main/CMakeLists.txt`** (shared component — no `.c` copies in `main/`):
```cmake
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES ble_uart nvs_flash)
```
**`main/CMakeLists.txt`** (classic copy layout — both backends in `main/`):
```cmake ```cmake
idf_component_register(SRCS "main.c" idf_component_register(SRCS "main.c"
"ble_uart_nimble.c" "ble_uart_nimble.c"
@@ -633,6 +707,11 @@ CONFIG_BT_NIMBLE_NVS_PERSIST=y
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512 CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=512
``` ```
**Bluedroid host instead of NimBLE:** copy
`examples/bluetooth/ble_uart_service/sdkconfig.bluedroid` next to your
`sdkconfig.defaults` and pass
`-D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.bluedroid"` (see §4.3).
**`main/main.c`** — copy the §4.4 template verbatim. **`main/main.c`** — copy the §4.4 template verbatim.
Flash: Flash:
@@ -5,8 +5,8 @@
* *
* BLE UART — turnkey serial-over-BLE peripheral. * BLE UART — turnkey serial-over-BLE peripheral.
* *
* Implements the de-facto Nordic UART Service (NUS) GATT layout * Implements the de-facto BLE UART-over-GATT layout (RX write, TX notify;
* (RX write, TX notify) on top of either NimBLE or Bluedroid; the * fixed 128-bit UUIDs below) on top of either NimBLE or Bluedroid; the
* backend is picked at compile time via CONFIG_BT_NIMBLE_ENABLED / * backend is picked at compile time via CONFIG_BT_NIMBLE_ENABLED /
* CONFIG_BT_BLUEDROID_ENABLED. * CONFIG_BT_BLUEDROID_ENABLED.
* *
@@ -21,13 +21,13 @@
* Run-forever apps only need install + open. close / uninstall is * Run-forever apps only need install + open. close / uninstall is
* for apps that need to power BLE off at runtime. * for apps that need to power BLE off at runtime.
* *
* GATT layout (UUIDs fixed by the NUS spec): * GATT layout (UUIDs are the widely used fixed 128-bit values):
* *
* Service: 6e400001-b5a3-f393-e0a9-e50e24dcca9e * Service: 6e400001-b5a3-f393-e0a9-e50e24dcca9e
* RX : 6e400002-b5a3-f393-e0a9-e50e24dcca9e write * RX : 6e400002-b5a3-f393-e0a9-e50e24dcca9e write
* TX : 6e400003-b5a3-f393-e0a9-e50e24dcca9e notify * TX : 6e400003-b5a3-f393-e0a9-e50e24dcca9e notify
* *
* See PORTING.md for the integration guide. * See PORTING.md in this component directory for the integration guide.
*/ */
#pragma once #pragma once
@@ -89,7 +89,7 @@ typedef struct {
/* ----- Lifecycle ------------------------------------------------------ */ /* ----- Lifecycle ------------------------------------------------------ */
/** Bring up host stack + Security Manager + SIG services + NUS GATT /** Bring up host stack + Security Manager + SIG services + BLE UART GATT
* service. Caller must have already called nvs_flash_init(). * service. Caller must have already called nvs_flash_init().
* cfg->device_name is copied; doesn't need to outlive the call. * cfg->device_name is copied; doesn't need to outlive the call.
* Single-shot until ble_uart_uninstall(); a second call returns * Single-shot until ble_uart_uninstall(); a second call returns
@@ -145,7 +145,7 @@ bool ble_uart_is_subscribed(void);
/* ----- Service UUID -------------------------------------------------- */ /* ----- Service UUID -------------------------------------------------- */
/** The NUS service UUID, exposed for custom advertising payloads. /** The BLE UART service UUID, exposed for custom advertising payloads.
* The two characteristic UUIDs are private to the backend. */ * The two characteristic UUIDs are private to the backend. */
extern const ble_uart_uuid128_t ble_uart_service_uuid; extern const ble_uart_uuid128_t ble_uart_service_uuid;
@@ -204,7 +204,7 @@ static void build_attr_table(bool encrypted)
}, },
}; };
/* [TX value] — NUS spec is notify-only, so the prop above doesn't /* [TX value] — TX characteristic is notify-only, so the prop above doesn't
* advertise READ; perm only matters if a client tries READ anyway. */ * advertise READ; perm only matters if a client tries READ anyway. */
s_nus_db[NUS_IDX_TX_VAL] = (esp_gatts_attr_db_t){ s_nus_db[NUS_IDX_TX_VAL] = (esp_gatts_attr_db_t){
.attr_control = {ESP_GATT_AUTO_RSP}, .attr_control = {ESP_GATT_AUTO_RSP},
@@ -251,7 +251,7 @@ static esp_ble_adv_data_t s_adv_data = {
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
}; };
/* Scan response = NUS UUID. Splitting it off the primary payload /* Scan response = 128-bit UART service UUID. Splitting it off the primary payload
* leaves room for name + tx_pwr in the 31-byte primary. */ * leaves room for name + tx_pwr in the 31-byte primary. */
static esp_ble_adv_data_t s_scan_rsp_data = { static esp_ble_adv_data_t s_scan_rsp_data = {
.set_scan_rsp = true, .set_scan_rsp = true,
@@ -64,7 +64,7 @@ extern void ble_store_config_init(void);
/* ===== UUIDs =========================================================== */ /* ===== UUIDs =========================================================== */
/* NUS UUIDs in little-endian byte order. */ /* BLE UART profile UUIDs in little-endian byte order. */
#define NUS_SVC_BYTES 0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, \ #define NUS_SVC_BYTES 0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, \
0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e 0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e
#define NUS_RX_BYTES 0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, \ #define NUS_RX_BYTES 0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, \
@@ -80,8 +80,9 @@ static const ble_uuid128_t s_chr_tx_uuid = BLE_UUID128_INIT(NUS_TX_BYTES);
/* ===== State =========================================================== */ /* ===== State =========================================================== */
/* RX scratch capacity. Tunable via menuconfig; fall back to 1024 if /* RX scratch capacity. Tunable via menuconfig (Component config → BLE UART
* Kconfig.projbuild isn't carried along when reusing this file. */ * library); fall
* back to 1024 if CONFIG_BLE_UART_RX_SCRATCH_SIZE is absent. */
#ifndef CONFIG_BLE_UART_RX_SCRATCH_SIZE #ifndef CONFIG_BLE_UART_RX_SCRATCH_SIZE
#define CONFIG_BLE_UART_RX_SCRATCH_SIZE 1024 #define CONFIG_BLE_UART_RX_SCRATCH_SIZE 1024
#endif #endif
@@ -106,7 +107,7 @@ static uint8_t s_own_addr_type;
static int gap_event(struct ble_gap_event *event, void *arg); static int gap_event(struct ble_gap_event *event, void *arg);
static int start_advertising(void); static int start_advertising(void);
/* ===== GATT (NUS) ====================================================== */ /* ===== GATT (BLE UART service) ========================================= */
static int chr_access(uint16_t conn_handle, uint16_t attr_handle, static int chr_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg) struct ble_gatt_access_ctxt *ctxt, void *arg)
@@ -253,7 +254,7 @@ static int start_advertising(void)
{ {
/* 31-byte primary adv can't hold flags + tx_pwr + name + 128-bit /* 31-byte primary adv can't hold flags + tx_pwr + name + 128-bit
* UUID together, so split: primary = flags+tx_pwr+name, * UUID together, so split: primary = flags+tx_pwr+name,
* scan rsp = NUS UUID. */ * scan rsp = 128-bit service UUID. */
const char *name = s_dev_name; const char *name = s_dev_name;
size_t name_len = strlen(name); size_t name_len = strlen(name);
@@ -262,7 +263,7 @@ static int start_advertising(void)
.tx_pwr_lvl_is_present = 1, .tx_pwr_lvl_is_present = 1,
.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO, .tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO,
/* If no name was set, advertise without one (NimBLE accepts /* If no name was set, advertise without one (NimBLE accepts
* NULL+0); the NUS UUID in scan rsp still identifies us. */ * NULL+0); the service UUID in scan rsp still identifies us. */
.name = name_len > 0 ? (uint8_t *)name : NULL, .name = name_len > 0 ? (uint8_t *)name : NULL,
.name_len = name_len, .name_len = name_len,
.name_is_complete = name_len > 0 ? 1 : 0, .name_is_complete = name_len > 0 ? 1 : 0,
+3 -3
View File
@@ -62,7 +62,7 @@ Open an interactive BLE UART Console:
python main.py console DEVICE_ID python main.py console DEVICE_ID
``` ```
For Console options such as line endings, hex mode, and write-with-response, see [Quick-Start-BLE-UART-Console.md](docs/Quick-Start-BLE-UART-Console.md). If you need firmware to test against, use the [BLE UART Service example](../../../examples/bluetooth/ble_uart_service) as an Echo Server: it advertises the default Nordic UART Service profile and echoes RX writes back through TX notifications. For Console options such as line endings, hex mode, and write-with-response, see [Quick-Start-BLE-UART-Console.md](docs/Quick-Start-BLE-UART-Console.md). If you need firmware to test against, use the [BLE UART Service example](../../../examples/bluetooth/ble_uart_service) as an Echo Server: it advertises the default BLE UART-over-GATT UUIDs and echoes RX writes back through TX notifications.
Run the BLE UART Daemon: Run the BLE UART Daemon:
@@ -194,7 +194,7 @@ Main responsibilities:
- Connect and disconnect with a BLE UART GATT profile. - Connect and disconnect with a BLE UART GATT profile.
- Subscribe to device-to-host notifications. - Subscribe to device-to-host notifications.
- Send host-to-device data as `str`, `bytes`, or `bytearray`. - Send host-to-device data as `str`, `bytes`, or `bytearray`.
- Support a default NUS profile and user-defined BLE UART profiles. - Support a default BLE-UART UUID profile and user-defined BLE UART profiles.
Important APIs: Important APIs:
@@ -262,7 +262,7 @@ Use Core when your business logic lives in Python. Use Console when you only nee
## Profile compatibility ## Profile compatibility
The default profile is compatible with the Nordic UART Service (NUS): The default profile uses the widely deployed BLE UART-over-GATT UUID set:
- Service UUID: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` - Service UUID: `6E400001-B5A3-F393-E0A9-E50E24DCCA9E`
- RX characteristic UUID, host to device: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` - RX characteristic UUID, host to device: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`
@@ -42,7 +42,7 @@ flowchart LR
OC -->|permission.asked| Plugin OC -->|permission.asked| Plugin
Plugin -->|POST /notify| Daemon[ble_uart_bridge daemon] Plugin -->|POST /notify| Daemon[ble_uart_bridge daemon]
Plugin -->|POST /request| Daemon Plugin -->|POST /request| Daemon
Daemon -->|BLE NUS JSONL| Device[BLE device UI] Daemon -->|BLE UART JSONL| Device[BLE device UI]
Device -->|once / reject| Daemon Device -->|once / reject| Daemon
Daemon -->|HTTP response| Plugin Daemon -->|HTTP response| Plugin
Plugin -->|SDK permission reply| OC Plugin -->|SDK permission reply| OC
@@ -54,7 +54,7 @@ flowchart LR
The intended firmware companion is an `esp-vocat` example for the MiaoBan The intended firmware companion is an `esp-vocat` example for the MiaoBan
(喵伴) device, planned for the `esp-iot-solution` repository. Until that (喵伴) device, planned for the `esp-iot-solution` repository. Until that
example is available, use any device that implements Nordic UART Service and example is available, use any device that implements the default BLE UART-over-GATT UUIDs and
the JSONL request/response envelope described in the JSONL request/response envelope described in
[Firmware protocol reference](#firmware-protocol-reference). [Firmware protocol reference](#firmware-protocol-reference).
@@ -235,7 +235,7 @@ permission requests can be approved once with `once` or denied with `reject`.
- The BLE daemon endpoint is configured by `OPENCODE_BLE_DAEMON_URL`, defaulting - The BLE daemon endpoint is configured by `OPENCODE_BLE_DAEMON_URL`, defaulting
to `http://127.0.0.1:8888`. to `http://127.0.0.1:8888`.
- The BLE daemon supports both `POST /notify` and `POST /request`. - The BLE daemon supports both `POST /notify` and `POST /request`.
- The BLE device implements Nordic UART Service. - The BLE device implements the default BLE UART-over-GATT UUID layout.
- The BLE device understands JSON messages described in - The BLE device understands JSON messages described in
[Firmware protocol reference](#firmware-protocol-reference). [Firmware protocol reference](#firmware-protocol-reference).
- Permission decisions from the current single-key device are: `once`, `reject`. - Permission decisions from the current single-key device are: `once`, `reject`.
+1 -1
View File
@@ -141,7 +141,7 @@ await bridge.send(b"\x01\x02", with_response=True)
## Use a custom BLE UART profile ## Use a custom BLE UART profile
The default profile uses Nordic UART Service UUIDs. For custom firmware, create a `BLEUARTProfile`: The default profile uses the de-facto BLE UART-over-GATT UUIDs. For custom firmware, create a `BLEUARTProfile`:
```python ```python
from src.core import BLEUARTBridge from src.core import BLEUARTBridge
@@ -8,9 +8,9 @@ BLE UART Bridge works with BLE GATT profiles that provide a UART-like data path:
- one characteristic that the host writes to - one characteristic that the host writes to
- one characteristic that the device uses to notify data back to the host - one characteristic that the device uses to notify data back to the host
The default profile is compatible with the Nordic UART Service (NUS), but NUS is not the only possible BLE UART-style profile. The default profile matches the widely used BLE UART-over-GATT UUID set (service `6E400001-…`, RX/TX characteristics), but that layout is not the only possible BLE UART-style profile.
## Default NUS-compatible profile ## Default BLE-UART-compatible profile
The built-in default profile uses these UUIDs: The built-in default profile uses these UUIDs:
@@ -20,7 +20,7 @@ The built-in default profile uses these UUIDs:
| RX, host to device | `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` | | RX, host to device | `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` |
| TX, device to host | `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` | | TX, device to host | `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` |
Use the default profile when the device advertises a NUS-compatible service. Use the default profile when the device advertises a service using those UUIDs.
## ESP-IDF BLE SPP examples ## ESP-IDF BLE SPP examples
@@ -31,7 +31,7 @@ ESP-IDF includes BLE SPP examples that implement Espressif BLE UART-like vendor-
- `examples/bluetooth/bluedroid/ble/ble_spp_server` - `examples/bluetooth/bluedroid/ble/ble_spp_server`
- `examples/bluetooth/bluedroid/ble/ble_spp_client` - `examples/bluetooth/bluedroid/ble/ble_spp_client`
BLE SPP over BLE is not a Bluetooth SIG standard profile. It is a vendor-specific GATT design that emulates a serial link, similar in purpose to NUS. BLE SPP over BLE is not a Bluetooth SIG standard profile. It is a vendor-specific GATT design that emulates a serial link, similar in purpose to the default BLE UART layout above.
ESP-IDF BLE SPP examples may define more characteristics than BLE UART Bridge needs, such as data, command, and status characteristics. To use BLE UART Bridge with such a profile, map only the UART-like data path into `BLEUARTProfile`. ESP-IDF BLE SPP examples may define more characteristics than BLE UART Bridge needs, such as data, command, and status characteristics. To use BLE UART Bridge with such a profile, map only the UART-like data path into `BLEUARTProfile`.
@@ -21,7 +21,7 @@ The Console is useful when you want to type data into a BLE UART device and insp
On Windows, run `export.bat` or `export.ps1` from the ESP-IDF root directory before installing `requirements.txt`. If you use your own Python virtual environment instead, activate it before installing `requirements.txt`. On Windows, run `export.bat` or `export.ps1` from the ESP-IDF root directory before installing `requirements.txt`. If you use your own Python virtual environment instead, activate it before installing `requirements.txt`.
3. A BLE device advertising the BLE UART service. By default the tool scans for Nordic UART Service UUIDs. For a known-compatible test target, build and flash the [BLE UART Service example](../../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server by echoing RX writes back through TX notifications. 3. A BLE device advertising the BLE UART service. By default the tool scans for the de-facto BLE UART-over-GATT UUIDs (`6E400001-…` / `…02` / `…03`). For a known-compatible test target, build and flash the [BLE UART Service example](../../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server by echoing RX writes back through TX notifications.
## Find a device ## Find a device