diff --git a/examples/bluetooth/ble_uart_service/README.md b/examples/bluetooth/ble_uart_service/README.md index be4235d9af8..88b2311dbda 100644 --- a/examples/bluetooth/ble_uart_service/README.md +++ b/examples/bluetooth/ble_uart_service/README.md @@ -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(); diff --git a/examples/bluetooth/ble_uart_service/main/CMakeLists.txt b/examples/bluetooth/ble_uart_service/main/CMakeLists.txt index c05578d30a9..653ce563147 100644 --- a/examples/bluetooth/ble_uart_service/main/CMakeLists.txt +++ b/examples/bluetooth/ble_uart_service/main/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/ble_uart_service/main/idf_component.yml b/examples/bluetooth/ble_uart_service/main/idf_component.yml new file mode 100644 index 00000000000..fea2a54b661 --- /dev/null +++ b/examples/bluetooth/ble_uart_service/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + ble_uart: + path: ${IDF_PATH}/examples/bluetooth/common/ble_uart diff --git a/examples/bluetooth/ble_uart_service/sdkconfig.ci.bluedroid b/examples/bluetooth/ble_uart_service/sdkconfig.bluedroid similarity index 81% rename from examples/bluetooth/ble_uart_service/sdkconfig.ci.bluedroid rename to examples/bluetooth/ble_uart_service/sdkconfig.bluedroid index f0604fab87d..521b833b3ff 100644 --- a/examples/bluetooth/ble_uart_service/sdkconfig.ci.bluedroid +++ b/examples/bluetooth/ble_uart_service/sdkconfig.bluedroid @@ -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 diff --git a/examples/bluetooth/common/ble_uart/CMakeLists.txt b/examples/bluetooth/common/ble_uart/CMakeLists.txt new file mode 100644 index 00000000000..e1c873c2d8b --- /dev/null +++ b/examples/bluetooth/common/ble_uart/CMakeLists.txt @@ -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) diff --git a/examples/bluetooth/ble_uart_service/main/Kconfig.projbuild b/examples/bluetooth/common/ble_uart/Kconfig similarity index 75% rename from examples/bluetooth/ble_uart_service/main/Kconfig.projbuild rename to examples/bluetooth/common/ble_uart/Kconfig index 0f3654d8305..433d629fcc9 100644 --- a/examples/bluetooth/ble_uart_service/main/Kconfig.projbuild +++ b/examples/bluetooth/common/ble_uart/Kconfig @@ -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 `-XXXX` where XXXX is - the last two bytes of the BT MAC in hex. + Default prefix for examples that advertise as `-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)" diff --git a/examples/bluetooth/ble_uart_service/PORTING.md b/examples/bluetooth/common/ble_uart/PORTING.md similarity index 75% rename from examples/bluetooth/ble_uart_service/PORTING.md rename to examples/bluetooth/common/ble_uart/PORTING.md index 47e3859c58f..1d3cbe8a9c2 100644 --- a/examples/bluetooth/ble_uart_service/PORTING.md +++ b/examples/bluetooth/common/ble_uart/PORTING.md @@ -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: diff --git a/examples/bluetooth/ble_uart_service/main/ble_uart.h b/examples/bluetooth/common/ble_uart/ble_uart.h similarity index 92% rename from examples/bluetooth/ble_uart_service/main/ble_uart.h rename to examples/bluetooth/common/ble_uart/ble_uart.h index 037b48390d7..6388252e1aa 100644 --- a/examples/bluetooth/ble_uart_service/main/ble_uart.h +++ b/examples/bluetooth/common/ble_uart/ble_uart.h @@ -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; diff --git a/examples/bluetooth/ble_uart_service/main/ble_uart_bluedroid.c b/examples/bluetooth/common/ble_uart/ble_uart_bluedroid.c similarity index 99% rename from examples/bluetooth/ble_uart_service/main/ble_uart_bluedroid.c rename to examples/bluetooth/common/ble_uart/ble_uart_bluedroid.c index 6f93ce057d2..26b41ac585a 100644 --- a/examples/bluetooth/ble_uart_service/main/ble_uart_bluedroid.c +++ b/examples/bluetooth/common/ble_uart/ble_uart_bluedroid.c @@ -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, diff --git a/examples/bluetooth/ble_uart_service/main/ble_uart_nimble.c b/examples/bluetooth/common/ble_uart/ble_uart_nimble.c similarity index 98% rename from examples/bluetooth/ble_uart_service/main/ble_uart_nimble.c rename to examples/bluetooth/common/ble_uart/ble_uart_nimble.c index bce4ab66f93..c59698f09a2 100644 --- a/examples/bluetooth/ble_uart_service/main/ble_uart_nimble.c +++ b/examples/bluetooth/common/ble_uart/ble_uart_nimble.c @@ -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, diff --git a/tools/ble/ble_uart_bridge/README.md b/tools/ble/ble_uart_bridge/README.md index 04af1fb38ce..d4a514b1693 100644 --- a/tools/ble/ble_uart_bridge/README.md +++ b/tools/ble/ble_uart_bridge/README.md @@ -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` diff --git a/tools/ble/ble_uart_bridge/demos/opencode/README.md b/tools/ble/ble_uart_bridge/demos/opencode/README.md index caf758a1b96..92347e448a4 100644 --- a/tools/ble/ble_uart_bridge/demos/opencode/README.md +++ b/tools/ble/ble_uart_bridge/demos/opencode/README.md @@ -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`. diff --git a/tools/ble/ble_uart_bridge/docs/PORTING.md b/tools/ble/ble_uart_bridge/docs/PORTING.md index f3f468c690c..070f77f86c0 100644 --- a/tools/ble/ble_uart_bridge/docs/PORTING.md +++ b/tools/ble/ble_uart_bridge/docs/PORTING.md @@ -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 diff --git a/tools/ble/ble_uart_bridge/docs/Profile-Compatibility.md b/tools/ble/ble_uart_bridge/docs/Profile-Compatibility.md index 799c979e7fe..fc88264786b 100644 --- a/tools/ble/ble_uart_bridge/docs/Profile-Compatibility.md +++ b/tools/ble/ble_uart_bridge/docs/Profile-Compatibility.md @@ -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`. diff --git a/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md b/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md index 0f18279a1b5..5570ff2195d 100644 --- a/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md +++ b/tools/ble/ble_uart_bridge/docs/Quick-Start-BLE-UART-Console.md @@ -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