Stream slave accel via ESP-NOW with master snapshot cache.
Slaves push BMA456 samples at 16ms when enabled; the master caches per client and exposes ACCEL_SNAPSHOT and ACCEL_STREAM over UART. goTool adds dashboard stream controls, HTTP accel-stream routes, and an external WebSocket API with per-connection receive/interval and slave stream commands. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+96
-2
@@ -25,7 +25,7 @@ go run . -port /dev/ttyUSB0 clients
|
||||
| `version` | `0x03` | Prints `version` and `git_hash` from firmware |
|
||||
| `clients` | `0x04` | Lists slaves registered on the master via ESP-NOW |
|
||||
| `deadzone` | `0x06` | Get/set accelerometer deadzone LSB (`-set`, `-value`, `-client`, `-all`) |
|
||||
| `accel` | `0x18` | Read current BMA456 XYZ (raw LSB, ±2g); alias `accel-read` |
|
||||
| `accel` | `0x18` | Cached slave accel snapshot from master (`ACCEL_SNAPSHOT`); alias `accel-read` |
|
||||
| `unicast-test` | `0x07` | Sends ESP-NOW unicast test to one slave (`-client`, `-seq`) |
|
||||
| `test` | — | Run an automated scenario (JSON configs under `testdata/`) |
|
||||
| `serve` | — | Web dashboard at `http://localhost:8080` (WebSocket live updates) |
|
||||
@@ -62,11 +62,91 @@ Polls the master over UART and pushes state to the browser via WebSocket (Alpine
|
||||
```bash
|
||||
go run . -port /dev/ttyUSB0 serve
|
||||
go run . -port /dev/ttyUSB0 serve -addr :8080 -interval 2s
|
||||
go run . -port /dev/ttyUSB0 serve -api-addr :8081 -accel-interval 16ms
|
||||
make gotool-serve PORT=/dev/ttyUSB0
|
||||
```
|
||||
|
||||
Open [http://localhost:8080](http://localhost:8080) — shows master firmware info and the ESP-NOW client table from `CLIENT_INFO`.
|
||||
|
||||
### External API (second HTTP server)
|
||||
|
||||
`serve` starts a separate listener (default **`:8081`**, disable with `-api-addr ""`) for external programs. It shares the same UART connection as the dashboard.
|
||||
|
||||
| Endpoint | Description |
|
||||
|----------|-------------|
|
||||
| `GET /` or `GET /api/v1/` | JSON service info (`default_interval_ms`, min/max, `serial_port`) |
|
||||
| `WebSocket /ws` | Per-connection accel receive + interval; slave ESP-NOW stream control |
|
||||
|
||||
Two layers:
|
||||
|
||||
1. **`set_stream`** — this WebSocket connection: whether to receive `accel` JSON and at what poll rate (1 ms … 10 s per client; server UART poll uses the minimum among active subscribers).
|
||||
2. **`set_accel_stream`** — firmware: whether a slave sends accel to the master over ESP-NOW (16 ms on the pod).
|
||||
|
||||
Polling runs only when at least one connection has `receive_accel: true` **and** at least one slave streams (via `set_accel_stream` or dashboard `:8080`).
|
||||
|
||||
**Hello** (on connect; accel is off until `set_stream`):
|
||||
|
||||
```json
|
||||
{"type":"hello","serial_port":"/dev/ttyUSB0","interval_ms":16,"commands":["set_stream","get_stream","set_accel_stream","get_accel_stream"]}
|
||||
```
|
||||
|
||||
**Receive accel on this connection** (optional `interval_ms`, default from `-accel-interval`):
|
||||
|
||||
```json
|
||||
{"type":"set_stream","enable":true,"interval_ms":32}
|
||||
{"type":"get_stream"}
|
||||
```
|
||||
|
||||
Reply:
|
||||
|
||||
```json
|
||||
{"type":"stream_status","receive_accel":true,"interval_ms":32,"success":true}
|
||||
```
|
||||
|
||||
**Slave ESP-NOW stream** (per `client_id`):
|
||||
|
||||
```json
|
||||
{"type":"set_accel_stream","client_id":16,"enable":true}
|
||||
{"type":"get_accel_stream","client_id":16}
|
||||
```
|
||||
|
||||
Reply:
|
||||
|
||||
```json
|
||||
{"type":"accel_stream_status","client_id":16,"enabled":true,"success":true}
|
||||
```
|
||||
|
||||
**Accel** (only to connections with `receive_accel: true`, and only while slaves stream):
|
||||
|
||||
```json
|
||||
{"type":"accel","t":1716900123456789012,"success":true,"clients":[{"client_id":16,"valid":true,"x":12,"y":-34,"z":16384,"age_ms":8}]}
|
||||
```
|
||||
|
||||
`t` is Unix time in nanoseconds. Each `clients[]` entry is one slave's latest cached sample (raw LSB, ±2g).
|
||||
|
||||
Example (Python):
|
||||
|
||||
```python
|
||||
import asyncio, json, websockets
|
||||
|
||||
async def main():
|
||||
async with websockets.connect("ws://127.0.0.1:8081/ws") as ws:
|
||||
print(await ws.recv()) # hello
|
||||
await ws.send(json.dumps({"type": "set_stream", "enable": True, "interval_ms": 16}))
|
||||
print(await ws.recv()) # stream_status
|
||||
await ws.send(json.dumps({"type": "set_accel_stream", "client_id": 16, "enable": True}))
|
||||
print(await ws.recv()) # accel_stream_status
|
||||
while True:
|
||||
msg = json.loads(await ws.recv())
|
||||
if msg.get("type") != "accel" or not msg.get("success"):
|
||||
continue
|
||||
for c in msg.get("clients", []):
|
||||
if c.get("valid"):
|
||||
print(c["client_id"], c["x"], c["y"], c["z"])
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
If the UART device is unplugged or the port disappears, `serve` keeps running and retries on each poll interval; the UI shows **UART off** until the port is available again.
|
||||
|
||||
The dashboard can configure nodes using the same UART commands as the CLI:
|
||||
@@ -78,7 +158,21 @@ The dashboard can configure nodes using the same UART commands as the CLI:
|
||||
| Alle Slaves | per-slave ESP-NOW (Master bleibt unverändert; CLI `-all` setzt auch den Master) |
|
||||
| Unicast test | `unicast-test -client ID` |
|
||||
|
||||
HTTP API (used by the web UI): `GET/POST /api/deadzone`, `POST /api/unicast-test`, `POST /api/find-me`, `POST /api/restart`, `POST /api/ota` (multipart field `firmware`, max 2 MiB).
|
||||
HTTP API (used by the web UI): `GET/POST /api/deadzone`, `GET/PUT /api/clients/{id}/accel-stream`, `POST /api/accel-stream` (legacy / `all_clients`), `POST /api/unicast-test`, `POST /api/find-me`, `POST /api/restart`, `POST /api/ota` (multipart field `firmware`, max 2 MiB).
|
||||
|
||||
**Accel stream per slave** (must be enabled before values appear; goTool polls only while at least one slave has stream on):
|
||||
|
||||
```http
|
||||
GET /api/clients/16/accel-stream
|
||||
→ {"enabled":false,"client_id":16,"success":true}
|
||||
|
||||
PUT /api/clients/16/accel-stream
|
||||
Content-Type: application/json
|
||||
{"enable": true}
|
||||
→ {"enabled":true,"client_id":16,"success":true}
|
||||
```
|
||||
|
||||
Enable all slaves: `POST /api/accel-stream` with `{"write":true,"enable":true,"all_clients":true}`.
|
||||
|
||||
| UI / API | Behaviour |
|
||||
|----------|-----------|
|
||||
|
||||
Reference in New Issue
Block a user