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
This commit is contained in:
2026-06-22 19:12:49 +02:00
parent a2bdf99db2
commit 411572511d
5 changed files with 147 additions and 7 deletions
+6 -4
View File
@@ -82,10 +82,12 @@ Center**, then **Alarm Settings → Alarm Server**, makes the camera **HTTP-POST
sends** (inspect via `GET /api/events` or the server log) before wiring it to the read bus.
- **Not yet a barrier trigger.** It records + breadcrumbs only; it does NOT emit a `DeviceReadEvent`
or open anything. A plate read is **advisory, never the sole reason** a barrier opens
([[append-only-event-chain]], [[opencv-anpr-service]]) — the entry/exit wiring is a deliberate
next step once the real payload is known. If the camera emits its own plate (`<plateNumber>`), we
can use it as an advisory read directly; otherwise the server hands the attached/pulled frame to
the [[opencv-anpr-service|vision service]] for ANPR.
([[append-only-event-chain]], [[opencv-anpr-service]]). Two consumers were since designed off this
same vehicle event — see **[[lane-presence-and-anpr-entry]]**: (a) BUILT — advisory lane busy/free
booth lights; (b) PLANNED — the ANPR "bridge" that snapshots → ANPR → emits a `kind:"plate"` read
for a SUBSCRIBER match through the existing gated flow (a small `apps/server` handler, not a
service). If the camera ever emits its own `<plateNumber>` we'd use it directly; this `DS-2CD1043G2`
does not, so the server pulls the frame and hands it to the [[opencv-anpr-service|vision service]].
### Gotchas learned the hard way (2026-06-22 field session)
+7 -3
View File
@@ -258,9 +258,13 @@ LPR/ANPR plate identity** (the plate binding below):
number. The GEE readers are combo QR + RFID (ID/IC/NFC), so the same device captures both. A
Wiegand-out reader keeps a future autonomous path open ([[entry-exit-readers]]); the
[[dingtian-relay]] has no onboard card list.
- **Plate (LPR/ANPR) — NOT YET IMPLEMENTED.** When plate-bound (below), a matching plate read is an
accepted identity too. The vision/ANPR service that produces plate reads is future work
([[opencv-anpr-service]] / [[lpr-camera]]); until it exists, plate binding has no live source.
- **Plate (LPR/ANPR) — matching is BUILT, the live SOURCE is the one missing wire.** When plate-bound
(below), a matching plate read is an accepted identity — and `subscription-flow.ts` `match()` +
`read-dispatch.ts` already handle a `via:"plate"` read end to end (gate + entry/exit). What's
missing is the thing that EMITS a plate read from the lane camera: the **ANPR "bridge"** (a small
handler in `apps/server`, not a new service) that snapshots on a camera vehicle event, runs
[[opencv-anpr-service|ANPR]], and on a high-confidence match emits the plate onto the read bus. PLANNED,
scoped to subscribers only. See [[lane-presence-and-anpr-entry]] for the full design + decisions.
Both feed the host as a reader event whose `source` is `wiegand` / `qr` (the `IdentitySource`
already in the model) and whose value is the credential id.