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>
This commit is contained in:
117
README.md
Normal file
117
README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user