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