Generalise printer-only monitoring to every configured device. New DeviceMonitor polls all enabled devices each tick (default 8s): printers via rich readStatus(), relays/readers/cameras via the generic healthCheck() reachability probe, flattened to one traffic-light (ready/degraded/offline) + detail, deduped (emit on change only), fail-toward-offline. - device-status bus event + GET /api/devices/status snapshot. - Pushed over the existing /api/ws (hello carries the initial set; device-status frame per change). - Web: live-store devices map, WS handler, DeviceFooter chip-per-device (role label not vendor; click a degraded/offline chip for an issues panel). Verified roleKind resolution + change-only emit on a fresh DB. Note: the footer's UI surface (api type, router mount, i18n devices) rides in the subsequent subscription commit due to shared-file overlap. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
5.9 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | |||||
|---|---|---|---|---|---|---|---|---|---|
| concept |
|
2026-06-18 | open |
Device status monitoring (the booth footer)
The booth shows a fixed footer with the live status of every configured device — relays, readers, cameras, printers — so an operator sees at a glance that the barrier relay is reachable, the exit scanner is up, and the ticket printer has paper. This generalises the printer-only printer-status-monitoring to all four device-adapter-pattern. A reliability control, not a threat-model one. (Built 2026-06-18.)
What gets polled, and how
Every enabled row in devices is polled on an interval, regardless of category — the monitor
talks only to the adapter interfaces (device-adapter-pattern), never a driver SDK:
- Printers → their rich
MonitorableDevice.readStatus()(paper end / near-end, cover open, cutter error, off-line) — the same capability the existing printer-status-monitoring uses. The footer surfaces the fault detail. - Relays / readers / cameras → the generic
Device.healthCheck()reachability probe every adapter implements (ready | degraded | offline). This is presence/up-ness, not a deep fault model — a relay either answers or it doesn't.
Both collapse to one traffic-light: ready | degraded | offline, plus a detail string. Fail
toward "there's a problem", never false-healthy: a probe that throws or times out reads
offline (consistent with printer-status-monitoring's fail-safe mapping); a driver that's no
longer registered reads offline ("driver not registered") rather than vanishing.
The monitor (server)
DeviceMonitor (apps/server/src/device-monitor.ts), modelled on the PrinterMonitor:
- re-reads the device set each tick (a newly-assigned/removed device appears/disappears without a restart); drops cached status for devices that are gone or disabled;
- polls every
DEVICE_POLL_MS(default 8000ms), never overlapping ticks; - caches the latest unified status per device id;
- emits a
device-statusbus event only when a device's state or detail changes (deduped).
Relationship to the PrinterMonitor. Both run. The PrinterMonitor stays the authority for the printer-specific live detail + its SSE stream (
/api/printers/status*) that the entry flow may later depend on for printer-roles-failover. The DeviceMonitor is the unified footer feed across all categories. They poll independently (printers get probed by both — cheap HTTP reads); the small duplication is deliberate, to avoid coupling the footer to printer internals. Could be consolidated later if the overlap ever matters.
API / live UI
GET /api/devices/status— cached snapshot of all devices (no device round-trip). Any authenticated role (operational, not a setup action).- Live updates ride the one booth booth-console (
/api/ws): thehelloframe carries the initial device-status set; adevice-statusframe is pushed per change. The web booth-console holds the set keyed by device id; the REST snapshot seeds it / fills in if the socket is briefly down. DeviceFooter(apps/web/src/ui/DeviceFooter.tsx) renders one compact chip per device — a coloured dot + a role label, never the vendor — ordered access → reader → camera → printer, with a right-aligned roll-up ("N with issues" / "all ready"). Mounted in the app shell so it's visible on every screen.
Label = role, not vendor (refinement 2026-06-18)
The chip shows what the device does, not who made it: the localised category + a role/direction
suffix → Lexuesi hyrje, Printer kabina, Kamera dalje. The server sends a structured
roleKind token (not a composed string), the client localises it:
- reader / camera → the direction inherited from its bound relay (
directionOf()in entry-exit-points):entry | exit | both. - access controller →
entry | exit | bothfrom itsrelays[], ormixedwhen it spans more than one direction;nullif it declares none yet. - printer →
lane(entry-dispenser) |booth(booth-receipt) — the printer-roles-failover role. null→ the chip shows the category alone.
Detail does NOT pollute the footer (refinement 2026-06-18)
Chips stay short — no inline fault text. A device that is degraded/offline is clickable (so
is the roll-up); clicking opens a small issues panel anchored above the footer that lists only
the problem devices with their role label, state, the detail string, and the last-checked time.
ready chips are non-interactive. The panel closes on outside-click / Escape (a lightweight
popover — no extra dependency; only Radix Dialog is installed).
Verified (2026-06-18)
On a fresh DB seeded with a stub relay, a TCP reader, and two printers (one reachable, one not):
relay + reader → ready via healthCheck; the unreachable printer → offline (with a detail
string, never threw); the bus emitted once per device on first observation, and a second unchanged
tick was silent (change-only emit). Server + web build clean.
Open / not yet done
- Reachability ≠ correctness.
healthCheck()says a relay/reader answers, not that it's wired to the right barrier or reading cards — that's a setup/precondition concern (first-run-setup, the Dingtian access-controller-button-flow). - No per-device history / alerting. The footer is point-in-time; a flapping device isn't tracked over time. Reconciliation-style alerting is out of scope here.
- Cameras only expose
healthCheckreachability today; a "last snapshot age" health signal could be richer (lpr-camera, opencv-anpr-service). - Possible later consolidation of PrinterMonitor + DeviceMonitor (see the note above).