Files
ble-uart/README.md
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

118 lines
4.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 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).