Android app that connects to a BLE UART device (Nordic UART Service by default, matching RNode BLE) and exposes it as a TCP byte pipe, so socat can present it to Reticulum as a serial port. Foreground service hosts a GATT client with MTU negotiation, chunked serialized writes, and auto-reconnect, plus a single-client last-wins TCP server. No root required on either end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BLE Serial Bridge
An Android app that connects to a BLE UART device (Nordic UART Service by
default — the same service RNode firmware uses for BLE serial) and exposes it
as a TCP byte pipe. With socat on the consuming side, that pipe becomes a
real serial device node that Reticulum can use
via its serial-based interfaces.
┌─────────────┐ BLE/GATT ┌──────────────────┐ TCP ┌──────────────────────┐
│ BLE device │◄────────────►│ BLE Serial Bridge │◄───────►│ socat → pty │
│ (NUS UART, │ │ (this app, │ :7777 │ → rnsd / picocom / │
│ RNode, …) │ │ foreground svc) │ │ anything serial │
└─────────────┘ └──────────────────┘ └──────────────────────┘
No root required anywhere. The consumer can be Termux on the same phone (loopback) or a PC on the same network (enable "Allow LAN").
Usage
- Install and open the app, grant Bluetooth (and notification) permissions.
- If the device requires bonding (RNode BLE does — it displays a pairing PIN), pair it once in Android's Bluetooth settings first.
- Set the TCP port (default
7777). Leave "Allow LAN" off for Termux-on-the-same-phone use; turn it on to serve a PC over WiFi. - The UUID fields are pre-filled with Nordic UART Service. Change them only for devices with a custom UART-style service.
- Tap Scan, then tap your device. The bridge starts as a foreground service and auto-reconnects if the BLE link drops.
The TCP server accepts one client at a time; a new connection displaces the
old one, so a restarted socat reconnects cleanly.
Reticulum on the same phone (Termux)
pkg install socat
socat -d pty,link=$HOME/ttyBLE0,raw,echo=0 tcp:127.0.0.1:7777
Then point an RNS interface at the pty. For an RNode over BLE:
[[RNode BLE]]
type = RNodeInterface
enabled = true
port = /data/data/com.termux/files/home/ttyBLE0
frequency = 867200000
bandwidth = 125000
txpower = 7
spreadingfactor = 8
codingrate = 5
For a generic KISS TNC behind a BLE UART:
[[BLE KISS]]
type = KISSInterface
enabled = true
port = /data/data/com.termux/files/home/ttyBLE0
speed = 115200
(speed is required by pyserial but meaningless here — the BLE link ignores
it.)
Run socat and rnsd together, e.g. in two Termux sessions or under a
process supervisor. Disable Android battery optimization for both this app
and Termux, or the OS will kill the bridge.
Reticulum on a PC (phone as BLE radio bridge)
Enable "Allow LAN" in the app, then on the PC:
socat -d pty,link=/tmp/ttyBLE0,raw,echo=0 tcp:<phone-ip>:7777
and use /tmp/ttyBLE0 as the port in the RNS interface config as above.
To get a system-style name, run socat as root with
pty,link=/dev/ttyBLE0,raw,echo=0.
Note: "Allow LAN" binds to all interfaces with no authentication or encryption — anyone on the network can talk to your radio. Use it only on networks you trust.
Sanity check without Reticulum
socat -d pty,link=/tmp/ttyBLE0,raw,echo=0 tcp:<phone-ip>:7777 &
picocom /tmp/ttyBLE0
Building
Requires the Android SDK (API 36) and JDK 17+.
gradle assembleDebug
adb install app/build/outputs/apk/debug/app-debug.apk
Design notes
- BLE side: GATT central. Negotiates the largest MTU it can (up to 517), chunks writes to MTU−3, serializes them (one write in flight), and uses write-without-response when the characteristic supports it. Notifications on the RX characteristic feed the TCP side. Reconnects every 3 s if the link drops, for as long as the bridge is running.
- TCP side: plain byte stream, no framing, no RFC 2217 — the BLE link has
no baud rate to negotiate.
TCP_NODELAYis set to keep KISS frame latency down. - Defaults: Nordic UART Service
6e400001-…, write6e400002-…, notify6e400003-…— matches RNode BLE and most BLE-UART firmware (Adafruit, Espruino, many ESP32 sketches).