diff --git a/frontend/src/pages/engineering/developer/ApiReferencePage.jsx b/frontend/src/pages/engineering/developer/ApiReferencePage.jsx index 9467952..9a38e69 100644 --- a/frontend/src/pages/engineering/developer/ApiReferencePage.jsx +++ b/frontend/src/pages/engineering/developer/ApiReferencePage.jsx @@ -19,7 +19,7 @@ const TRANSPORTS_V2 = [ { transport: 'MQTT', address: 'vesper/{device_id}/control/ack', direction: 'Outbound, QoS 1', switch: 'command', notes: 'Strictly replies to commands (incl. pong) — nothing unsolicited is ever published here. Echoes req_id when the request sent one.' }, { transport: 'MQTT', address: 'vesper/{device_id}/control/reports', direction: 'Outbound, QoS 1', switch: 'reports', notes: 'Critical, unsolicited, time-sensitive board-initiated events (e.g. bell_overload). Not retained.' }, { transport: 'MQTT', address: 'vesper/{device_id}/status/heartbeat', direction: 'Outbound, retained, QoS 1', switch: 'heartbeat', notes: 'Every 30 s. Also carries LWT — broker publishes {"state":"offline","ok":false} on an unclean disconnect.' }, - { transport: 'MQTT', address: 'vesper/{device_id}/status/playback', direction: 'Outbound, retained, QoS 1', switch: 'playback', notes: 'Playback state transitions only (playing/paused/idle). Published on transition, not polled.' }, + { transport: 'MQTT', address: 'vesper/{device_id}/status/playback', direction: 'Outbound, retained, QoS 1', switch: 'playback', notes: 'Playback state transitions (playing/paused/idle) with pid, speed, duration, started_at, source, ts. Published on transition, not polled. Source of truth for what the board is doing.' }, { transport: 'MQTT', address: 'vesper/{device_id}/system/alerts', direction: 'Outbound, retained until cleared, QoS 1', switch: 'alerts', notes: 'Subsystem state changes (WARNING / CRITICAL / FAILED / CLEARED). Published on transition only.' }, { transport: 'MQTT', address: 'vesper/{device_id}/system/info', direction: 'Outbound, retained, QoS 1', switch: 'info', notes: 'Discrete system events, e.g. boot_report on every boot.' }, { transport: 'MQTT', address: 'vesper/{device_id}/system/logs', direction: 'Outbound, QoS 0', switch: 'logs', notes: 'Full log stream, mirrors serial output. Also gated by the log.set_mqtt level, independently of this switch.' }, @@ -1852,12 +1852,21 @@ function TransportsV2() {
- + +

+ Source of truth for what the board is doing — a control/ack SUCCESS only means the command was accepted. Retained, so a client can receive a stale message: real elapsed time is now − started_at (when non-zero), and ts says how old the snapshot is. WebSocket clients get the same fields on every transition as {'{ "status": "INFO", "type": "playback", "payload": { ... } }'}. +

{[ { field: 'action', type: 'string', notes: '"playing" | "paused" | "idle". Published on transition, not polled.' }, - { field: 'time_elapsed', type: 'int', notes: 'Seconds since playback started (0 for "idle").' }, + { field: 'time_elapsed', type: 'int', notes: 'Seconds since playback started, as of ts (0 for "idle").' }, { field: 'projected_run_time', type: 'int', notes: 'Projected total run time in ms (0 if not applicable).' }, + { field: 'pid', type: 'string', notes: 'Melody being played. "" when idle.' }, + { field: 'speed', type: 'int', notes: 'ms per beat. 0 when idle.' }, + { field: 'duration', type: 'int', notes: 'Requested run-time cap in ms after mode/legacy resolution (timed → duration, interval → total_duration). 0 = no cap (single ends naturally, infinite runs until stopped).' }, + { field: 'started_at', type: 'int', notes: 'Unix epoch s (UTC) when playback started. 0 when idle or no valid clock (NTP, else RTC; RTC-less builds need NTP).' }, + { field: 'source', type: 'string', notes: '"app" (MQTT/WS/HTTP) | "rf" | "button" (UART slave board) | "schedule" (reserved) | "unknown". "" when idle.' }, + { field: 'ts', type: 'int', notes: 'Unix epoch s (UTC) when this message was built. 0 if no valid clock.' }, ].map(row => (