- Small Python service that runs on the pub Pi alongside
pi-capture, polling system metrics every 30s and pushing them to the Worker for the admin dashboard - The dashboard at
/admin/tv-healthshows the latest report as a “Pi · djpi-01 · last seen Ns ago · CPU X% · Mem Y%” card with severity coloring (CPU/temp/mem/disk/transcode backlog) - Push-based because the Pi is NAT’d; the Worker can’t poll. Bearer-token authenticated using the same
NOW_PLAYING_INGEST_TOKENaspi-capture(no new secret to rotate) - Non-critical service — failure does not affect the now-playing pipeline. The dashboard card expires from KV after 10 minutes if reports stop
Sources: pi/pi-status-reporter/main.py, pi/systemd/pi-status-reporter.service, worker.js (handlePiStatusIngest, readPiStatus, PI_STATUS_TTL_SECONDS)
What it reports
Every 30s the reporter calls psutil, reads a few /proc and systemctl interfaces, and POSTs a JSON payload to https://pub.ihnyc-rc.org/api/pi-status:
| Group | Fields |
|---|---|
| Identity | hostname, ipAddress, uptimeSeconds, kernel, distro, lastBoot |
| CPU | cores, percent, load1, load5, load15, temperatureC |
| Memory | totalMb, usedMb, percent |
| Disk | totalGb, usedGb, percent (root filesystem) |
| Scripts | One key per WATCH_SCRIPTS entry → running / stopped / failed (via systemctl is-active) |
| Transcode | transcodeBacklog — count of files in TRANSCODE_BACKLOG_DIR (null if dir absent) |
| Meta | reporterVersion, version (payload schema) |
CPU temperature is read via psutil.sensors_temperatures() with a vcgencmd measure_temp fallback for kernels where the sensor name varies.
Sources: pi/pi-status-reporter/main.py (build_payload, read_cpu_temp_c, read_script_states)
Wire format
sequenceDiagram participant R as pi-status-reporter participant W as Worker /api/pi-status participant KV as DJ_FLASH KV loop every 30s R->>R: build_payload() via psutil R->>W: POST + Bearer NOW_PLAYING_INGEST_TOKEN W->>W: validate + sanitise (bounds + truncation) W->>KV: put("pi:status", json, { expirationTtl: 600 }) W-->>R: 200 { ok: true, reportedAt } end
Server-side validation bounds payload size and field length before writing to KV. Strings are truncated to per-field limits. Numeric values pass through finiteOrNull() so JSON null values are not coerced to zero by Number(null).
The Worker stamps a server-side reportedAt (ISO 8601) so the dashboard’s “last seen Nm ago” is independent of Pi clock drift.
Sources: worker.js (handlePiStatusIngest, finiteOrNull ~line 10770), tests/pi-status.test.js
TTL semantics
pi:status is written with expirationTtl: 600 (10 minutes). Trade-off:
- Long enough to ride out a brief
systemctl restart pi-status-reporterwithout the dashboard card disappearing - Short enough to mark the reporter stale within 10 minutes instead of retaining an old reading
The card hides entirely, rather than turning red, when piStatus is null. Installations without a reporter do not show a permanent offline tile.
Adding pi-status-reporter to WATCH_SCRIPTS makes the dashboard show reporter state alongside the metrics.
Sources: worker.js (PI_STATUS_TTL_SECONDS = 10 * 60), tests/pi-status.test.js (TTL assertion)
Operations
Deployment, environment variables, verification, restart, update, and pause procedures are in the Raspberry Pi audio runbook.
Related
- tv-health — how the dashboard renders the reported data
- pi-capture-ops — the larger Pi runbook (audio capture services)
- required-secrets — where the shared bearer token is configured