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>
118 lines
4.3 KiB
Markdown
118 lines
4.3 KiB
Markdown
# 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](https://reticulum.network/) 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)
|
||
|
||
```sh
|
||
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:
|
||
|
||
```ini
|
||
[[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:
|
||
|
||
```ini
|
||
[[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:
|
||
|
||
```sh
|
||
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
|
||
|
||
```sh
|
||
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+.
|
||
|
||
```sh
|
||
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_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).
|