feat(booth): live lane busy/free barrier lights from camera vehicle detection

A Hikvision vehicle detection (eventType=VMD, targetType=vehicle) on a
camera bound to entry/exit now marks that lane "busy" and shows it as a
barrier light beside the scan input on the booth (green=free, red=busy).
Advisory only — it gates nothing (never blocks a ticket or opens a barrier).

- Parse eventState (active/inactive) from the Hik payload.
- LaneStatus tracker: a vehicle `active` event marks the camera's bound lane
  busy + arms an auto-clear timer. This camera class sends no leave/`inactive`
  signal, so "free" is timeout-driven (LANE_BUSY_TTL_MS, default 90s; the
  camera re-fires `active` while a car sits there, refreshing the timer). A
  "both"-direction camera marks both lanes.
- Push lane-status over the existing booth WS (+ in the hello snapshot);
  live-store holds { entry, exit }; two BarrierLight icons render it.
- i18n booth.laneEntry/laneExit (sq + en).

Tests: lane-status.test.ts (7 — busy/free, TTL auto-clear, timer re-arm,
no re-emit while busy, both/exit direction, unknown device). server 120/120;
web + server build/lint green.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-22 17:42:54 +02:00
parent 6f4e390c05
commit e0b9442acc
11 changed files with 366 additions and 17 deletions
+2
View File
@@ -95,6 +95,8 @@ export const en: Catalog = {
booth: {
processTicket: "Process ticket",
scanPlaceholder: "Scan or type ticket number…",
laneEntry: "Entry",
laneExit: "Exit",
open: "Open",
occupancy: "Occupancy",
occUnavailable: "occupancy unavailable",
+2
View File
@@ -97,6 +97,8 @@ export const sq = {
booth: {
processTicket: "Proceso biletën",
scanPlaceholder: "Skano ose shkruaj numrin e biletës…",
laneEntry: "Hyrje",
laneExit: "Dalje",
open: "Hap",
occupancy: "Prania",
occUnavailable: "zënia e padisponueshme",