powerpods/goTool/docs/API_WEBSOCKET.md

7.4 KiB
Raw Permalink Blame History

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

  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.


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:

  1. list_clients → slave IDs
  2. Per slave: set_input_stream and/or set_tap_notify
  3. set_stream with "enable": true
  4. Read input messages; filter by client_id in 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 typestream_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 0100
digit digit 010
blink blink_ms, blink_count
find-me Locate pod

Target: client_id (0 = master) or all_clients (+ optional slaves_only).

Response led_ring_statusmode 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
    }
  ]
}