# 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`](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 ```http GET / GET /api/v1/ ``` ```json { "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 ```http GET /api/battery?all_clients=true GET /api/battery?client_id=16 POST /api/battery Content-Type: application/json ``` POST body: ```json {"all_clients": true} {"client_id": 0} {"client_id": 16} ``` Response: ```json { "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 ```http POST /api/led-ring Content-Type: application/json ``` Body: ```json {"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` 0–100 | | `digit` | `digit` 0–10 | | `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) ```http GET /api/live-stream PUT /api/live-stream Content-Type: application/json {"enable": true} ``` ```json {"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) ```http GET /api/clients/16/accel-stream PUT /api/clients/16/accel-stream Content-Type: application/json {"enable": true} ``` ```json {"enabled": true, "client_id": 16, "success": true} ``` All slaves: ```http 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) ```http GET /api/clients/16/tap-notify PUT /api/clients/16/tap-notify Content-Type: application/json {"single": true, "double_tap": false, "triple": false} ``` ```json { "client_id": 16, "success": true, "slaves_updated": 1, "single": true, "double_tap": false, "triple": false } ``` All slaves: ```http 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`) ```http 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. ```json { "events": [ {"client_id": 16, "kind": "single", "age_ms": 4} ] } ``` ### Deadzone ```http 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). ```json {"deadzone": 128, "client_id": 0, "success": true, "slaves_updated": 2} ``` ### Unicast test ```http POST /api/unicast-test Content-Type: application/json {"client_id": 16, "seq": 42} ``` ### Echo ping (ESP-NOW round-trip latency) ```http POST /api/echo-ping Content-Type: application/json {"client_id": 16} ``` `client_id` must be a registered slave id (`> 0`). The host sends a microsecond timestamp; the master forwards it over ESP-NOW and the slave echoes it back unchanged. **Flow:** Host → UART → `cmd_espnow_echo_ping` → `ESPNOW_ECHO_PING` (with `master_time_us` from `esp_timer_get_time()`) → Slave → `ESPNOW_ECHO_PONG` → Master `recv_cb` → UART response. **Response fields:** | Field | Unit | Meaning | |-------|------|---------| | `success` | — | `true` if pong received within 500 ms | | `client_id` | — | Echo of request | | `timestamp_us` | µs (Unix) | Echoed host timestamp; must match request on success | | `rtt_ms` | ms | **Host-side** round-trip: goTool send → UART response (full chain incl. USB serial) | | `esp_rtt_us` | µs | **Master-side** ESP-NOW only: `esp_timer_get_time()` delta from ping send to pong recv | `esp_rtt_us` is the raw firmware value (microseconds, not converted). The web dashboard displays it converted to milliseconds for readability (`esp_rtt_us / 1000`, 3 decimal places). The success banner stays visible for at least 5 seconds. ```json { "success": true, "client_id": 16, "timestamp_us": 1717654321123456, "rtt_ms": 49.729, "esp_rtt_us": 18234 } ``` On failure (`success: false`), `esp_rtt_us` is omitted; `rtt_ms` still reflects the host round-trip. HTTP 503 if UART exchange fails (e.g. timeout, client not in registry). **CLI:** ```bash go run . -port /dev/ttyUSB0 echo-ping -client 16 ``` Example output: ``` echo ping: success=true client_id=16 rtt_ms=49.729 esp_rtt_us=18234 ``` ### Find me ```http POST /api/find-me Content-Type: application/json {"client_id": 16} ``` `client_id` `0` = master LED ring. ### Restart ```http POST /api/restart Content-Type: application/json {"client_id": 16} ``` ### OTA (master UART upload) ```http POST /api/ota Content-Type: multipart/form-data ``` Form field **`firmware`**: binary image, max **2 MiB**. ```json {"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: ` | | Alle Slaves deadzone | `all_clients` + `slaves_only` on POST | | Unicast test | `POST /api/unicast-test` | | Echo ping | `POST /api/echo-ping` (per-slave latency; UI shows Host ms + ESP ms) | | Tap notify S/D/T | `PUT /api/clients/{id}/tap-notify` | | Tap receive (UI) | Live stream + tap notify; see WebSocket doc for external API |