6.4 KiB
WebSocket API
External API: ws://localhost:8081/ws (default -api-addr, disable with empty string).
Start with go run . -port /dev/ttyUSB0 serve.
Connection flow
- Connect → server sends
hello(push off; defaults and command list). - Send JSON commands → one reply per message (
*_statusorclient_list). - After
set_streamwithenable: true, server may pushinputmessages 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.
Two layers (firmware vs host)
| Layer | Commands | Effect |
|---|---|---|
| Firmware (ESP-NOW) | set_input_stream, set_tap_notify |
Per client_id: slave sends accel and/or tap events to the master |
| 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.
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:
list_clients→ slave IDs- Per slave:
set_input_streamand/orset_tap_notify set_streamwith"enable": true- Read
inputmessages; filter byclient_idin your app (no per-slave filter on the wire)
Push: input
Combines latest accel cache and visible tap state for every slave slot on the master.
Success:
{
"type": "input",
"t": 1716900123456789012,
"success": true,
"clients": [
{
"client_id": 16,
"valid": true,
"x": 12,
"y": -34,
"z": 16384,
"accel_age_ms": 8,
"tap_kind": "single",
"tap_age_ms": 3
},
{
"client_id": 42,
"valid": false
}
]
}
| Field | Meaning |
|---|---|
t |
Unix timestamp in nanoseconds when the host read the cache |
success |
true if CACHE_STATUS succeeded |
clients[] |
One entry per slave slot (includes invalid/stale entries) |
client_id |
Same id as in list_clients |
valid |
false if no accel sample yet or stale; omit x/y/z when false |
x, y, z |
Raw accelerometer LSB (BMA456, ±2 g) |
accel_age_ms |
Ms since the master received this accel sample |
tap_kind |
"single", "double", or "triple"; omit when no recent tap |
tap_age_ms |
Ms since tap in master cache; omit with tap_kind |
Tap events stay visible for tap_display_min_ms (2000, in hello) after the API first saw them.
Failure (no clients array):
{"type":"input","t":1716900123456789012,"success":false,"error":"uart busy"}
Commands
One JSON object per message; field type selects the command.
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)
{
"type": "hello",
"serial_port": "/dev/ttyUSB0",
"interval_ms": 16,
"pre_fetch_ms": 2,
"tap_display_min_ms": 2000,
"commands": [
"list_clients",
"set_stream", "get_stream",
"set_input_stream", "get_input_stream",
"set_tap_notify", "get_tap_notify",
"set_led_ring", "get_battery"
]
}
list_clients
Request: {"type":"list_clients"}
Response client_list:
{
"type": "client_list",
"success": true,
"clients": [
{
"id": 16,
"mac": "aa:bb:cc:dd:ee:10",
"available": true,
"input_stream": false,
"tap_notify_single": false,
"tap_notify_double": false,
"tap_notify_triple": false
}
]
}
Also per client: version, used, last_ping, last_success_ping.
set_stream / get_stream
{"type":"set_stream","enable":true,"interval_ms":32,"pre_fetch":2}
{"type":"get_stream"}
Response stream_status:
{"type":"stream_status","receive_input":true,"interval_ms":32,"pre_fetch":2,"success":true}
set_input_stream / get_input_stream (firmware)
client_id required (> 0).
{"type":"set_input_stream","client_id":16,"enable":true}
{"type":"get_input_stream","client_id":16}
Response input_stream_status:
{"type":"input_stream_status","client_id":16,"enabled":true,"success":true}
set_tap_notify / get_tap_notify (firmware)
Set requires single, double_tap, triple per client, or "all_clients": true for broadcast.
{"type":"set_tap_notify","client_id":16,"single":true,"double_tap":false,"triple":false}
{"type":"get_tap_notify","client_id":16}
Response tap_notify_status:
{"type":"tap_notify_status","client_id":16,"success":true,"single":true,"double_tap":false,"triple":false}
set_led_ring
{"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":"find-me","all_clients":true,"slaves_only":true}
Request mode |
Notes |
|---|---|
clear |
Turn off |
color |
Full ring RGB + intensity |
progress |
progress 0–100 |
digit |
digit 0–10 |
blink |
blink_ms, blink_count |
find-me |
Locate pod |
Target: client_id (0 = master) or all_clients (+ optional slaves_only).
Response led_ring_status — mode is numeric: 0=clear, 1=progress, 2=digit, 3=blink, 4=find-me, 5=color.
{"type":"led_ring_status","success":true,"mode":5,"client_id":16,"slaves_updated":1}
get_battery
Slaves push battery every 30 s; this reads the master cache. Default: all clients.
{"type":"get_battery","all_clients":true}
{"type":"get_battery","client_id":16}
Response battery_status:
{
"type": "battery_status",
"success": true,
"samples": [
{
"client_id": 16,
"lipo1": {"valid": true, "voltage_mv": 3850, "percent": 71},
"lipo2": {"valid": false},
"age_ms": 1200
}
]
}