An early wrong assumption named the QR/RFID access reader "GEE" / "GEE/Fondvision" / "GEE-QR-ER80" (and summarized a raw GEE PDF as its datasheet). There is no GEE device — it's the Dingtian DT-008 (dingtian-tech.com/en_us/qr_code_reader.html), the same vendor as the relay board, which is why it integrates the identical HTTP-GET-push way. Code: - Driver symbol geeQrReaderDriver → dingtianQrReaderDriver; label → "Dingtian DT-008 QR/RFID reader (HTTP push)"; comments/description rewritten to the real DT-008 facts (Wiegand 26/34, TCP/IP, USB, RS485 — not RS-232; QR/barcode + ID/IC/NFC — not DataMatrix/1D). - Persisted driverId "gee-qr-reader" → "dingtian-qr-reader" (the registry lookup key + the row created on assign in qr-reader.ts). - Migration 0015 rewrites existing devices.driver_id rows so configured readers keep resolving (applied to the dev DB — 2 rows; the booth applies it on boot). Behaviour is unchanged: naming + the persisted id only. Wiki + memory: - Renamed entities/gee-qr-er80.md → dingtian-dt008-reader.md and sources/gee-qr-er80.md → dingtian-dt008.md; rewrote both to the real DT-008 product-page specs while KEEPING all the verified-on-hardware protocol facts (cjihao serial, .jsp path, Connection: close). Fixed every cross-reference + "GEE" mention in 6 other pages. Memory gee-reader-serial-binding → dingtian-reader-serial-binding. The only surviving "GEE" mentions are deliberate naming-correction notes, the raw PDF filename, and the append-only log history. Full workspace build/lint/test green; dev DB readers verified resolving to the registered dingtian-qr-reader driver. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
6.7 KiB
type, tags, sources, updated, status
| type | tags | sources | updated | status | |||||
|---|---|---|---|---|---|---|---|---|---|
| concept |
|
2026-06-26 | 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.Reader health was a LIE until 2026-06-26. The QR-reader adapter (a PUSH device: it GETs our backend on each scan and exposes no TCP port) had a hardcoded
healthCheck → { ready, "stub" }, so two genuinely-offline readers still showed green. A push device that's silent is indistinguishable from a dead one — so claimingreadyunconditionally is the worst failure (false-healthy). Fix: an optional reader IP (monitor-only; scans still resolve by serial) + an unprivileged ICMP ping (drivers/icmp.ts, shells/bin/pingin SOCK_DGRAM mode — no CAP_NET_RAW, no native dep; the booth compose setsnet.ipv4.ping_group_range). Reply →ready, no reply →offline; no IP set →degraded("set IP to monitor"), never a false green. Verified on hardware: pinged the real readers on the device VLAN. See dingtian-dt008-reader.
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).