Merge branch 'feat/share_ble_uart_component_and_refresh_docs' into 'master'

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

See merge request espressif/esp-idf!48321
This commit is contained in:
Island
2026-05-12 15:15:32 +08:00
15 changed files with 217 additions and 100 deletions

View File

@@ -2,6 +2,10 @@
# CMakeLists in this exact order for cmake to work correctly.
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")
cmake_path(CONVERT "$ENV{IDF_PATH}" TO_CMAKE_PATH_LIST ESP_IDF_PATH)
list(APPEND sdkconfig_defaults "${ESP_IDF_PATH}/components/mbedtls/config/mbedtls_preset_bt.conf")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)

View File

@@ -4,9 +4,10 @@
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | --------- |
A turnkey serial-over-BLE peripheral that implements the de-facto
**Nordic UART Service** GATT layout (RX write, TX notify), so any
standard BLE-serial central (nRF Connect, Web Bluetooth examples, your
own iOS / Android / Linux / Python scripts) can talk to it unchanged.
**BLE UART-over-GATT** layout (RX write, TX notify; fixed 128-bit UUIDs
below), so any widely used BLE-serial central (mobile GATT client apps,
Web Bluetooth examples, your own iOS / Android / Linux / Python scripts)
can talk to it unchanged.
The example ships with **two interchangeable backends** — NimBLE and
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`:
```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
```
@@ -53,12 +54,14 @@ The `_ENC | _AUTHEN` flags are turned on only when `cfg.encrypted = true`
| File | Lines | Role |
| --- | ---: | --- |
| `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. |
| `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`. |
| `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`. |
| `main/Kconfig.projbuild` | ~40 | Device-name prefix + RX scratch buffer size knobs. |
| `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 → 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.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
@@ -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
(`ble_uart_nimble.c`) and once on top of Bluedroid
(`ble_uart_bluedroid.c`). `main/CMakeLists.txt` registers both files;
each guards its body with `#if CONFIG_BT_NIMBLE_ENABLED` / `#if
CONFIG_BT_BLUEDROID_ENABLED`, so exactly one becomes live at compile
time.
(`ble_uart_bluedroid.c`). The shared `ble_uart` component's
`CMakeLists.txt` registers both files; each guards its body with
`#if CONFIG_BT_NIMBLE_ENABLED` / `#if CONFIG_BT_BLUEDROID_ENABLED`, so
exactly one becomes live at compile time.
Two ways to switch:
@@ -103,9 +106,9 @@ Two ways to switch:
idf.py menuconfig
# 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 \
-D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.bluedroid" \
-D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.bluedroid" \
reconfigure
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
idf.py set-target esp32c3 # or esp32, esp32s3, esp32c6, esp32h2 ...
idf.py menuconfig # optional
# Component config -> BLE UART Example
# Component configuration -> BLE UART library
# - 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`:
```ini
@@ -152,7 +161,7 @@ idf.py build flash monitor
```
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):
```
@@ -174,7 +183,9 @@ I (xxx) ble_uart: advertising started
## 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
6-digit code.
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
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
logs them to UART and **echoes them right back** through TX.
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
Copy `main/ble_uart.h` plus the backend(s) you want — `main/ble_uart_nimble.c`
and/or `main/ble_uart_bluedroid.c` — into your project, add `bt nvs_flash`
to your component's `REQUIRES`, then in your `app_main`:
**Recommended (no copy):** register the shared component directory **before**
`project()` so CMake can resolve `REQUIRES ble_uart` from `main/` (same pattern
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
nvs_flash_init();

View File

@@ -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"
"ble_uart_nimble.c"
"ble_uart_bluedroid.c"
INCLUDE_DIRS "."
REQUIRES bt nvs_flash)
REQUIRES ble_uart nvs_flash)

View File

@@ -2,12 +2,13 @@
# from the default NimBLE backend to Bluedroid. Use it like:
#
# idf.py -B build_bd \
# -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.bluedroid" \
# -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.bluedroid" \
# reconfigure
# idf.py -B build_bd build flash monitor
#
# When this overlay wins, main/CMakeLists.txt links ble_uart_bluedroid.c
# instead of ble_uart_nimble.c. The public ble_uart.h API is identical
# When this overlay wins, the ble_uart component compiles
# 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.
CONFIG_BT_ENABLED=y

View File

@@ -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)

View File

@@ -1,11 +1,13 @@
menu "BLE UART Example"
menu "BLE UART library"
config BLE_UART_DEVICE_NAME_PREFIX
string "BLE device name prefix"
default "BleUart"
help
The firmware advertises as `<prefix>-XXXX` where XXXX is
the last two bytes of the BT MAC in hex.
Default prefix for examples that advertise as `<prefix>-XXXX`
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
int "RX scratch buffer size (bytes)"

View File

@@ -1,7 +1,20 @@
# 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.
**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
`ble_uart.h` API works on top of either NimBLE or Bluedroid; pick the
host with a Kconfig knob.
@@ -18,7 +31,7 @@ flagged inline.
| 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 |
| 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` |
@@ -44,26 +57,45 @@ is entirely up to you**.
## 3. File Inventory
Files to copy into the target project — pick the backend you want and
copy that pair plus the public header:
Canonical sources live under **`$IDF_PATH/examples/bluetooth/common/ble_uart/`**
(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/
├── ble_uart.h ← copy this (stack-agnostic public API, ~260 lines)
├── ble_uart_nimble.c ← if you'll set CONFIG_BT_NIMBLE_ENABLED=y (~670 lines)
└── ble_uart_bluedroid.c ← if you'll set CONFIG_BT_BLUEDROID_ENABLED=y (~900 lines)
├── ble_uart.h ← copy from .../common/ble_uart/
├── 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
```
You can also copy *both* `ble_uart_nimble.c` and `ble_uart_bluedroid.c`
unchanged — each `.c` file gates its body on the matching Kconfig
symbol, so the inactive one compiles to nothing. This is what the
example itself does, and it lets you flip stacks without changing the
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.
Optional: copy `Kconfig` from `common/ble_uart/` into your component (or merge
its symbols into your own `Kconfig`) if you want `BLE_UART_*` in `menuconfig`;
otherwise hard-code the device name 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
cd my_project/main
BLE_UART_SRC="$IDF_PATH/examples/bluetooth/common/ble_uart"
# 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).
cp /path/to/ble_uart_service/main/ble_uart_nimble.c .
cp /path/to/ble_uart_service/main/ble_uart_bluedroid.c .
cp "$BLE_UART_SRC/ble_uart_nimble.c" .
cp "$BLE_UART_SRC/ble_uart_bluedroid.c" .
```
### 4.2 Edit `main/CMakeLists.txt`
@@ -116,6 +149,22 @@ the central must support it.
**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
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=n
@@ -124,8 +173,8 @@ CONFIG_BT_BLUEDROID_ENABLED=y
# LE Secure Connections + bonding (Bluedroid persists LTKs by default)
CONFIG_BT_BLE_SMP_ENABLE=y
# Optional: bigger MTU
CONFIG_BT_GATT_MAX_MTU_SIZE=512
# Optional: bigger MTU (when supported by your IDF target / menuconfig)
# CONFIG_BT_GATT_MAX_MTU_SIZE=512
# BLE-only feature set (saves flash on classic-BT-capable parts)
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
```
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
back.
@@ -337,7 +386,7 @@ returns `ENOTCONN` to tell you.
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
does not touch it**. You only need it if you take over advertising
yourself (see 6.3).
@@ -403,7 +452,7 @@ Effect:
- GATT characteristics drop the `_ENC | _AUTHEN` flags.
- Any central can read/write — no pairing required.
- 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.**
@@ -439,7 +488,10 @@ ble_uart_open();
### 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
char name[24];
@@ -453,8 +505,8 @@ ble_uart_install(&(ble_uart_config_t){
});
```
Edit the default through `menuconfig → BLE UART Example → BLE device
name prefix`.
Edit the default through `menuconfig → Component configuration → BLE UART
library → BLE device name prefix`.
### 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 |
| `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 |
If you **start from an empty project**:
| 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 |
| 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 |
---
@@ -591,8 +644,21 @@ extern const ble_uart_uuid128_t ble_uart_service_uuid;
## 12. Minimal Project Template (ready to flash)
A complete flashable project takes 7 files (the inactive backend `.c`
compiles to nothing, so it costs you nothing to ship both):
A complete flashable project takes a handful of files. The inactive
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/
@@ -600,20 +666,28 @@ my_ble_uart_project/
├── sdkconfig.defaults
└── main/
├── CMakeLists.txt
├── ble_uart.h ← copied from this example
├── ble_uart_nimble.c ← copied from this example
├── ble_uart_bluedroid.c ← copied from this example (optional)
├── ble_uart.h ← from $IDF_PATH/examples/bluetooth/common/ble_uart/
├── ble_uart_nimble.c
├── ble_uart_bluedroid.c ← optional second backend
└── main.c
```
**Root `CMakeLists.txt`**:
**Root `CMakeLists.txt`** (shared `ble_uart` via `EXTRA_COMPONENT_DIRS`):
```cmake
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)
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
idf_component_register(SRCS "main.c"
"ble_uart_nimble.c"
@@ -633,6 +707,11 @@ CONFIG_BT_NIMBLE_NVS_PERSIST=y
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.
Flash:

View File

@@ -5,8 +5,8 @@
*
* BLE UART turnkey serial-over-BLE peripheral.
*
* Implements the de-facto Nordic UART Service (NUS) GATT layout
* (RX write, TX notify) on top of either NimBLE or Bluedroid; the
* Implements the de-facto BLE UART-over-GATT layout (RX write, TX notify;
* fixed 128-bit UUIDs below) on top of either NimBLE or Bluedroid; the
* backend is picked at compile time via CONFIG_BT_NIMBLE_ENABLED /
* CONFIG_BT_BLUEDROID_ENABLED.
*
@@ -21,13 +21,13 @@
* Run-forever apps only need install + open. close / uninstall is
* 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
* RX : 6e400002-b5a3-f393-e0a9-e50e24dcca9e write
* 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
@@ -89,7 +89,7 @@ typedef struct {
/* ----- 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().
* cfg->device_name is copied; doesn't need to outlive the call.
* Single-shot until ble_uart_uninstall(); a second call returns
@@ -145,7 +145,7 @@ bool ble_uart_is_subscribed(void);
/* ----- 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. */
extern const ble_uart_uuid128_t ble_uart_service_uuid;

View File

@@ -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. */
s_nus_db[NUS_IDX_TX_VAL] = (esp_gatts_attr_db_t){
.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),
};
/* 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. */
static esp_ble_adv_data_t s_scan_rsp_data = {
.set_scan_rsp = true,

View File

@@ -64,7 +64,7 @@ extern void ble_store_config_init(void);
/* ===== 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, \
0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e
#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 =========================================================== */
/* RX scratch capacity. Tunable via menuconfig; fall back to 1024 if
* Kconfig.projbuild isn't carried along when reusing this file. */
/* RX scratch capacity. Tunable via menuconfig (Component config → BLE UART
* library); fall
* back to 1024 if CONFIG_BLE_UART_RX_SCRATCH_SIZE is absent. */
#ifndef CONFIG_BLE_UART_RX_SCRATCH_SIZE
#define CONFIG_BLE_UART_RX_SCRATCH_SIZE 1024
#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 start_advertising(void);
/* ===== GATT (NUS) ====================================================== */
/* ===== GATT (BLE UART service) ========================================= */
static int chr_access(uint16_t conn_handle, uint16_t attr_handle,
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
* 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;
size_t name_len = strlen(name);
@@ -262,7 +263,7 @@ static int start_advertising(void)
.tx_pwr_lvl_is_present = 1,
.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO,
/* 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_len = name_len,
.name_is_complete = name_len > 0 ? 1 : 0,

View File

@@ -62,7 +62,7 @@ Open an interactive BLE UART Console:
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:
@@ -194,7 +194,7 @@ Main responsibilities:
- Connect and disconnect with a BLE UART GATT profile.
- Subscribe to device-to-host notifications.
- 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:
@@ -262,7 +262,7 @@ Use Core when your business logic lives in Python. Use Console when you only nee
## 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`
- RX characteristic UUID, host to device: `6E400002-B5A3-F393-E0A9-E50E24DCCA9E`

View File

@@ -42,7 +42,7 @@ flowchart LR
OC -->|permission.asked| Plugin
Plugin -->|POST /notify| Daemon[ble_uart_bridge 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
Daemon -->|HTTP response| Plugin
Plugin -->|SDK permission reply| OC
@@ -54,7 +54,7 @@ flowchart LR
The intended firmware companion is an `esp-vocat` example for the MiaoBan
(喵伴) 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
[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
to `http://127.0.0.1:8888`.
- 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
[Firmware protocol reference](#firmware-protocol-reference).
- Permission decisions from the current single-key device are: `once`, `reject`.

View File

@@ -141,7 +141,7 @@ await bridge.send(b"\x01\x02", with_response=True)
## 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
from src.core import BLEUARTBridge

View File

@@ -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 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:
@@ -20,7 +20,7 @@ The built-in default profile uses these UUIDs:
| RX, host to device | `6E400002-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
@@ -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_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`.

View File

@@ -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`.
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