Files
parking_solution/wiki/concepts/lane-presence-and-anpr-entry.md
T
julian 411572511d docs(camera): lane presence + ANPR subscriber-entry bridge design
Captures this session's back-and-forth as a new concept page
[[lane-presence-and-anpr-entry]] and cross-links it:

- BUILT: advisory lane busy/free booth lights (LaneStatus + WS), with the
  measured camera limits behind the 30s timeout (no leave signal; movement-
  driven re-fire; notificationRecurrence locked to "beginning" — ISAPI flip
  silently reverts).
- PLANNED: the ANPR "bridge" — explicitly a small apps/server HANDLER (~40
  lines), NOT a new service/container. On a camera vehicle event: snapshot ->
  ANPR -> high-confidence match -> debounce -> emitRead{kind:"plate"}, then
  the existing subscription match/dispatch/gate admits the subscriber. Both
  directions, opt-in (config.anpr), plate never the sole authority.
- Records the decisions (high confidence floor, debounce-for-correctness)
  and the REJECTED ideas (continuous livestream / per-car queue tracking /
  make-model) with why, plus the open hardware question (booth-PC test).

Updates subscription.md (plate matching is built; the live source is this
bridge) and lpr-camera.md (the two consumers of the vehicle event).

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-22 19:12:49 +02:00

115 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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).