docs(api-reference): document new status/playback fields (firmware F-064)
pid, speed, duration, started_at, source, ts on status/playback (and the same fields on the WebSocket playback INFO event), plus how to handle a stale retained message and when the epoch fields are 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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() {
|
||||
|
||||
<Card title="Playback Payload" subtitle="Published on every playback transition to vesper/{device_id}/status/playback — retained, QoS 1">
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
|
||||
<MonoBlock code={'{\n "action": "playing",\n "time_elapsed": 12,\n "projected_run_time": 45000\n}'} />
|
||||
<MonoBlock code={'{\n "action": "playing",\n "time_elapsed": 12,\n "projected_run_time": 45000,\n "pid": "westminster",\n "speed": 400,\n "duration": 120000,\n "started_at": 1790000000,\n "source": "app",\n "ts": 1790000012\n}\n\n// idle\n{ "action": "idle", "time_elapsed": 0, "projected_run_time": 0, "pid": "", "speed": 0, "duration": 0, "started_at": 0, "source": "", "ts": 1790000130 }'} />
|
||||
<p style={{ margin: 0, fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)' }}>
|
||||
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": { ... } }'}.
|
||||
</p>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 'var(--space-3)' }}>
|
||||
{[
|
||||
{ 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 => (
|
||||
<div key={row.field} style={{ padding: 'var(--space-3)', borderRadius: 'var(--radius-md)', backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', marginBottom: 'var(--space-1)' }}>
|
||||
|
||||
Reference in New Issue
Block a user