feat(anpr): poll snapshots until a confident plate, so auto-exit works
The ANPR bridge took ONE snapshot at the camera's vehicle-alarm instant — but the
alarm fires as the car APPROACHES, so that frame's plate is small/blurry/half-in-
frame and ANPR returns a low-confidence misread ('111'@0.20). The manual test reads
the SAME car at ~100% because by then it's STOPPED at the barrier, well-framed. So
subscriber auto-exit silently never fired (read below the 0.85 floor → ignored).
Fix (the car-stops-at-the-barrier insight): the bridge now PULLS A FRESH FRAME every
ANPR_POLL_MS (1000) and re-runs ANPR until one clears VISION_ENTRY_MIN_CONFIDENCE, or
ANPR_POLL_WINDOW_MS (8000) elapses (drove off / non-subscriber → give up cleanly).
- One loop per camera (#polling set) — the camera's ~1Hz alarm re-fires JOIN the
running loop instead of spawning N concurrent loops.
- Fresh camera.captureSnapshot each tick, NOT captureSnapshotShared (its 1.5s TTL
would re-serve the same bad approach frame).
- Camera-level debounce stamp moved to AFTER a successful emit (suppresses re-fires
for ANPR_DEBOUNCE_MS once we've acted), not before the loop.
VERIFIED on hardware (DS-2CD1047G3H-LIU exit lane): 7 garbage approach frames →
AA890XX@0.999 at the barrier → signed vehicle_exit. Still advisory + fail-soft; a
barrier never opens on a low-confidence read. anpr-entry.test.ts +1 (poll
escalation low→low→high); 169 server tests green. Documented in
lane-presence-and-anpr-entry + the lpr-camera camera-fault writeup.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
type: entity
|
||||
tags: [parking, hardware, readers, offline-first]
|
||||
sources: [parking-system-architecture]
|
||||
updated: 2026-06-26
|
||||
updated: 2026-06-27
|
||||
---
|
||||
|
||||
# LPR Camera
|
||||
@@ -74,6 +74,21 @@ fixes — **don't assume it's a momentary blip**:
|
||||
status); the main encoder was simply never free. A retry loop **cannot** fix this — it just delays
|
||||
the failure.
|
||||
|
||||
> **Sharper finding (2026-06-27, same DS-2CD1047G3H-LIU).** Re-probed `10.0.10.13` directly after a
|
||||
> camera reboot with every web/live-view connection closed. **Sub (102): 10/10 rapid back-to-back
|
||||
> pulls → 200** (~50 ms, ~14.7 KB) — flawless even with NO delay, harder than the live ANPR cadence.
|
||||
> **Main (101): 3/3 → 503 in ~20 ms** — an *instant* reject, not a timeout. So main isn't merely
|
||||
> "saturated/busy" on this model — its snapshot endpoint is **structurally unavailable**; **sub (102)
|
||||
> is mandatory**, not just preferable. SEPARATELY, the camera 503s on **any** stream when its
|
||||
> **connection slots are exhausted** — a human holding the web UI / live-view, or parallel
|
||||
> main-stream experiments, consume slots; a **reboot clears stuck slots**. This was the actual cause
|
||||
> of the **2026-06-27 "subscribers auto-enter but don't auto-exit"** scare: manual main-stream
|
||||
> testing held the exit camera's slots → the system's sub-stream snapshot pulls got "Device Busy" →
|
||||
> the ANPR exit read never got a frame → no auto-exit. **NOT a code/flow bug** — the subscription
|
||||
> exit logic, camera→relay binding, and `stream: "2"` config were all correct (auto-exit/entry pairs
|
||||
> were clean before the test storm and after the reboot). (Also recorded as the LLM memory
|
||||
> `g3h-main-stream-snapshot-503`.)
|
||||
|
||||
**The fix that actually works: snapshot from the SUB stream.** The Hikvision ISAPI channel id is
|
||||
`<channel><stream>` (e.g. ch1 main = `101`, ch1 **sub = `102`**). The driver now has a **`stream`
|
||||
config field** (`1` = main, default for back-compat; `2` = sub). Set the G3H camera to **Sub (02)** in
|
||||
@@ -126,6 +141,58 @@ Center**, then **Alarm Settings → Alarm Server**, makes the camera **HTTP-POST
|
||||
use it directly; this `DS-2CD1043G2`
|
||||
does not, so the server pulls the frame and hands it to the [[opencv-anpr-service|vision service]].
|
||||
|
||||
### "Subscribers auto-enter but don't auto-exit" — a 4-layer CAMERA fault, NOT our code (2026-06-27)
|
||||
|
||||
A long debugging session on the **DS-2CD1047G3H-LIU** exit camera (`10.0.10.13`, exit-lane). The
|
||||
symptom: subscribers (e.g. Caca) auto-entered via ANPR fine but **never auto-exited**. **Every
|
||||
assumption about *our* code was wrong; all four real causes were camera-side.** Method that finally
|
||||
cracked it: a **dumb HTTP sink** (`scratch-camera-sink.py`) the camera's Alarm Server was pointed
|
||||
at, to see — verbatim — what the camera actually sends, independent of our app's parsing/acceptance.
|
||||
|
||||
The wrong turns, and what was actually true:
|
||||
|
||||
1. **Wrong assumption: "the exit camera 503s, so harden the snapshot retry / reduce load."** The 503
|
||||
storm in the data was mostly **manual main-stream testing**: on this G3H, `channels/101/picture`
|
||||
(MAIN) **503s instantly every time** — structurally unavailable, not "busy" — while `102` (SUB)
|
||||
serves 10/10 rapid pulls cleanly. AND the camera **503s on *any* stream when its connection slots
|
||||
are exhausted** (a held web UI / live-view, parallel experiments); a **reboot clears stuck slots**.
|
||||
So the snapshot retry/flow code was fine. See [[#HTTP 503 "Device Busy"]] + the
|
||||
`g3h-main-stream-snapshot-503` memory. (The exit/subscription FLOW logic, camera→relay binding,
|
||||
and `stream:"2"` config were all correct the whole time — verified: clean entry/exit pairs before
|
||||
the test storm, and after the reboot.)
|
||||
|
||||
2. **The actual blocker #1 — the exit camera never POSTed at all.** `alarmPushEnabled=true` in our
|
||||
config, but `10.0.10.13` had sent **ZERO** alarms ever (entry cam `.12`: 1126). The sink received
|
||||
nothing from `.13`; our `/event` endpoint logged no rejections either → the camera wasn't sending.
|
||||
Cause found in the camera's own **Diagnose Information** dump: **`Main Db is broken` /
|
||||
`db_restore failed` / `Going to reset cfg`** — the camera's internal config DB (`ipc_db`) was
|
||||
**CORRUPT**, plus repeated reboots. A broken config DB means the event→linkage→push pipeline can't
|
||||
reliably read its own config, so it silently never POSTs. **Fix: factory-reset the camera** (rebuilds
|
||||
`ipc_db`), then reconfigure. (If corruption returns after a clean reset → failing flash → RMA.)
|
||||
|
||||
3. **Wrong assumption: "missing gateway/DNS blocks the push."** A documented Hikvision note says a
|
||||
gateway is needed even same-subnet — but here it was **inverted**: the WORKING cam `.12` has NO
|
||||
gateway/DNS; the broken `.13` HAD both. Red herring. Gateway/DNS was not the cause.
|
||||
|
||||
4. **The actual blocker #2 — after reset, the camera pushed PLAIN MOTION, not vehicle.** Post-reset
|
||||
`.13` POSTed `<eventType>VMD</eventType>` with **no target tag**. The backend gates on
|
||||
`target == "vehicle"` (`hikvision-alarm.ts` `isVehicleActive`, matches `<targetType>` /
|
||||
`<detectionTarget>` / `<objectType>`), so a plain-motion push is **ignored** → bridge never fires.
|
||||
The working `.12` sends `eventType=VMD` **with `target=vehicle`**. The difference is the AcuSense
|
||||
**Detection-Target = Vehicle** filter ON the Motion event — defaulted OFF after factory reset.
|
||||
**Fix: enable Vehicle target classification** on `.13`'s Motion Detection. Confirmed live: a real
|
||||
drive-through then POSTed `<eventType>VMD</eventType> … <targetType>vehicle</targetType>` — exactly
|
||||
what the backend needs. (So "VMD/Motion" IS the right event for this camera class; it's the *target
|
||||
filter* that matters, not switching to a different event type.)
|
||||
|
||||
**Takeaways:** (a) a camera that's silently not-pushing looks identical to "fine" in our logs — the
|
||||
sink-to-prove-it-sends method is the fastest disambiguator; flagged as an observability gap (a
|
||||
`alarmPushEnabled=true` camera with 0 pushes ever should be a surfaced condition, like the
|
||||
[[device-status-monitoring|reader-liveness]] fix). (b) For ANPR the camera must send a **vehicle
|
||||
`targetType`** — verify the push body, not just the UI toggles. (c) Hikvision config-DB corruption
|
||||
is real; factory reset is the cure. None of this was a code bug. See
|
||||
[[lane-presence-and-anpr-entry]] for the push→bridge→exit path.
|
||||
|
||||
### Gotchas learned the hard way (2026-06-22 field session)
|
||||
|
||||
Several traps surfaced trying to get a real camera to push. In order of how long each cost:
|
||||
|
||||
Reference in New Issue
Block a user