--- type: concept tags: [parking, camera, anpr, subscription, lane, vision] sources: [] updated: 2026-06-22 status: open --- # Lane Presence & ANPR Subscriber Entry Two related things a camera's vehicle detection feeds, worked out over a long field session (2026-06-22, see [[lpr-camera]] for the camera-side saga + the corrected "it was the undrawn detection area" conclusion): 1. **Lane busy/free** — BUILT. An advisory barrier light on the booth. 2. **ANPR subscriber entry** — PLANNED. A subscriber's plate, read from the lane camera, drives their entry/exit through the EXISTING [[subscription]] flow. The "bridge" below. The camera (Hik `DS-2CD1043G2-LIU`) only emits `VMD` events with `eventState=active` and a `targetType` of `vehicle`/`human` — a coarse **presence** signal, never an identity. Everything here is built on that, and on the camera's hard limits. ## What the camera actually gives us (measured) - **Push only, no poll.** No ISAPI endpoint reports "is a car in the zone now"; the only live source is the event push. We tried to force a steadier signal by flipping `notificationRecurrence` `beginning → recurring` via ISAPI — the firmware **accepts the PUT but silently reverts** (locked to `beginning` on this value line). So: notify-once-at-motion-start is all we get. - **No leave signal.** The camera never sends an `inactive`/end event. Confirmed by config (`notificationRecurrence: beginning`) AND a controlled in/out test. - **Re-fire is MOVEMENT-driven, not steady.** Controlled test (call out enter/leave, correlate to the event log): while a car MOVES, `active` repeats ~1–3 s apart; while it sits MOTIONLESS, gaps stretch to ~15–25 s. Crucially the camera has **~no dwell lag** — it goes silent within ~1 s of the car leaving (last event 16:15:17 vs car-left ~16:15:30). ## 1. Lane busy/free (BUILT) `apps/server/src/lane-status.ts` (`LaneStatus`) + the hik-alarm handler + the booth WS. A `vehicle` `active` event on a camera bound to entry/exit marks THAT lane busy and arms an auto-clear timer; the booth shows two barrier lights beside the scan input (green=free, red=busy). **Advisory only — gates nothing** (never blocks a ticket or opens a barrier; the standing rule). - **"Free" is timeout-driven** (no leave signal). The TTL must exceed the still-car gap (~25 s) or a parked car flickers free — so `LANE_BUSY_TTL_MS` default is **30 s** (started at a guessed 90 s, briefly 5 s, then set to 30 s from the measured data). The camera's lack of dwell lag means 30 s also clears promptly after departure. - A `both`-direction camera marks both lanes. Pushed over the existing `/api/ws` (kind `lane-status`). ## 2. ANPR subscriber entry — THE BRIDGE (PLANNED, not built) > **"Bridge" = a HANDLER FUNCTION in `apps/server` (≈40 lines, e.g. `anpr-entry.ts`). NOT a new > service / container / app.** It is in-process glue that calls things that ALREADY exist. The goal (narrowed deliberately — see Rejected below): **a subscriber's plate, read by the lane camera, admits them through the same gated flow a QR/card scan uses.** Scope was cut to subscribers ONLY — no queue segmentation, no per-car tracking, no make/model, no ticket-button gating. Almost everything already exists; the bridge is the one missing wire: | Piece | Status | | --- | --- | | Camera vehicle event | ✅ [[lpr-camera]] (hik-alarm) | | Pull a snapshot | ✅ `snapshot.ts` (`captureSnapshot`) | | Read the plate | ✅ [[opencv-anpr-service]] `/analyze` (~50 ms on the DEV PC; appliance TBD) | | Match a plate → subscriber | ✅ `subscription-flow.ts` `match()` + `subscription_plates` (`via:"plate"`) | | Plate read → gated entry/exit | ✅ `read-dispatch.ts` + SubscriptionFlow (active/window/blocklist/car-count) | | **Emit the plate onto the read bus** | ❌ **the bridge** — today the snapshot ANPR only RECORDS the plate as advisory telemetry; it does NOT `emitRead`. hik-alarm.ts literally says "NOT a DeviceReadEvent yet". | **The bridge logic:** on a camera `vehicle`/`active` event from an **opt-in** camera (`config.anpr`), snapshot → `vision.analyze` → if a plate clears a **HIGH** confidence floor → **debounce** → emit `DeviceReadEvent{kind:"plate", value, deviceId}`. The existing dispatcher + flow do the rest. ### Decisions settled with the user (2026-06-22) - **Both directions.** Entry- and exit-bound cameras both work; the dispatcher infers the verb from the camera's bound relay direction (an entry camera → entry, exit → exit). No per-read inference. - **High confidence required.** A barrier-driving read needs a stricter bar than the advisory-record floor — a NEW `VISION_ENTRY_MIN_CONFIDENCE` (≈0.85) distinct from `VISION_MIN_CONFIDENCE`. (The subscriber still holds their card/QR, so a near-miss read just falls back to that.) - **Opt-in** per camera (`config.anpr`), so a site that didn't ask for plate-entry is unaffected. - **Debounce is REQUIRED — for correctness, NOT CPU.** The camera re-fires ~1 Hz while a car is present; emitting a read every second would drive REPEAT entries (a fleet sub opens a 2nd occurrence; a single-car sub spams "already inside") or, on an exit camera, repeat exits (`sub.refused.noSession` after the first, and FIFO could phantom-close another occurrence). So the same plate on the same camera within ~10–15 s = ONE credential presentation. - NB: a direction-bound camera does NOT flip entry↔exit on repeat reads (the barrier's direction fixes the verb), so the earlier "flip-flop" fear was wrong — but repeat-same-direction is still bad. Debounce stands. - **Threat-model rule preserved by construction.** A plate is trivially spoofable (print it on paper); it must NEVER be the sole reason a barrier opens. Routing through the existing SubscriptionFlow means the plate is just another credential through the same gate (active / window / blocklist / car-count) — not a bypass. See [[opencv-anpr-service]], [[append-only-event-chain]]. ### To validate (booth-PC test day, ~2026-06-23) The dev-PC ANPR is ~50 ms/frame, but that says little about the hardened **booth appliance** (likely a low-power CPU, possibly 5–15× slower). Real-hardware latency is the open number. The user is bringing the actual booth PC to test on. ## Rejected / out of scope (and why) - **Vision monitors the RTSP livestream continuously** for presence — rejected: rebuilds (worse) what the camera already does (presence), pegs the appliance CPU 24/7, and the leave-detection heuristic is no cleaner than a 30 s timeout. - **Vision polls every ~1 s to track THE car, segment a queue, count cars, read make/model** — a genuinely harder need (bumper-to-bumper cars the camera can't tell apart). Set ASIDE, not dismissed: it needs a general vehicle DETECTOR ([[opencv-anpr-service]] is plate-only today) + "same-car-vs-new" logic + make/model (a third, weak, heavy model), and its viability is **gated on appliance inference budget we cannot measure on the dev PC**. Revisit only if real traffic + hardware justify it. The vision feasibility was checked: `fast_alpr` live + ready; plate-only; no vehicle/presence detector exists yet. - **Gate the entry ticket button on lane-busy** (don't print when no car) — deferred. The lane-busy signal supports it, but it gates a PHYSICAL action so it must FAIL-OPEN (allow when presence is unknown). Parked pending the user's real goal (anti-spam print vs one-ticket-per-car).