Update InputClientSample struct to make TapKind optional and improve WebSocket API documentation. Adjust README to reflect changes in input push stream format and clarify connection flow. Enhance API_WEBSOCKET.md with detailed command and push message descriptions.
This commit is contained in:
parent
ab1844ac32
commit
99956e3362
@ -86,7 +86,7 @@ If the UART device is unplugged or the port disappears, `serve` keeps running an
|
|||||||
|
|
||||||
| Doc | Content |
|
| Doc | Content |
|
||||||
|-----|---------|
|
|-----|---------|
|
||||||
| **[docs/API_WEBSOCKET.md](docs/API_WEBSOCKET.md)** | `ws://…:8081/ws` commands, **`accel` / `tap` push stream** format, dashboard `ws://…:8080/ws` |
|
| **[docs/API_WEBSOCKET.md](docs/API_WEBSOCKET.md)** | `ws://…:8081/ws` commands and **`input` push stream** (accel + tap) |
|
||||||
| **[docs/API_REST.md](docs/API_REST.md)** | REST on `:8080` (dashboard) and `:8081` (battery, LED, service info) |
|
| **[docs/API_REST.md](docs/API_REST.md)** | REST on `:8080` (dashboard) and `:8081` (battery, LED, service info) |
|
||||||
|
|
||||||
CLI:
|
CLI:
|
||||||
|
|||||||
@ -31,7 +31,7 @@ type InputClientSample struct {
|
|||||||
Y int32 `json:"y,omitempty"`
|
Y int32 `json:"y,omitempty"`
|
||||||
Z int32 `json:"z,omitempty"`
|
Z int32 `json:"z,omitempty"`
|
||||||
AccelAgeMs uint32 `json:"accel_age_ms,omitempty"`
|
AccelAgeMs uint32 `json:"accel_age_ms,omitempty"`
|
||||||
TapKind string `json:"tap_kind"`
|
TapKind string `json:"tap_kind,omitempty"`
|
||||||
TapAgeMs uint32 `json:"tap_age_ms,omitempty"`
|
TapAgeMs uint32 `json:"tap_age_ms,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,7 +389,6 @@ func (h *accelStreamHub) inputClientsFromCacheLocked(cache *pb.CacheStatusRespon
|
|||||||
for _, c := range cache.GetClients() {
|
for _, c := range cache.GetClients() {
|
||||||
sample := InputClientSample{
|
sample := InputClientSample{
|
||||||
ClientID: c.GetClientId(),
|
ClientID: c.GetClientId(),
|
||||||
TapKind: "none",
|
|
||||||
}
|
}
|
||||||
if a := c.GetAccel(); a != nil {
|
if a := c.GetAccel(); a != nil {
|
||||||
sample.Valid = a.GetValid()
|
sample.Valid = a.GetValid()
|
||||||
|
|||||||
@ -1,58 +1,54 @@
|
|||||||
# WebSocket API
|
# WebSocket API
|
||||||
|
|
||||||
`go run . -port /dev/ttyUSB0 serve` exposes the WebSocket enpoint
|
External API: `ws://localhost:8081/ws` (default `-api-addr`, disable with empty string).
|
||||||
|
|
||||||
| URL | Port (default) | Role |
|
Start with `go run . -port /dev/ttyUSB0 serve`.
|
||||||
|-----|----------------|------|
|
|
||||||
| `ws://localhost:8081/ws` | External API (`-api-addr`) | Request/response commands + optional **input** push stream |
|
---
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
Commands and pushes share one socket — always branch on `type`.
|
||||||
|
|
||||||
|
On disconnect, `set_stream` state for that socket is dropped. Firmware settings (`set_input_stream`, `set_tap_notify`) stay on the master until changed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## External API (`:8081/ws`)
|
## Two layers (firmware vs host)
|
||||||
|
|
||||||
### Connection flow
|
|
||||||
|
|
||||||
1. Connect → server sends **`hello`** (receive off; lists available commands).
|
|
||||||
2. Send JSON commands → server replies with a matching `*_status` or `client_list` message (one reply per command).
|
|
||||||
3. After `set_stream` with `enable: true`, the server may send **`input`** messages **without** a prior command (push stream).
|
|
||||||
|
|
||||||
Commands and stream pushes are multiplexed on one socket. While streaming, always parse `type` and branch (status vs sample vs error).
|
|
||||||
|
|
||||||
### Two layers (firmware vs host)
|
|
||||||
|
|
||||||
| Layer | Commands | Effect |
|
| Layer | Commands | Effect |
|
||||||
|-------|----------|--------|
|
|-------|----------|--------|
|
||||||
| **Firmware (ESP-NOW)** | `set_input_stream`, `set_tap_notify` | Per `client_id`: slave sends accel samples and/or tap events to the master |
|
| Firmware (ESP-NOW) | `set_input_stream`, `set_tap_notify` | Per `client_id`: slave sends accel and/or tap events to the master |
|
||||||
| **This connection (host)** | `set_stream` | Whether **you** receive push JSON, at what rate (`interval_ms`, 1 ms … 10 s), and how early the UART read starts (`pre_fetch`) |
|
| This connection | `set_stream` | Whether you receive push JSON on this socket |
|
||||||
|
|
||||||
- **UART polling** runs only if at least one connection has `receive_input: true` (`set_stream`) **and** at least one slave streams input (`set_input_stream`) or has tap notify enabled (`set_tap_notify`).
|
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`.
|
||||||
- **`set_tap_notify` alone** configures which tap kinds the slave reports; it does **not** enable host push by itself — you still need `set_stream`.
|
|
||||||
|
### Push timing (per connection)
|
||||||
|
|
||||||
|
| Field | Where | Meaning |
|
||||||
|
|-------|-------|---------|
|
||||||
|
| `interval_ms` | `hello`, `set_stream`, `stream_status` | Minimum ms between `input` pushes on this socket (1 … 10000) |
|
||||||
|
| `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.
|
||||||
|
|
||||||
Typical sequence:
|
Typical sequence:
|
||||||
|
|
||||||
1. `list_clients` → slave IDs
|
1. `list_clients` → slave IDs
|
||||||
2. Per slave: `set_input_stream` and/or `set_tap_notify` as needed
|
2. Per slave: `set_input_stream` and/or `set_tap_notify`
|
||||||
3. `set_stream` with `"enable": true`
|
3. `set_stream` with `"enable": true`
|
||||||
4. Read **`input`** messages in a loop
|
4. Read `input` messages; filter by `client_id` in your app (no per-slave filter on the wire)
|
||||||
|
|
||||||
There is **no per-slave filter** on push messages: each `input` contains all cached slaves. Filter by `client_id` in your app.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Push stream messages
|
## Push: `input`
|
||||||
|
|
||||||
These are the samples you get after enabling receive. Timing is per WebSocket connection:
|
Combines latest accel cache and visible tap state for every slave slot on the master.
|
||||||
|
|
||||||
- **`interval_ms`** — minimum time between consecutive `input` pushes on this socket.
|
**Success:**
|
||||||
- **`pre_fetch`** — milliseconds **before** each scheduled push when the host sends the UART cache read, so the master has time to collect data from all slaves before the JSON goes out.
|
|
||||||
|
|
||||||
The server UART poll uses the **minimum** `interval_ms` among all subscribers with `receive_input: true`.
|
|
||||||
|
|
||||||
### `input` (type `"input"`)
|
|
||||||
|
|
||||||
Sent when `set_stream` has `enable: true` and the poll tick fires for this connection (after the UART read started `pre_fetch` ms earlier). Each message combines the latest accel cache and visible tap state for every slave slot on the master.
|
|
||||||
|
|
||||||
**Success** — all slaves with a cache entry (not only those with `valid: true`):
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -72,8 +68,7 @@ Sent when `set_stream` has `enable: true` and the poll tick fires for this conne
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"client_id": 42,
|
"client_id": 42,
|
||||||
"valid": false,
|
"valid": false
|
||||||
"tap_kind": "none"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -81,38 +76,33 @@ Sent when `set_stream` has `enable: true` and the poll tick fires for this conne
|
|||||||
|
|
||||||
| Field | Meaning |
|
| Field | Meaning |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
| `t` | Unix timestamp in **nanoseconds** when the host read the cache |
|
| `t` | Unix timestamp in nanoseconds when the host read the cache |
|
||||||
| `success` | `true` if `CACHE_STATUS` succeeded |
|
| `success` | `true` if `CACHE_STATUS` succeeded |
|
||||||
| `clients[]` | One entry per slave slot in the master cache |
|
| `clients[]` | One entry per slave slot (includes invalid/stale entries) |
|
||||||
| `client_id` | ESP-NOW client id (same as `list_clients`) |
|
| `client_id` | Same id as in `list_clients` |
|
||||||
| `valid` | `false` if no accel sample yet or stale; omit `x`/`y`/`z` when false |
|
| `valid` | `false` if no accel sample yet or stale; omit `x`/`y`/`z` when false |
|
||||||
| `x`, `y`, `z` | Raw accelerometer LSB (BMA456, ±2 g scale on the pod) |
|
| `x`, `y`, `z` | Raw accelerometer LSB (BMA456, ±2 g) |
|
||||||
| `accel_age_ms` | Milliseconds since the master received this accel sample |
|
| `accel_age_ms` | Ms since the master received this accel sample |
|
||||||
| `tap_kind` | `"none"`, `"single"`, `"double"`, or `"triple"` |
|
| `tap_kind` | `"single"`, `"double"`, or `"triple"`; omit when no recent tap |
|
||||||
| `tap_age_ms` | Milliseconds since the tap was seen in the master cache; omit when `tap_kind` is `"none"` |
|
| `tap_age_ms` | Ms since tap in master cache; omit with `tap_kind` |
|
||||||
|
|
||||||
Tap events stay visible for **`tap_display_min_ms`** (2000 ms, also in `hello`) after the API first saw them, even if the hardware age grows.
|
Tap events stay visible for `tap_display_min_ms` (2000, in `hello`) after the API first saw them.
|
||||||
|
|
||||||
**Failure** (e.g. UART busy):
|
**Failure** (no `clients` array):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{"type":"input","t":1716900123456789012,"success":false,"error":"uart busy"}
|
||||||
"type": "input",
|
|
||||||
"t": 1716900123456789012,
|
|
||||||
"success": false,
|
|
||||||
"error": "uart busy"
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
No `clients` array on failure.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Commands (request → response)
|
## Commands
|
||||||
|
|
||||||
Send one JSON object per message. Field `type` selects the command.
|
One JSON object per message; field `type` selects the command.
|
||||||
|
|
||||||
### `hello` (server → client, on connect)
|
**Errors:** Replies use the matching response `type`. On failure: `success: false` (or omitted) and `"error": "…"`. Malformed JSON or unknown `type` → `stream_status` with `error`.
|
||||||
|
|
||||||
|
### `hello` (server → client)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -121,7 +111,6 @@ Send one JSON object per message. Field `type` selects the command.
|
|||||||
"interval_ms": 16,
|
"interval_ms": 16,
|
||||||
"pre_fetch_ms": 2,
|
"pre_fetch_ms": 2,
|
||||||
"tap_display_min_ms": 2000,
|
"tap_display_min_ms": 2000,
|
||||||
"note": "set_tap_notify configures slave S/D/T only; set_stream enables input polling/push on this connection",
|
|
||||||
"commands": [
|
"commands": [
|
||||||
"list_clients",
|
"list_clients",
|
||||||
"set_stream", "get_stream",
|
"set_stream", "get_stream",
|
||||||
@ -146,11 +135,7 @@ Response `client_list`:
|
|||||||
{
|
{
|
||||||
"id": 16,
|
"id": 16,
|
||||||
"mac": "aa:bb:cc:dd:ee:10",
|
"mac": "aa:bb:cc:dd:ee:10",
|
||||||
"version": 1,
|
|
||||||
"available": true,
|
"available": true,
|
||||||
"used": true,
|
|
||||||
"last_ping": 1234,
|
|
||||||
"last_success_ping": 1200,
|
|
||||||
"input_stream": false,
|
"input_stream": false,
|
||||||
"tap_notify_single": false,
|
"tap_notify_single": false,
|
||||||
"tap_notify_double": false,
|
"tap_notify_double": false,
|
||||||
@ -160,28 +145,24 @@ Response `client_list`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `set_stream` / `get_stream` (receive input on this connection)
|
Also per client: `version`, `used`, `last_ping`, `last_success_ping`.
|
||||||
|
|
||||||
|
### `set_stream` / `get_stream`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"type":"set_stream","enable":true,"interval_ms":32,"pre_fetch":2}
|
{"type":"set_stream","enable":true,"interval_ms":32,"pre_fetch":2}
|
||||||
{"type":"get_stream"}
|
{"type":"get_stream"}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Field | Meaning |
|
|
||||||
|-------|---------|
|
|
||||||
| `enable` | Turn push stream on/off for this connection |
|
|
||||||
| `interval_ms` | Minimum time between `input` pushes (1 … 10000) |
|
|
||||||
| `pre_fetch` | Milliseconds before each push when the host starts the UART cache read; optional, default in `hello` (`pre_fetch_ms`) |
|
|
||||||
|
|
||||||
Response `stream_status`:
|
Response `stream_status`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"type":"stream_status","receive_input":true,"interval_ms":32,"pre_fetch":2,"success":true}
|
{"type":"stream_status","receive_input":true,"interval_ms":32,"pre_fetch":2,"success":true}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `set_input_stream` / `get_input_stream` (firmware, per slave)
|
### `set_input_stream` / `get_input_stream` (firmware)
|
||||||
|
|
||||||
`client_id` required (> 0). Enables accel streaming from the slave to the master.
|
`client_id` required (> 0).
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"type":"set_input_stream","client_id":16,"enable":true}
|
{"type":"set_input_stream","client_id":16,"enable":true}
|
||||||
@ -194,41 +175,31 @@ Response `input_stream_status`:
|
|||||||
{"type":"input_stream_status","client_id":16,"enabled":true,"success":true}
|
{"type":"input_stream_status","client_id":16,"enabled":true,"success":true}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `set_tap_notify` / `get_tap_notify` (firmware, per slave)
|
### `set_tap_notify` / `get_tap_notify` (firmware)
|
||||||
|
|
||||||
Per client: `single`, `double_tap`, `triple` required on set.
|
Set requires `single`, `double_tap`, `triple` per client, or `"all_clients": true` for broadcast.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"type":"set_tap_notify","client_id":16,"single":true,"double_tap":false,"triple":false}
|
{"type":"set_tap_notify","client_id":16,"single":true,"double_tap":false,"triple":false}
|
||||||
|
{"type":"get_tap_notify","client_id":16}
|
||||||
```
|
```
|
||||||
|
|
||||||
Broadcast: `"all_clients": true` with the three booleans.
|
|
||||||
|
|
||||||
Response `tap_notify_status`:
|
Response `tap_notify_status`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{"type":"tap_notify_status","client_id":16,"success":true,"single":true,"double_tap":false,"triple":false}
|
||||||
"type": "tap_notify_status",
|
|
||||||
"client_id": 16,
|
|
||||||
"success": true,
|
|
||||||
"single": true,
|
|
||||||
"double_tap": false,
|
|
||||||
"triple": false
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### `set_led_ring`
|
### `set_led_ring`
|
||||||
|
|
||||||
Control the LED ring on the master or a slave.
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"type":"set_led_ring","mode":"color","client_id":16,"r":255,"g":0,"b":0,"intensity":128}
|
{"type":"set_led_ring","mode":"color","client_id":16,"r":255,"g":0,"b":0,"intensity":128}
|
||||||
{"type":"set_led_ring","mode":"digit","client_id":0,"digit":3,"r":0,"g":255,"b":0}
|
{"type":"set_led_ring","mode":"digit","client_id":0,"digit":3,"r":0,"g":255,"b":0}
|
||||||
{"type":"set_led_ring","mode":"find-me","all_clients":true,"slaves_only":true}
|
{"type":"set_led_ring","mode":"find-me","all_clients":true,"slaves_only":true}
|
||||||
```
|
```
|
||||||
|
|
||||||
| `mode` | Notes |
|
| Request `mode` | Notes |
|
||||||
|--------|--------|
|
|----------------|--------|
|
||||||
| `clear` | Turn off |
|
| `clear` | Turn off |
|
||||||
| `color` | Full ring RGB + `intensity` |
|
| `color` | Full ring RGB + `intensity` |
|
||||||
| `progress` | `progress` 0–100 |
|
| `progress` | `progress` 0–100 |
|
||||||
@ -236,9 +207,9 @@ Control the LED ring on the master or a slave.
|
|||||||
| `blink` | `blink_ms`, `blink_count` |
|
| `blink` | `blink_ms`, `blink_count` |
|
||||||
| `find-me` | Locate pod |
|
| `find-me` | Locate pod |
|
||||||
|
|
||||||
Use `client_id` (`0` = master) or `all_clients` (+ optional `slaves_only`) for broadcast.
|
Target: `client_id` (`0` = master) or `all_clients` (+ optional `slaves_only`).
|
||||||
|
|
||||||
Response `led_ring_status`:
|
Response `led_ring_status` — `mode` is numeric: 0=clear, 1=progress, 2=digit, 3=blink, 4=find-me, 5=color.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"type":"led_ring_status","success":true,"mode":5,"client_id":16,"slaves_updated":1}
|
{"type":"led_ring_status","success":true,"mode":5,"client_id":16,"slaves_updated":1}
|
||||||
@ -246,15 +217,13 @@ Response `led_ring_status`:
|
|||||||
|
|
||||||
### `get_battery`
|
### `get_battery`
|
||||||
|
|
||||||
Read cached battery samples from the master. Slaves push battery every **30 s**; this command reads the master cache.
|
Slaves push battery every 30 s; this reads the master cache. Default: all clients.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"type":"get_battery","all_clients":true}
|
{"type":"get_battery","all_clients":true}
|
||||||
{"type":"get_battery","client_id":16}
|
{"type":"get_battery","client_id":16}
|
||||||
```
|
```
|
||||||
|
|
||||||
Default if omitted: all clients.
|
|
||||||
|
|
||||||
Response `battery_status`:
|
Response `battery_status`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user