334 lines
7.9 KiB
Markdown
334 lines
7.9 KiB
Markdown
# 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: <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 |
|