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