feat(booth): blink the Entry/Exit lights on radar presence (mirror relay 3)
The on-screen Hyrje/Dalje barrier lights were 2-state (green=free / red=busy)
off the camera lane-status only — they couldn't show the radar-only "detected,
not yet confirmed" state that makes the physical button lamp (relay 3) blink.
Now they mirror the lamp's 3-state rule per lane:
radar present + camera not busy → BLINK green↔red (~1 Hz)
camera busy → SOLID red
otherwise → SOLID green
End-to-end:
- LanePresence (lane-presence.ts): subscribes to deviceEvents.onInput, resolves
each presence edge to its lane via the new direction-agnostic presenceLaneOf()
(device-resolve.ts) — entry AND exit, unlike the entry-gated relayForPresence
the one-car-one-ticket gate uses — and emits a lane-presence {entry,exit} bus
event on change. Wired in server.ts (start + onClose).
- WS forwards it (hello snapshot + push) into live-store.radar.
- BarrierLight (BoothScreen.tsx) is now 3-state; blinks via the .lane-blink
keyframe (index.css), which holds solid-red under prefers-reduced-motion.
Same input + same rule as the lamp, so the screen and the post never disagree.
A new test (lane-presence.test.ts) caught a real bug: the first cut reused
relayForPresence, so the EXIT lane never resolved (it's entry-gated) and never
blinked — presenceLaneOf fixes it. Covers entry/exit independence, de-dupe
across several radars on one lane, and ignoring non-presence inputs.
Full workspace build/lint/test green (185 server tests). Updated the
button-light-indicator wiki page ("On-screen twin").
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -28,6 +28,7 @@ import { roleRoutes } from "./routes/roles.js";
|
||||
import { deviceRoutes } from "./routes/devices.js";
|
||||
import { hikvisionAlarmRoutes } from "./routes/hikvision-alarm.js";
|
||||
import { LaneStatus } from "./lane-status.js";
|
||||
import { LanePresence } from "./lane-presence.js";
|
||||
import { AnprBridge } from "./anpr-entry.js";
|
||||
import { eventRoutes } from "./routes/events.js";
|
||||
import { reportRoutes } from "./routes/reports.js";
|
||||
@@ -125,6 +126,12 @@ export async function buildServer(opts: BuildOptions = {}): Promise<FastifyInsta
|
||||
const laneStatus = new LaneStatus(db, app.log);
|
||||
app.addHook("onClose", async () => laneStatus.stop());
|
||||
|
||||
// Per-lane RADAR presence (presence-input edges → barrier-light blink). Mirrors the
|
||||
// physical button lamp (relay 3): the SAME presence signal, surfaced to the booth UI.
|
||||
const lanePresence = new LanePresence(db, app.log);
|
||||
lanePresence.start();
|
||||
app.addHook("onClose", async () => lanePresence.stop());
|
||||
|
||||
// NB: the Hikvision Alarm Server routes are registered LOWER DOWN — after the read
|
||||
// flows are constructed — because the ANPR bridge they carry depends on the
|
||||
// SubscriptionFlow. See the hikvisionAlarmRoutes() call below the read-flow wiring.
|
||||
@@ -169,7 +176,7 @@ export async function buildServer(opts: BuildOptions = {}): Promise<FastifyInsta
|
||||
|
||||
// Live booth feed: server-pushed ledger + occupancy + printer-status over a
|
||||
// single authenticated WebSocket (/api/ws). See routes/ws.ts.
|
||||
await wsRoutes(app, db, deviceMonitor, laneStatus);
|
||||
await wsRoutes(app, db, deviceMonitor, laneStatus, lanePresence);
|
||||
|
||||
// Entry/exit camera snapshots (BLOB-in-DB), read-only. See snapshot.ts.
|
||||
await snapshotRoutes(app, db);
|
||||
|
||||
Reference in New Issue
Block a user