docs(ble): add ESP-BLE-UART companion guide

Move the OpenCode companion guide into the ble_uart_service example.

Add English and Chinese Markdown guides with image assets.

Keep ESP-BLE-UART naming consistent across the example and bridge tooling.


(cherry picked from commit 926111e721)

Co-authored-by: Zhou Xiao <zhouxiao@espressif.com>
This commit is contained in:
Zhou Xiao
2026-07-10 16:44:44 +08:00
committed by zhiweijian
parent eb40c51701
commit 4bc65ebd82
30 changed files with 1072 additions and 112 deletions
+13 -11
View File
@@ -1,9 +1,11 @@
<!-- SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
# BLE UART Bridge
# ESP-BLE-UART Bridge
BLE UART Bridge is a host-side utility for talking to ESP-IDF applications that expose a BLE UART-style GATT service. It provides a reusable Python transport layer, an interactive console for manual testing, and a daemon mode for simple local IPC request/response workflows.
ESP-BLE-UART Bridge is a host-side utility for talking to ESP-IDF applications that expose a BLE UART-style GATT service. It provides a reusable Python transport layer, an interactive console for manual testing, and a daemon mode for simple local IPC request/response workflows.
> **Naming convention:** Use **ESP-BLE-UART** for Espressif-owned product names (Bridge, Console, Daemon, Echo Server, the `ble_uart` component, and the `ble_uart_service` example). Use **BLE UART** for the generic GATT service convention, transport layer, and compatible third-party devices. This follows the same pattern as ESP-BLE-MESH.
## Table of contents
@@ -26,7 +28,7 @@ BLE UART Bridge is a host-side utility for talking to ESP-IDF applications that
## Quick Start
You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the additional BLE UART Bridge dependencies:
You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the additional ESP-BLE-UART Bridge dependencies:
```bash
cd $IDF_PATH
@@ -56,15 +58,15 @@ Check whether the device can be connected:
python main.py connection-check DEVICE_ID
```
Open an interactive BLE UART Console:
Open an interactive ESP-BLE-UART Console:
```bash
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 BLE UART-over-GATT UUIDs 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 [ESP-BLE-UART 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 ESP-BLE-UART Daemon:
```bash
python main.py daemon DEVICE_ID
@@ -111,7 +113,7 @@ python main.py daemon-notify DATA
### Typical Console workflow
Use Console when you want to manually test a BLE UART device from a terminal UI. For a known-compatible target, build and flash the [BLE UART Service example](../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server for Console smoke tests:
Use Console when you want to manually test a BLE UART device from a terminal UI. For a known-compatible target, build and flash the [ESP-BLE-UART example](../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server for Console smoke tests:
```bash
python main.py list-devices
@@ -194,7 +196,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 BLE-UART UUID profile and user-defined BLE UART profiles.
- Support a default BLE UART UUID profile and user-defined BLE UART profiles.
Important APIs:
@@ -241,10 +243,10 @@ By default, the daemon binds to `127.0.0.1`. Keep it on a loopback address unles
## Demos
The `demos/` directory contains example integrations that build on BLE UART
The `demos/` directory contains example integrations that build on ESP-BLE-UART
Bridge components.
- [BLE UART Bridge Demo - OpenCode Integration](demos/opencode/README.md) shows
- [ESP-BLE-UART Bridge Demo - OpenCode Integration](demos/opencode/README.md) shows
how an OpenCode plugin can forward session status and permission requests to a
BLE device through the daemon. It also includes a firmware-side protocol
reference for devices, such as the planned `esp-vocat` / MiaoBan (喵伴)
@@ -294,6 +296,6 @@ The tool depends on:
- [Quick-Start-BLE-UART-Console.md](docs/Quick-Start-BLE-UART-Console.md)
- [Quick-Start-BLE-UART-Daemon.md](docs/Quick-Start-BLE-UART-Daemon.md)
- [BLE UART Bridge Demo - OpenCode Integration](demos/opencode/README.md)
- [ESP-BLE-UART Bridge Demo - OpenCode Integration](demos/opencode/README.md)
- [Profile-Compatibility.md](docs/Profile-Compatibility.md)
- [PORTING.md](docs/PORTING.md)
@@ -1,4 +1,4 @@
# BLE UART Bridge Demo - OpenCode Integration
# ESP-BLE-UART Bridge Demo - OpenCode Integration
This demo sketches how to bridge OpenCode events to a BLE device through
`tools/ble/ble_uart_bridge`.
@@ -12,7 +12,7 @@ device decisions, and daemon-side protocol handling for their own products.
- [Goal](#goal)
- [Quick Start](#quick-start)
- [How it relates to BLE UART Bridge](#how-it-relates-to-ble-uart-bridge)
- [How it relates to ESP-BLE-UART Bridge](#how-it-relates-to-esp-ble-uart-bridge)
- [Daemon JSONL protocol summary](#daemon-jsonl-protocol-summary)
- [Files](#files)
- [Demo and customization notes](#demo-and-customization-notes)
@@ -40,7 +40,7 @@ Use an OpenCode plugin to:
flowchart LR
OC[OpenCode] -->|session.status| Plugin[OpenCode BLE plugin]
OC -->|permission.asked| Plugin
Plugin -->|POST /notify| Daemon[ble_uart_bridge daemon]
Plugin -->|POST /notify| Daemon[ESP-BLE-UART Daemon]
Plugin -->|POST /request| Daemon
Daemon -->|BLE UART JSONL| Device[BLE device UI]
Device -->|once / reject| Daemon
@@ -52,10 +52,13 @@ flowchart LR
1. Prepare a BLE device firmware example.
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 the default BLE UART-over-GATT UUIDs and
the JSONL request/response envelope described in
The intended firmware companion is the `esp-vocat` example for the MiaoBan
(喵伴) device, available in the
[esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository
at `examples/bluetooth/ble_uart_service`. See the example README for
supported boards, dependency versions, and build instructions. Alternatively,
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).
2. Install the bridge dependencies:
@@ -64,7 +67,7 @@ flowchart LR
python -m pip install -r tools/ble/ble_uart_bridge/requirements.txt
```
3. Start the BLE UART daemon:
3. Start the ESP-BLE-UART Daemon:
```bash
python tools/ble/ble_uart_bridge/main.py list-devices
@@ -152,14 +155,15 @@ flowchart LR
should receive a `permission.request` JSONL message and return `once` or
`reject`.
After the `esp-vocat` example is published in `esp-iot-solution`, this section
should be updated with the exact example path, build/flash commands, and any
MiaoBan-specific button or display behavior.
The `esp-vocat` example is available in the
[esp-iot-solution](https://github.com/espressif/esp-iot-solution) repository at
`examples/bluetooth/ble_uart_service`. See the example README for build/flash
commands, dependency versions, and MiaoBan-specific button and display behavior.
## How it relates to BLE UART Bridge
## How it relates to ESP-BLE-UART Bridge
The OpenCode plugin does not talk to BLE directly. It sends local HTTP requests
to the BLE UART Bridge daemon, and the daemon keeps the BLE connection open for
to the ESP-BLE-UART Daemon, and the Daemon keeps the BLE connection open for
the plugin:
- `POST /notify` sends fire-and-forget events, such as session status updates.
@@ -170,8 +174,8 @@ the plugin:
For the daemon itself, see:
- [BLE UART Bridge README](../../README.md)
- [BLE UART Daemon Quick Start](../../docs/Quick-Start-BLE-UART-Daemon.md)
- [ESP-BLE-UART Bridge README](../../README.md)
- [ESP-BLE-UART Daemon Quick Start](../../docs/Quick-Start-BLE-UART-Daemon.md)
### Daemon JSONL protocol summary
@@ -201,7 +205,7 @@ are documented below in [Firmware protocol reference](#firmware-protocol-referen
## Files
- `src/opencode-ble-uart-bridge.ts` — OpenCode plugin entry point using `/notify` for status and `/request` for permission decisions.
- `src/*.ts` helper modules — typed, commented demo code for payloads, BLE daemon transport, OpenCode replies, and permission queue handling.
- `src/*.ts` helper modules — typed, commented demo code for payloads, ESP-BLE-UART Daemon transport, OpenCode replies, and permission queue handling.
- `opencode.json.example` — example OpenCode config to load the plugin and ask for permissions.
## Demo and customization notes
@@ -224,7 +228,7 @@ permission requests can be approved once with `once` or denied with `reject`.
## Environment variables
- `OPENCODE_BLE_DAEMON_URL`: BLE daemon base URL. Defaults to
- `OPENCODE_BLE_DAEMON_URL`: ESP-BLE-UART Daemon base URL. Defaults to
`http://127.0.0.1:8888`.
- `OPENCODE_BLE_DECISION_TIMEOUT_SECONDS`: permission decision timeout in
seconds. Defaults to `60`; set it to a positive number.
@@ -232,9 +236,9 @@ permission requests can be approved once with `once` or denied with `reject`.
## Current assumptions
- The BLE daemon endpoint is configured by `OPENCODE_BLE_DAEMON_URL`, defaulting
- The ESP-BLE-UART 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 ESP-BLE-UART Daemon supports both `POST /notify` and `POST /request`.
- 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).
@@ -247,7 +251,7 @@ permission requests can be approved once with `once` or denied with `reject`.
- The plugin checks daemon `/status` to maintain a connected, degraded, or
disabled BLE forwarding state. State changes are reported with OpenCode TUI
notifications when `client.tui.showToast` is available.
- If BLE forwarding is disabled or the BLE daemon cannot return a permission
- If BLE forwarding is disabled or the ESP-BLE-UART Daemon cannot return a permission
decision, the plugin replies `reject`.
## Message routing
@@ -259,7 +263,7 @@ permission requests can be approved once with `once` or denied with `reject`.
## Firmware protocol reference
The BLE UART Bridge daemon wraps plugin messages into JSONL over BLE UART. For
The ESP-BLE-UART Daemon wraps plugin messages into JSONL over BLE UART. For
request/response RPC, `POST /request` sends a non-empty bridge request ID:
```json
@@ -410,4 +414,4 @@ and truncated before crossing BLE.
## Open items
- Add an integration test with a mocked BLE daemon.
- Add an integration test with a mocked ESP-BLE-UART Daemon.
@@ -7,7 +7,7 @@ import { isPermissionDecision } from "./opencode-permission-reply"
import type { BridgeResponse, DaemonResponse, DaemonStatus } from "./types"
/**
* Check whether the local BLE daemon is reachable.
* Check whether the local ESP-BLE-UART Daemon is reachable.
*
* This probe is intentionally silent: OpenCode loads plugins during startup, so
* a missing optional daemon must not print fetch errors into the OpenCode UI.
@@ -25,13 +25,13 @@ export async function isDaemonAvailable(): Promise<boolean> {
export async function getDaemonStatus(): Promise<DaemonStatus> {
const response = await fetch(`${BLE_DAEMON_URL}/status`)
if (!response.ok) {
throw new Error(`BLE daemon status failed: HTTP ${response.status}`)
throw new Error(`ESP-BLE-UART Daemon status check failed: HTTP ${response.status}`)
}
return (await response.json()) as DaemonStatus
}
/**
* Normalize the BLE daemon's response envelope into a permission response.
* Normalize the ESP-BLE-UART Daemon's response envelope into a permission response.
*
* The daemon supports both nested `data`/`response` envelopes and a direct
* top-level decision. Keeping that tolerance here prevents transport quirks
@@ -61,11 +61,11 @@ function parseBridgeResponse(body: DaemonResponse): BridgeResponse {
return body as BridgeResponse
}
throw new Error(`BLE daemon returned an invalid response payload: ${JSON.stringify(body)}`)
throw new Error(`ESP-BLE-UART Daemon returned an invalid response payload: ${JSON.stringify(body)}`)
}
/**
* Send a one-way notification to the BLE daemon.
* Send a one-way notification to the ESP-BLE-UART Daemon.
*
* Use this for events such as session status updates or permission cancellation,
* where the BLE device should update its UI but OpenCode is not waiting for a
@@ -79,12 +79,12 @@ export async function notifyBLE(op: string, data: unknown): Promise<void> {
})
if (!response.ok) {
throw new Error(`BLE daemon notify failed: HTTP ${response.status}`)
throw new Error(`ESP-BLE-UART Daemon notify failed: HTTP ${response.status}`)
}
}
/**
* Send a request to the BLE daemon and wait for a structured response.
* Send a request to the ESP-BLE-UART Daemon and wait for a structured response.
*
* Permission prompts use this path because OpenCode cannot continue until the
* BLE device returns a decision or the request times out.
@@ -101,7 +101,7 @@ export async function sendRequestToBLE(op: string, data: unknown, timeoutSeconds
})
if (!response.ok) {
throw new Error(`BLE daemon request failed: HTTP ${response.status}`)
throw new Error(`ESP-BLE-UART Daemon request failed: HTTP ${response.status}`)
}
return parseBridgeResponse((await response.json()) as DaemonResponse)
@@ -31,7 +31,7 @@ export const METADATA_DISPLAY_KEYS = ["command", "path", "url"] as const
/** Enables verbose local console logging when OPENCODE_BLE_DEBUG=1. */
export const DEBUG = process.env.OPENCODE_BLE_DEBUG === "1"
/** HTTP base URL of the local BLE daemon that bridges OpenCode to the BLE device. */
/** HTTP base URL of the local ESP-BLE-UART Daemon that bridges OpenCode to the BLE device. */
export const BLE_DAEMON_URL = process.env.OPENCODE_BLE_DAEMON_URL ?? "http://127.0.0.1:8888"
const DEFAULT_DECISION_TIMEOUT_SECONDS = 60
@@ -39,12 +39,12 @@ function stateMessage(state: BLEPluginState, status?: DaemonStatus): string {
status?.reconnect_failures !== undefined && status.max_reconnect_failures !== undefined
? ` (${status.reconnect_failures}/${status.max_reconnect_failures} reconnect failures)`
: ""
return `BLE UART daemon is reachable, but the device is disconnected${attempts}. The next BLE send will try to reconnect.`
return `ESP-BLE-UART Daemon is reachable, but the device is disconnected${attempts}. The next BLE send will try to reconnect.`
}
if (status?.daemon_state === "exiting") {
return "BLE UART daemon is exiting after repeated reconnect failures. BLE forwarding is disabled."
return "ESP-BLE-UART Daemon is exiting after repeated reconnect failures. BLE forwarding is disabled."
}
return "BLE UART daemon is unreachable. BLE forwarding is disabled until the daemon is available."
return "ESP-BLE-UART Daemon is unreachable. BLE forwarding is disabled until the daemon is available."
}
async function notifyStateChange(
@@ -54,7 +54,7 @@ async function notifyStateChange(
): Promise<void> {
const variant = state === "connected" ? "success" : state === "degraded" ? "warning" : "error"
const message = stateMessage(state, status)
await showToastBestEffort(client, variant, "OpenCode BLE UART Bridge", message)
await showToastBestEffort(client, variant, "OpenCode ESP-BLE-UART Bridge", message)
await appLogBestEffort(client, variant === "error" ? "error" : variant === "warning" ? "warn" : "info", message, {
state,
status,
@@ -95,7 +95,7 @@ export const BLEDeviceBridgePlugin: Plugin = async ({ client, serverUrl, directo
bleState = "disabled"
if (shouldNotify) {
await notifyStateChange(openCodeClient, "disabled")
await appLogBestEffort(openCodeClient, "warn", "BLE UART daemon status check failed", { error: String(error) })
await appLogBestEffort(openCodeClient, "warn", "ESP-BLE-UART Daemon status check failed", { error: String(error) })
}
}
return bleState
@@ -124,7 +124,7 @@ export const BLEDeviceBridgePlugin: Plugin = async ({ client, serverUrl, directo
// Forwarding session status is best-effort background work. Do not
// await this async IIFE from the OpenCode event callback; otherwise a
// slow or unavailable BLE daemon could block OpenCode's own event loop.
// slow or unavailable ESP-BLE-UART Daemon could block OpenCode's own event loop.
void (async () => {
try {
const previousState = bleState
@@ -139,7 +139,7 @@ export const BLEDeviceBridgePlugin: Plugin = async ({ client, serverUrl, directo
await showToastBestEffort(
openCodeClient,
"success",
"OpenCode BLE UART Bridge",
"OpenCode ESP-BLE-UART Bridge",
"BLE UART device is connected for this OpenCode session.",
)
}
@@ -149,8 +149,8 @@ export const BLEDeviceBridgePlugin: Plugin = async ({ client, serverUrl, directo
if (
// If the session became idle while a BLE permission prompt is
// active, mark that prompt as externally resolved and ask the BLE
// daemon to dismiss it. This prevents an old prompt from being
// active, mark that prompt as externally resolved and ask the
// ESP-BLE-UART Daemon to dismiss it. This prevents an old prompt from being
// answered after OpenCode no longer needs the decision. Only idle
// triggers this cancellation: busy/retry are normal activity
// transitions and should not dismiss an actively displayed prompt.
@@ -174,7 +174,7 @@ export async function replyToOpenCodePermission(
throw new Error("OpenCode client does not expose a permission reply API")
}
/** Runtime guard for permission decisions parsed from BLE daemon JSON. */
/** Runtime guard for permission decisions parsed from ESP-BLE-UART Daemon JSON. */
export function isPermissionDecision(value: unknown): value is PermissionDecision {
return value === "once" || value === "reject"
}
@@ -9,7 +9,7 @@ import {
} from "./config"
import type { PermissionEventProperties } from "./types"
/** Create a unique event ID for messages sent to the BLE daemon. */
/** Create a unique event ID for messages sent to the ESP-BLE-UART Daemon. */
function eventID() {
return crypto.randomUUID()
}
@@ -116,7 +116,7 @@ export function permissionRequestID(permission: PermissionEventProperties): stri
}
/**
* Build the protocol message sent to the BLE daemon whenever OpenCode's session
* Build the protocol message sent to the ESP-BLE-UART Daemon whenever OpenCode's session
* state changes. This is a one-way notification, so the BLE device can update
* its UI but is not expected to send a reply.
*/
@@ -135,7 +135,7 @@ export function buildSessionStatusPayload(
}
/**
* Tell the BLE daemon to dismiss any permission prompt for this session.
* Tell the ESP-BLE-UART Daemon to dismiss any permission prompt for this session.
*
* This is used when OpenCode has already moved on, for example after the
* session becomes idle before the BLE device returns a decision.
@@ -156,7 +156,7 @@ export function buildPermissionCancelPayload(sessionID: string) {
/**
* Build the BLE permission prompt payload from an OpenCode permission event.
*
* This is the main protocol boundary between OpenCode and the BLE daemon. The
* This is the main protocol boundary between OpenCode and the ESP-BLE-UART Daemon. The
* outer fields describe routing and reply behavior; the nested `payload` fields
* are intentionally small and display-oriented for the device UI.
*/
@@ -174,7 +174,7 @@ async function handlePermissionQueueItem(item: PermissionQueueItem): Promise<Per
await showToastBestEffort(
item.client,
"error",
"OpenCode BLE UART Bridge",
"OpenCode ESP-BLE-UART Bridge",
"Failed to get a BLE permission decision. The request was rejected.",
)
} finally {
@@ -15,7 +15,7 @@ export type PermissionDecision = "once" | "reject"
*
* Several ID field names are optional because OpenCode permission events have
* changed across API versions. The bridge normalizes them before talking to the
* BLE daemon or replying back to OpenCode.
* ESP-BLE-UART Daemon or replying back to OpenCode.
*/
export type PermissionEventProperties = {
id?: string
@@ -32,7 +32,7 @@ export type PermissionEventProperties = {
}
/**
* Normalized response expected from the BLE daemon after a permission request.
* Normalized response expected from the ESP-BLE-UART Daemon after a permission request.
*
* `decision` is optional at the type level because the daemon response is parsed
* from JSON. Runtime code validates it before using it as a permission reply.
@@ -44,7 +44,7 @@ export type BridgeResponse = {
}
/**
* Envelope formats the BLE daemon may return.
* Envelope formats the ESP-BLE-UART Daemon may return.
*
* The daemon can wrap the actual payload in either `data` or `response`, and it
* may encode that payload as an object or a JSON string. Keeping this type loose
@@ -55,7 +55,7 @@ export type DaemonResponse = {
response?: unknown
}
/** Status payload returned by the BLE UART daemon `/status` endpoint. */
/** Status payload returned by the ESP-BLE-UART Daemon `/status` endpoint. */
export type DaemonStatus = {
device_id?: string
connection_state?: "DISCONNECTED" | "CONNECTING" | "CONNECTED" | string
+3 -3
View File
@@ -1,9 +1,9 @@
<!-- SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Porting BLE UART Bridge to Custom Scripts
# Porting ESP-BLE-UART Bridge to Custom Scripts
This guide explains how to reuse BLE UART Bridge in your own Python scripts.
This guide explains how to reuse ESP-BLE-UART Bridge in your own Python scripts.
Use the Core API when the Console and Daemon are not the right abstraction for your application. For example, use Core directly when you want to implement custom framing, a test harness, a device provisioning flow, or a domain-specific automation script.
@@ -18,7 +18,7 @@ Use the Core API when the Console and Daemon are not the right abstraction for y
## Install dependencies
You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the extra dependencies required by BLE UART Bridge:
You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the extra dependencies required by ESP-BLE-UART Bridge:
```bash
cd $IDF_PATH
@@ -1,16 +1,16 @@
<!-- SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
# BLE UART Profile Compatibility
# ESP-BLE-UART Profile Compatibility
BLE UART Bridge works with BLE GATT profiles that provide a UART-like data path:
ESP-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 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 BLE-UART-compatible profile
## Default BLE UART-compatible profile
The built-in default profile uses these UUIDs:
@@ -33,7 +33,7 @@ ESP-IDF includes BLE SPP examples that implement Espressif BLE UART-like vendor-
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 ESP-BLE-UART Bridge needs, such as data, command, and status characteristics. To use ESP-BLE-UART Bridge with such a profile, map only the UART-like data path into `BLEUARTProfile`.
## Mapping an ESP-IDF BLE SPP profile
@@ -63,22 +63,22 @@ bridge = BLEUARTBridge("AA:BB:CC:DD:EE:FF", profile=profile)
Replace the UUIDs with the actual UUIDs used by the device firmware.
## What BLE UART Bridge does not map
## What ESP-BLE-UART Bridge does not map
BLE UART Bridge is intentionally focused on the data path. It does not automatically map extra control-plane characteristics that a profile may expose, such as:
ESP-BLE-UART Bridge is intentionally focused on the data path. It does not automatically map extra control-plane characteristics that a profile may expose, such as:
- command characteristics
- status characteristics
- custom configuration characteristics
- profile-specific flow-control semantics
If an application needs those characteristics, implement that logic in a custom script on top of `bleak`, or extend BLE UART Bridge for that specific profile.
If an application needs those characteristics, implement that logic in a custom script on top of `bleak`, or extend ESP-BLE-UART Bridge for that specific profile.
## Classic Bluetooth SPP is different
Classic Bluetooth SPP examples, such as `examples/bluetooth/bluedroid/classic_bt/bt_spp_*`, are not BLE GATT profiles.
They use Classic Bluetooth SPP rather than BLE GATT characteristics, so they are not compatible with BLE UART Bridge.
They use Classic Bluetooth SPP rather than BLE GATT characteristics, so they are not compatible with ESP-BLE-UART Bridge.
## Related docs
@@ -1,16 +1,16 @@
<!-- SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Quick Start: BLE UART Console
# Quick Start: ESP-BLE-UART Console
This guide shows how to use the BLE UART Console for quick manual testing.
This guide shows how to use the ESP-BLE-UART Console for quick manual testing.
The Console is useful when you want to type data into a BLE UART device and inspect the bytes or text sent back by the device.
## Prerequisites
1. A host machine with Bluetooth access.
2. Python environment prepared. You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the BLE UART Bridge dependencies:
2. Python environment prepared. You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the ESP-BLE-UART Bridge dependencies:
```bash
cd $IDF_PATH
@@ -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 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.
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 [ESP-BLE-UART example](../../../../examples/bluetooth/ble_uart_service), which acts as an Echo Server by echoing RX writes back through TX notifications.
## Find a device
@@ -33,7 +33,7 @@ python main.py list-devices
Example output may include a device address and name:
```text
Found: AA:BB:CC:DD:EE:FF, with name esp-ble-uart, rssi=-42
Found: AA:BB:CC:DD:EE:FF, with name BleUart-XXXX, rssi=-42
```
Use the printed device identifier as `DEVICE_ID`. On macOS, this identifier is a CoreBluetooth UUID and is different from the device MAC address.
@@ -131,9 +131,9 @@ This affects BLE GATT write behavior only. It does not create an application-lev
## Common examples
### ESP-IDF BLE UART Echo Server
### ESP-BLE-UART Echo Server
Use the [BLE UART Service example](../../../../examples/bluetooth/ble_uart_service) when you want a ready-made ESP-IDF Echo Server for testing BLE UART Bridge Console. After building, flashing, and pairing with the example, open Console and type any text; the example should echo the same data back as `[RX]` output.
Use the [ESP-BLE-UART example](../../../../examples/bluetooth/ble_uart_service) when you want a ready-made ESP-IDF Echo Server for testing ESP-BLE-UART Bridge Console. After building, flashing, and pairing with the example, open Console and type any text; the example should echo the same data back as `[RX]` output.
```bash
# List nearby BLE devices and use the printed device ID as DEVICE_ID
@@ -1,16 +1,16 @@
<!-- SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
# Quick Start: BLE UART Daemon
# Quick Start: ESP-BLE-UART Daemon
This guide shows how to use BLE UART Daemon mode and the lightweight JSONL RPC protocol used between the host and the BLE device.
This guide shows how to use ESP-BLE-UART Daemon mode and the lightweight JSONL RPC protocol used between the host and the BLE device.
Daemon mode is useful when another local process needs to communicate with a BLE UART device without owning the BLE connection itself.
## Prerequisites
1. A host machine with Bluetooth access.
2. Python environment prepared. You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the BLE UART Bridge dependencies:
2. Python environment prepared. You can reuse the ESP-IDF Python environment, or use your own Python virtual environment. If you reuse the ESP-IDF environment, export it first and then install the ESP-BLE-UART Bridge dependencies:
```bash
cd $IDF_PATH
+1 -1
View File
@@ -16,7 +16,7 @@ def run_console(
encoding: ConsoleEncoding | str = ConsoleEncoding.text,
with_response: bool = False,
) -> None:
# Initialize BLE UART Console
# Initialize ESP-BLE-UART Console
console = BLEUARTConsole(
device_id,
terminator=terminator,
@@ -130,14 +130,14 @@ class BLEUARTConsole(App):
try:
# Should try connection to catch KeyInterrupt during connection establishment
if not await self._bridge.connect():
logger.error(f'Failed to open BLE UART Console for {self._device_id}')
logger.error(f'Failed to open ESP-BLE-UART Console for {self._device_id}')
return
# Run UI event loop
await self.run_async()
finally:
# Disconnect from device
logger.info(f'Closing BLE UART Console for {self._device_id}...')
logger.info(f'Closing ESP-BLE-UART Console for {self._device_id}...')
await self._bridge.disconnect()
# Textual lifecycle hook: build the widget tree before the app is mounted.
@@ -166,7 +166,7 @@ class BLEUARTConsole(App):
# Textual lifecycle hook: widgets are ready, so BLE can be connected and UI updated.
async def on_mount(self) -> None:
self._ui_ready = True
self.title = f'BLE UART — {self._device_id}'
self.title = f'ESP-BLE-UART — {self._device_id}'
self.query_one('#input', Input).focus()
self._write_info(f'Connected to {self._device_id}')
self._drain_rx_pending()
+1 -1
View File
@@ -11,7 +11,7 @@ def run_list_devices() -> None:
def run_connection_check(device_id: str) -> None:
# Initialize BLE UART Bridge
# Initialize ESP-BLE-UART Bridge
bridge = BLEUARTBridge(device_id)
# Connection check
+3 -3
View File
@@ -5,15 +5,15 @@ from __future__ import annotations
class BUBError(Exception):
"""Base exception for the BLE UART Bridge."""
"""Base exception for the ESP-BLE-UART Bridge."""
class DeviceNotFoundError(BUBError):
"""Raised when the requested BLE UART device cannot be found."""
"""Raised when the requested device cannot be found."""
class ConnectionTimeout(BUBError):
"""Raised when the client cannot connect to a BLE UART device."""
"""Raised when the client cannot connect to the device."""
class NotConnectedError(BUBError):
+2 -2
View File
@@ -34,9 +34,9 @@ def _request_json(
detail = e.read().decode(errors='replace')
raise RuntimeError(f'Daemon request failed with HTTP {e.code}: {detail}') from e
except TimeoutError as e:
raise RuntimeError(f'Timed out waiting for BLE UART Daemon: {url}') from e
raise RuntimeError(f'Timed out waiting for ESP-BLE-UART Daemon: {url}') from e
except URLError as e:
raise RuntimeError(f'Failed to connect to BLE UART Daemon: {e.reason}') from e
raise RuntimeError(f'Failed to connect to ESP-BLE-UART Daemon: {e.reason}') from e
if not body:
return {}
@@ -29,7 +29,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
app.state.bridge = BLEUARTBridge(app.state.device_id)
app.state.request_lock = asyncio.Lock()
# Set BLE UART Bridge RX callback
# Set ESP-BLE-UART Bridge RX callback
loop = asyncio.get_running_loop()
app.state.rx_buffer = bytearray()
app.state.pending_requests = {}
@@ -53,8 +53,8 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
# Try to connect to the device
if not await app.state.bridge.connect():
logger.error('Failed to start BLE UART Daemon!')
raise RuntimeError('Failed to start BLE UART Daemon!')
logger.error('Failed to start ESP-BLE-UART Daemon!')
raise RuntimeError('Failed to start ESP-BLE-UART Daemon!')
yield
@@ -62,7 +62,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
await app.state.bridge.disconnect()
app = FastAPI(title='BLE UART Daemon', lifespan=lifespan)
app = FastAPI(title='ESP-BLE-UART Daemon', lifespan=lifespan)
def _request_data_size(data: object) -> int: