powerpods/goTool/docs/API_REST.md
simon 490e0ee61f Add UART SET_LOG_LEVEL for runtime master ESP-IDF logging.
Expose the command via goTool CLI/REST and dashboard controls so log verbosity can be tuned without reflashing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 18:03:34 +02:00

8.7 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}

Echo ping (ESP-NOW round-trip latency)

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_pingESPNOW_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.

{
  "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:

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

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}

Log level (master ESP-IDF console)

Runtime get/set of the global log filter on the master (esp_log_level_set("*", …)). Output goes to UART0 (USB debug, 115200), not the host protocol UART (GPIO 2/3, 921600).

GET /api/log-level
POST /api/log-level
Content-Type: application/json
{"write": true, "level": 3}
level Meaning
0 None (no log output)
1 Error
2 Warn
3 Info
4 Debug
5 Verbose
{"success": true, "level": 3}

CLI:

go run . -port /dev/ttyUSB0 log-level
go run . -port /dev/ttyUSB0 log-level -set -level 3

Dashboard: Master card → dropdown ESP Log-Level with Lesen / Setzen.

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
Echo ping POST /api/echo-ping (per-slave latency; UI shows Host ms + ESP ms)
Master log level GET / POST /api/log-level or CLI log-level
Tap notify S/D/T PUT /api/clients/{id}/tap-notify
Tap receive (UI) Live stream + tap notify; see WebSocket doc for external API