demo-game/API_REST.md
simon 4f3435ba37 Add per-client LED controls aligned with the REST API schema.
Each client panel gets mode-specific LED inputs (color, intensity, progress, digit, blink) and the Makefile now copies API_REST.md alongside the WebSocket docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 21:55:18 +02:00

6.2 KiB
Raw Permalink Blame History

REST API

go run . -port /dev/ttyUSB0 serve starts two HTTP servers on the same UART link:

Base URL Flag Used by
http://localhost:8080 -addr (default :8080) Web dashboard + automation on the UI routes
http://localhost:8081 -api-addr (default :8081, "" disables) External programs; subset of routes + service info

WebSocket streaming (accel/tap push): API_WEBSOCKET.md.

All JSON responses use Content-Type: application/json. On UART errors many routes return 503 with "error" in the body.


External API (:8081)

Service info

GET /
GET /api/v1/
{
  "name": "powerpod-external-api",
  "version": "1",
  "serial_port": "/dev/ttyUSB0",
  "websocket": "/ws",
  "default_interval_ms": 16,
  "min_interval_ms": 1,
  "max_interval_ms": 10000,
  "tap_display_min_ms": 2000,
  "description": "..."
}

Battery

GET /api/battery?all_clients=true
GET /api/battery?client_id=16
POST /api/battery
Content-Type: application/json

POST body:

{"all_clients": true}
{"client_id": 0}
{"client_id": 16}

Response:

{
  "success": true,
  "samples": [
    {
      "client_id": 16,
      "lipo1": {"valid": true, "voltage_mv": 3850, "percent": 71},
      "lipo2": {"valid": false},
      "age_ms": 1200
    }
  ]
}

Slaves push battery to the master every 30 s; these routes read the master cache.

WebSocket equivalent: get_battery on ws://localhost:8081/ws (reply type battery_status).

LED ring

POST /api/led-ring
Content-Type: application/json

Body:

{"mode":"color","client_id":16,"r":255,"g":0,"b":0,"intensity":128}
{"mode":"digit","client_id":0,"digit":3,"r":0,"g":255,"b":0}
{"mode":"find-me","all_clients":true,"slaves_only":true}
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

Use client_id (0 = master) or all_clients (+ optional slaves_only) for broadcast.

Response: success, slaves_updated, optional error.

WebSocket: set_led_ring with the same fields plus "type":"set_led_ring"led_ring_status.


Dashboard API (:8080)

Used by the web UI; safe for scripts that drive the same features.

Live stream (host CACHE_STATUS poll ~16 ms)

GET /api/live-stream
PUT /api/live-stream
Content-Type: application/json
{"enable": true}
{"enabled": true, "success": true}

Enables fast UART polling for dashboard accel/tap display. Per-slave accel still requires accel-stream (below).

Accel stream (firmware ESP-NOW, per slave)

GET /api/clients/16/accel-stream
PUT /api/clients/16/accel-stream
Content-Type: application/json
{"enable": true}
{"enabled": true, "client_id": 16, "success": true}

All slaves:

POST /api/accel-stream
Content-Type: application/json
{"write": true, "enable": true, "all_clients": true}

Polling on the host runs only while at least one slave has streaming enabled (here or via external WebSocket / dashboard).

Tap notify (firmware; does not start host tap polling)

GET /api/clients/16/tap-notify
PUT /api/clients/16/tap-notify
Content-Type: application/json
{"single": true, "double_tap": false, "triple": false}
{
  "client_id": 16,
  "success": true,
  "slaves_updated": 1,
  "single": true,
  "double_tap": false,
  "triple": false
}

All slaves:

POST /api/tap-notify
Content-Type: application/json
{"single": true, "double_tap": false, "triple": false, "all_clients": true}

Host tap display / external set_tap_stream is separate.

Tap snapshot (one-shot, via CACHE_STATUS)

GET /api/tap-snapshot?client_id=16

Reads the combined cache (CACHE_STATUS); optional client_id filters pending tap events. Pending taps are consumed on read.

{
  "events": [
    {"client_id": 16, "kind": "single", "age_ms": 4}
  ]
}

Deadzone

GET /api/deadzone?client_id=0
POST /api/deadzone
Content-Type: application/json
{"write": true, "deadzone": 128, "client_id": 0}

With all_clients + slaves_only: push to ESP-NOW slaves only (master BMA456 unchanged).

{"deadzone": 128, "client_id": 0, "success": true, "slaves_updated": 2}

Unicast test

POST /api/unicast-test
Content-Type: application/json
{"client_id": 16, "seq": 42}

Find me

POST /api/find-me
Content-Type: application/json
{"client_id": 16}

client_id 0 = master LED ring.

Restart

POST /api/restart
Content-Type: application/json
{"client_id": 16}

OTA (master UART upload)

POST /api/ota
Content-Type: multipart/form-data

Form field firmware: binary image, max 2 MiB.

{"success": true, "bytes_written": 123456, "target_slot": 1}

Firmware distributes to slaves over ESP-NOW after OTA_END. Progress also appears on dashboard WebSocket as ota_progress messages.

CLI equivalent: go run . -port /dev/ttyUSB0 ota build/powerpod.bin

LED ring and battery

Same as external API:

  • POST /api/led-ring
  • GET / POST /api/battery

Dashboard vs external

Feature Dashboard :8080 External :8081
Client list Via dashboard WebSocket state / CLI clients WebSocket list_clients
Accel/tap push stream WebSocket state when live-stream on WebSocket set_stream / set_tap_stream
Accel stream enable REST PUT .../accel-stream WebSocket set_accel_stream
Tap notify REST PUT .../tap-notify WebSocket set_tap_notify
LED / battery REST REST + WebSocket on :8081

UI mapping

UI action REST / CLI
Nur Master deadzone POST /api/deadzone client_id: 0 or CLI deadzone -set -client 0
Einzelner Slave client_id: <id>
Alle Slaves deadzone all_clients + slaves_only on POST
Unicast test POST /api/unicast-test
Tap notify S/D/T PUT /api/clients/{id}/tap-notify
Tap receive (UI) Live stream + tap notify; see WebSocket doc for external API