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.
Table of contents
- Quick Start - install dependencies and run the first commands
- Linux troubleshooting - reset the host Bluetooth service when Linux connections fail
- CLI overview - command list and common Console/Daemon workflows
- What is included - directory layout and component roles
- Demos - example integrations built on the daemon
- Choosing Core, Console, or Daemon
- Profile compatibility
- Dependencies
- Limitations
- Further reading
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:
cd $IDF_PATH
. ./export.sh
cd tools/ble/ble_uart_bridge
python -m pip install -r requirements.txt
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 running:
cd tools/ble/ble_uart_bridge
python -m pip install -r requirements.txt
List nearby BLE devices:
python main.py list-devices
Use the printed device identifier as DEVICE_ID in later commands. On macOS, this identifier is a CoreBluetooth UUID and is different from the device MAC address.
Check whether the device can be connected:
python main.py connection-check DEVICE_ID
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. If you need firmware to test against, use the BLE UART Service example 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:
python main.py daemon DEVICE_ID
In another terminal, check daemon status and send a request:
python main.py daemon-status
python main.py daemon-send --op echo "hello"
python main.py daemon-notify --op set_led --json '{"state": true}'
For Daemon details, the HTTP API, and the JSONL RPC protocol, see Quick-Start-BLE-UART-Daemon.md.
Linux troubleshooting
On Linux, the host Bluetooth stack can occasionally get into a stale state. Symptoms may include repeated connection failures, pairing getting stuck, or successful connection without discovering the expected BLE UART service or characteristics. When this happens, reset the system Bluetooth service, then retry list-devices, connection-check, console, or daemon:
sudo systemctl stop bluetooth
sudo systemctl start bluetooth
CLI overview
Run:
python main.py --help
Available commands:
python main.py list-devices
python main.py connection-check DEVICE_ID
python main.py console DEVICE_ID
python main.py daemon DEVICE_ID
python main.py daemon-status
python main.py daemon-send DATA
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, which acts as an Echo Server for Console smoke tests:
python main.py list-devices
python main.py connection-check DEVICE_ID
python main.py console DEVICE_ID
Common Console variants:
# Use CRLF for AT-style commands
python main.py console DEVICE_ID --terminator crlf
# Send and display raw bytes in hex
python main.py console DEVICE_ID --encoding hex
# Use BLE write-with-response
python main.py console DEVICE_ID --with-response
For the full Console guide, see Quick-Start-BLE-UART-Console.md.
Typical Daemon workflow
Use Daemon when a local script, editor integration, or automation tool needs request/response access to a BLE UART device.
Terminal 1 starts the daemon and owns the BLE connection:
python main.py daemon DEVICE_ID
Terminal 2 checks status and sends requests through the daemon:
python main.py daemon-status
python main.py daemon-send --op echo "hello"
python main.py daemon-send --op set_led --json '{"state": true}'
python main.py daemon-notify --op set_led --json '{"state": true}'
For the HTTP API and JSONL RPC wire protocol, see Quick-Start-BLE-UART-Daemon.md.
Custom scripts and porting
Use the Core API directly when you want your own Python script to own the BLE connection, implement custom framing, or integrate BLE UART into a larger automation flow.
For examples using BLEUARTBridge, RX handlers, byte payloads, custom BLEUARTProfile, and custom request/response logic, see PORTING.md.
What is included
tools/ble/ble_uart_bridge/
├── main.py
├── requirements.txt
├── README.md
├── docs/
│ ├── Quick-Start-BLE-UART-Console.md
│ ├── Quick-Start-BLE-UART-Daemon.md
│ ├── Profile-Compatibility.md
│ └── PORTING.md
├── demos/
│ └── opencode/
└── src/
├── core/
├── console/
└── daemon/
Core
The Core component is the reusable BLE transport layer.
Use it when you want to write your own Python script or tool on top of BLE UART without reimplementing scanning, connection management, notification subscription, and chunked GATT writes.
Main responsibilities:
- Scan for nearby BLE devices.
- Check whether a target device can be connected.
- Connect and disconnect with a BLE UART GATT profile.
- Subscribe to device-to-host notifications.
- Send host-to-device data as
str,bytes, orbytearray. - Support a default BLE-UART UUID profile and user-defined BLE UART profiles.
Important APIs:
BLEUARTBridgeBLEUARTProfilerun_list_devices()run_connection_check()
Additional models are available from src.core.models, including DeviceInfo and ConnectionState.
Console
The Console component is an interactive terminal UI for quick BLE UART testing.
Use it when you want to manually type data into a BLE UART device and observe received data without writing code.
Main responsibilities:
- Open an interactive Textual-based UI.
- Display TX, RX, and INFO logs separately.
- Send text lines with configurable line terminators.
- Send and display raw bytes in hex mode.
- Optionally use BLE write-with-response.
- Detect disconnects and show a notice in the UI.
Daemon
The Daemon component exposes BLE UART as a local HTTP service.
Use it when another local tool, script, editor integration, or automation process needs request/response or fire-and-forget IPC with a BLE UART device.
Main responsibilities:
- Keep one BLE UART connection open in a background server process.
- Expose local HTTP endpoints for status, request/response, and fire-and-forget calls.
- Encode requests as newline-delimited JSON messages over BLE UART.
- Correlate device responses by request ID.
- Attempt to reconnect on demand before sending after a BLE disconnect.
- Provide a small JSONL RPC-style envelope as an example protocol.
The daemon protocol is intentionally small. It is not a full RPC framework. It demonstrates a portable pattern that users can copy into firmware or extend in their own application protocol.
By default, the daemon binds to 127.0.0.1. Keep it on a loopback address unless you add your own network access control, because the daemon exposes unauthenticated HTTP endpoints that can send data to the BLE device.
Demos
The demos/ directory contains example integrations that build on BLE UART
Bridge components.
- BLE UART Bridge Demo - OpenCode Integration 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 (喵伴) example inesp-iot-solution.
Choosing Core, Console, or Daemon
| Component | Best for | Interface |
|---|---|---|
| Core | Custom Python tools and scripts | Python API |
| Console | Manual BLE UART smoke tests | Interactive TUI |
| Daemon | Local IPC and automation | HTTP + CLI client |
Use Core when your business logic lives in Python. Use Console when you only need to manually test a BLE UART endpoint. Use Daemon when multiple local processes need to share one BLE connection through a simple request/response or notification boundary.
Profile compatibility
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 - TX characteristic UUID, device to host:
6E400003-B5A3-F393-E0A9-E50E24DCCA9E
For ESP-IDF BLE SPP examples and custom profile mapping details, see Profile-Compatibility.md.
Dependencies
The tool depends on:
bleakfor BLE host accesstextualandrichfor the console UIfastapianduvicornfor daemon modetyperfor the CLIlogurufor logging
Limitations
- Custom GATT UUIDs require constructing
BLEUARTProfilein Python code. - Daemon mode is single-flight for request/response calls: it processes one
/requestat a time./notifysends without waiting for a device response. - Daemon startup requires the initial BLE connection to succeed. After a later BLE disconnect,
/requestor/notifyattempts one reconnect before sending and returns HTTP 503 if the device is still unavailable. After three consecutive reconnect failures, the daemon exits. - Daemon
/requestand/notifylimitopto 64 characters and JSON-encodeddatato 4096 bytes. - The JSONL RPC protocol is a demonstration envelope, not a complete RPC framework.
- Unmatched device messages are logged as unsolicited messages and are not exposed as a streaming API.
- The daemon request framing is newline-delimited JSON; device firmware must send a newline after every JSON response.