Add dynamic UART/BLE host connect for serve, dashboard, and WS.

Serve starts without a required port; REST/WS/UI can scan and switch transports, with BLE live ticks and UART-only OTA.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-23 22:40:28 +02:00
co-authored by Cursor
parent 92a13d7fd0
commit aa67edfe94
18 changed files with 1573 additions and 404 deletions
+30 -6
View File
@@ -2,15 +2,16 @@
External API: `ws://localhost:8081/ws` (default `-api-addr`, disable with empty string).
Start with `go run . -port /dev/ttyUSB0 serve`.
Start with `go run . serve` (optional `-port` / `-ble` to pre-connect). Host can be opened later via `host_connect` / dashboard REST.
---
## Connection flow
1. Connect → server sends `hello` (push off; defaults and command list).
2. Send JSON commands → one reply per message (`*_status` or `client_list`).
3. After `set_stream` with `enable: true`, server may push `input` messages without a prior command.
1. Connect → server sends `hello` (host status, push off; defaults and command list).
2. Optionally `ble_scan` / `list_serial_ports` then `host_connect` if not already linked.
3. Send JSON commands → one reply per message (`*_status`, `client_list`, `ble_scan_result`, …).
4. After `set_stream` with `enable: true`, server may push `input` messages without a prior command.
Commands and pushes share one socket — always branch on `type`.
@@ -27,7 +28,9 @@ On disconnect, `set_stream` state for that socket is dropped. Firmware settings
| This connection | `set_stream` | Whether you receive push JSON on this socket |
UART polling runs only when at least one connection has `receive_input: true` **and** at least one slave streams input or has tap notify enabled. `set_tap_notify` alone does not enable push — you still need `set_stream`.
Host polling / BLE ticks run only when at least one connection has `receive_input: true` **and** at least one slave streams input or has tap notify enabled. `set_tap_notify` alone does not enable push — you still need `set_stream`.
On **BLE**, live `input` prefers firmware `BLE_STREAM_TICK` pushes; otherwise falls back to `CACHE_STATUS` like UART.
### Push timing (per connection)
@@ -38,7 +41,7 @@ UART polling runs only when at least one connection has `receive_input: true` **
| `pre_fetch` | `set_stream`, `stream_status` | Ms before each push when the host starts the UART cache read |
Global UART poll interval = minimum `interval_ms` among all connections with push enabled.
Global host poll interval = minimum `interval_ms` among all connections with push enabled.
Typical sequence:
@@ -115,10 +118,14 @@ One JSON object per message; field `type` selects the command.
{
"type": "hello",
"serial_port": "/dev/ttyUSB0",
"transport": "uart",
"endpoint": "/dev/ttyUSB0",
"connected": true,
"interval_ms": 16,
"pre_fetch_ms": 2,
"tap_display_min_ms": 2000,
"commands": [
"get_host", "list_serial_ports", "ble_scan", "host_connect", "host_disconnect",
"list_clients",
"set_stream", "get_stream",
"set_input_stream", "get_input_stream",
@@ -128,6 +135,23 @@ One JSON object per message; field `type` selects the command.
}
```
### Host link
| Client `type` | Reply `type` | Notes |
|---------------|--------------|--------|
| `get_host` | `host_status` | `transport`, `endpoint`, `connected` |
| `list_serial_ports` | `serial_ports` | `{ports:[…]}` |
| `ble_scan` | `ble_scan_result` | optional `timeout_s`, `name_filter`; disconnects active BLE first |
| `host_connect` | `host_status` | `transport:"uart","port"` or `transport:"ble","address","name?"` |
| `host_disconnect` | `host_status` | |
```json
{"type":"host_connect","transport":"ble","address":"AA:BB:CC:DD:EE:FF","name":"Powerpod"}
{"type":"host_status","transport":"ble","endpoint":"AA:BB:…","connected":true,"success":true}
```
If the host is not connected, other commands that need the master return their usual error in the status reply.
### `list_clients`
Request: `{"type":"list_clients"}`