ESP-Bridge
Framed binary USB bridge protocol for talking to an ESP32 from Termux — or any Linux host — no root required.
This is the transport/USB layer shared by two separate tools:
- NRSuite — Wi-Fi scanning/sniffing, BLE HID, USB mass storage, and BadUSB over the framed CMD/RESP/EVENT protocol.
- Termux-ESP-Flasher (
nrflash) — a Termux-nativeesptool.pyreplacement that flashes ESP32/ESP8266 firmware with no root and no pyserial, using this package's fd-wrapping and endpoint discovery instead of a vendoredusb_device.py.
It doesn't know anything about Wi-Fi, packet capture, HID keyboards, or bootloader protocols — it just gets bytes reliably between an ESP32 and a host process over USB (native USB CDC or a UART bridge chip), with automatic root/no-root backend detection and USB permission handling. Use it to build your own ESP32-to-Termux tools instead of solving the no-root USB problem from scratch.
Pairs with the BridgeProtocol
PlatformIO library on the firmware side — install both and the two speak
the same CMD/RESP/EVENT frames out of the box. (Tools that don't need the
framed protocol — like nrflash, which speaks the ROM bootloader's own
SLIP framing instead — still use this package for the USB backend
detection, fd wrapping, and UART-bridge register access, just not the
Protocol/frame layer.)
Install
pip install espbridge
or
git clone https://github.com/7wp81x/ESP-Bridge
cd ESP-Bridge
pip install -e .
On stock Termux you'll also need:
pkg install python termux-api libusb
plus the Termux:API app from F-Droid (not the Play Store version).
You normally won't pip install espbridge directly — both NRSuite and
nrflash declare it as a dependency and pull it in automatically (or
auto-install it on first run if missing). Install it by hand only if
you're building your own tool on top of it, vendoring offline, or
debugging the package itself.
Quick start
See examples/echo_cmd.py for a full runnable
example. The short version:
import os, espbridge as eb
backend = eb.detect_backend() # "termux" or "root"
device = eb.wrap_fd(int(os.environ["TERMUX_USB_FD"])) if backend == "termux" \
else eb.wrap_direct()
ep_in, ep_out, iface = eb.get_cdc_endpoints(device)
eb.claim_device(device, iface, fd_wrapped=(backend == "termux"))
eb.reset_endpoint_toggles(device, ep_in, ep_out)
if eb.is_native_cdc(device):
# Required on ESP32-S2/S3 native-USB boards: firmware using
# `while (!Serial) {}` blocks in setup() until the host asserts DTR.
ctrl_iface = eb.find_cdc_control_interface(device, iface)
eb.open_native_cdc_port(device, ctrl_iface)
elif eb.is_uart_bridge(device):
# Required on CP2102/CH340/CH9102/FTDI boards: programs the bridge to
# 115200 8N1 and asserts DTR so it actually forwards RX to the host.
# Skipping this is why PING/send_cmd() times out with no response.
eb.init_uart_bridge(device)
sender = eb.Sender(device, ep_out)
receiver = eb.ReceiverThread(device, ep_in); receiver.start()
proto = eb.Protocol(sender, receiver); proto.start()
resp = proto.send_cmd("PING") # blocks until RESP or timeout
print(resp)
Frame format
[MAGIC 2B: 0xAD 0xDE][TYPE 1B][ID 1B][LENGTH 4B LE][PAYLOAD NB]
| Type | Hex | Direction | Payload |
|---|---|---|---|
| CMD | 0x01 | Host → ESP32 | JSON {"cmd": "...", "args": {...}} |
| RESP | 0x02 | ESP32 → Host | JSON response, matched by frame ID |
| EVENT | 0x03 | ESP32 → Host | Async JSON, id=0 |
| PCAP | 0x04 | ESP32 → Host | Raw binary chunk (id = chunk index) — despite the name, use this for any binary stream |
| ACK | 0x05 | Host → ESP32 | JSON {"chunk": N} — flow control |
| HTML | 0x06 | Host → ESP32 | Chunked raw payload upload |
Commands (CMD) are entirely up to you — define whatever cmd strings and
args your firmware understands. This library only handles framing,
transport, and request/response matching.
This frame format is what NRSuite uses end-to-end. nrflash doesn't use
it at all — the ROM bootloader it talks to has its own fixed SLIP-based
protocol, so nrflash only pulls in the USB backend/endpoint pieces
below, not Protocol.
What's included
protocol.py— frame builder/parser,Protocolclass (send_cmd/on_event/on_pcap)sender.py— thread-safe bulk OUT writer with retryreceiver.py— background bulk IN reader threadusb_device.py— root vs. no-root backend detection, permission flow, endpoint discovery, native-CDC control interface handling, UART-bridge line-coding setup
Not every consumer uses every piece — see "Used by" above for which tool relies on which parts.
Used by
| Project | Uses |
|---|---|
| NRSuite | Full stack — Protocol, Sender, ReceiverThread, and all of usb_device.py |
Termux-ESP-Flasher (nrflash) |
usb_device.py only — backend detection, fd wrapping, endpoint discovery, and UART-bridge register access; brings its own SLIP/ROM-bootloader protocol on top instead of Protocol |
If you're building something similar — anything that needs to talk to an ESP32 (or generic USB-CDC/UART-bridge device) from Termux without root — this package is meant to be the reusable base rather than something you fork per project.
License
MIT
Metadata
Release files for espbridge 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| espbridge-1.2.0.tar.gz | 24.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| espbridge-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.5 kB
Release files / espbridge-1.2.0.tar.gz
| Download URL | espbridge-1.2.0.tar.gz |
|---|---|
| Size | 24.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
34367b6e4bc64f81eb7f275cbe91c9176b5da679dd5a8d6e21cd1ddfb99bc564
|
|
BLAKE2b-256 checksum How to use checksums |
f6f04331404748b8e55109cfdcc6ad2428bb648cc2d67fb796d6009a6ccf25fb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 6, 2026.
Transparency logRelease files / espbridge-1.2.0-py3-none-any.whl
| Download URL | espbridge-1.2.0-py3-none-any.whl |
|---|---|
| Size | 24.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2b00e2c9f68d3d0a6a4ce925ed5e9de1a6600dc5a6345c5020f9f4b515c0b975
|
|
BLAKE2b-256 checksum How to use checksums |
e313edce145b2a3bd14e5afd5664fa9d7f0f9d45b5336a71710797b31fc13356
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 6, 2026.
Transparency log