Kyle Isom 26146ec8c3 Initial implementation of BLE-to-TCP serial bridge for Android
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>
2026-06-09 16:44:53 -07:00

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

  1. Install and open the app, grant Bluetooth (and notification) permissions.
  2. If the device requires bonding (RNode BLE does — it displays a pairing PIN), pair it once in Android's Bluetooth settings first.
  3. 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.
  4. The UUID fields are pre-filled with Nordic UART Service. Change them only for devices with a custom UART-style service.
  5. 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 MTU3, 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_NODELAY is set to keep KISS frame latency down.
  • Defaults: Nordic UART Service 6e400001-…, write 6e400002-…, notify 6e400003-… — matches RNode BLE and most BLE-UART firmware (Adafruit, Espruino, many ESP32 sketches).
Description
No description provided
Readme 39 KiB
Languages
Kotlin 100%