Files
parking_solution/wiki/entities/lpr-camera.md
T
julian 6133923094 feat(camera): Hikvision Alarm Server event-push ingress (discovery-first)
Newer Hik firmware can PUSH events to us: Event -> Smart/VCA with
"Detection Target: Human/Vehicle" + Notify Surveillance Center + Alarm
Settings -> Alarm Server makes the camera HTTP-POST an
EventNotificationAlert on each detection.

- New POST /api/devices/hikvision/:deviceId/event (routes/hikvision-alarm.ts):
  same machine-push pattern as the Dingtian Input Link — source-IP guarded
  + optional HTTP Digest, not behind the SPA cookie/CSRF.
- Discovery-first / permissive: a wildcard content-type parser accepts ANY
  body as raw bytes (event XML, multipart+JPEG, or JSON — Hik varies by
  firmware), records it verbatim as a kind:"alarm" device_event, and
  best-effort extracts eventType/target/plate/dateTime/channelID for the
  summary + a loud log line. The point is to SEE exactly what a camera
  sends before wiring it further.
- hikvision driver gains alarmPushEnabled + pushUser/pushPassword config and
  pushesToBackend:true (setup offers the backend push IP).
- NOT yet a barrier trigger / DeviceReadEvent — records only. A plate read
  is advisory, never the sole reason a barrier opens; the read-bus/ANPR
  wiring is a deliberate next step once the real payload is known.

Tests: hikvision-alarm.test.ts (6: vehicle XML summary, ANPR plate, raw
JSON, wrong-IP 404, disabled 404, unknown-device 404). server 109/109;
build+lint 14/14. Wiki: lpr-camera.md + log.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-22 10:02:35 +02:00

5.8 KiB
Raw Blame History

type, tags, sources, updated
type tags sources updated
entity
parking
hardware
readers
offline-first
parking-system-architecture
2026-06-15

LPR Camera

License-plate-recognition camera. For casual/transient vehicles, the plate acts as ticket + an independent record. (See parking-system-architecture §8, §9.)

Superseded direction (2026-06-15): recognition now runs host-side on snapshots from ordinary Hikvision/Dahua cameras via the opencv-anpr-service, not on a dedicated edge-AI LPR camera — see vision-service. The edge-AI camera below is kept as the original assumption / a fallback option, but is no longer the planned path. The host-side service also does vehicle verification (anti-plate-spoofing), which an edge-LPR camera does not.

  • Edge AI (original assumption): recognition runs on-device, so it keeps working with no internet — fits offline-first.
  • It's a host-side identity source: only the host sees the read; the host decides and commands the relay open (the uhppote-controller is demoted to a commanded relay for that lane). See entry-exit-readers.
  • Being host-in-the-loop is good for fraud detection — you get two independent records (the host's signed append-only-event-chain entry + the controller's remote-open event) that should reconcile one-to-one; any mismatch is an anomaly.
  • Mounting: within ~15° of vehicle travel at a controlled chokepoint for best reads.

Snapshot driver (entry/exit fraud-control record)

Separate from edge-AI LPR: the camera driver (packages/devices/src/drivers/camera.ts) does snapshot-on-event — the host pulls a still over HTTP when an entry/exit fires and stores it, referenced from the signed append-only-event-chain entry as an independent record. The camera pulls, it does not push — so it is NOT pushesToBackend and the setup wizard correctly hides the "Backend push IP" field for it (gated on the driver's pushesToBackend flag; only dingtian-relay sets it).

  • Hikvision uses ISAPI: GET /ISAPI/Streaming/channels/<id>/picture (101 = ch1 main stream) with HTTP Digest auth. The "Enable Hikvision-CGI" toggle (Network → Advanced → Integration Protocol) is a different legacy CGI surface — not needed for ISAPI.
  • Dahua uses CGI: GET /cgi-bin/snapshot.cgi?channel=<n> (0-based channel; the wizard's 1-based channel is decremented).

Driver / storage boundary: the driver FETCHES the image bytes (client-side HTTP Digest in drivers/http-digest.ts) and returns them on Snapshot.bytes; storage is the caller's job (the future entry/exit flow stores the bytes + mints a durable imageRef). This keeps the device adapter free of any filesystem/blob-store dependency. healthCheck() is honest — it actually pulls a frame (exercising reachability + auth + path/channel in one shot), not a fake ready/stub.

Verified on hardware (2026-06-15)

A Hikvision unit ("Camera 20", MAC 94:e1:ac:…, Hikvision OUI) at 10.0.10.121, creds admin / admin123 (Digest), TCP 80:

  • Initial curl test confirmed the ISAPI path returns a 2688×1520 JPEG (~306 KB).
  • The real driver (no longer a stub) was then run end to end against it: healthCheck() → ready (pulled a frame), captureSnapshot() → valid image/jpeg, ~322 KB, correct JPEG magic. Digest handshake works through HttpCamera.
  • Reaching it from the WSL dev box required forcing the source address (config.localAddress, threaded into the driver) — see wsl-dev-networking (multi-subnet source-selection trap).

Camera PUSH — "Alarm Server" event notifications (2026-06-22)

Separate from the pull snapshot path above: newer Hikvision firmware can push an event to us. Under Event → Smart/VCA (e.g. line crossing / intrusion / "Vehicle Detection") the unit exposes Detection Target: Human / Vehicle — selecting Vehicle + Notify Surveillance Center, then Alarm Settings → Alarm Server, makes the camera HTTP-POST an EventNotificationAlert to a URL we host on each detection. Same machine-call shape as the dingtian-relay Input Link push — no polling.

  • Ingress: POST /api/devices/hikvision/:deviceId/event (apps/server/src/routes/hikvision-alarm.ts). Source-IP guarded (must come from the device's configured host) + optional HTTP Digest (some firmware can't authenticate the Alarm Server call → source-IP only). NOT behind the SPA cookie/CSRF (it's a device call), exactly like the Dingtian push.
  • Config: added to the hikvision driver — alarmPushEnabled (bool), pushUser/pushPassword (optional Digest). The driver is now pushesToBackend: true, so first-run setup offers the backend push IP. Point the camera's Alarm Server at http://<backend-ip>:<port>/api/devices/hikvision/<deviceId>/event.
  • Discovery-first: the endpoint is permissive — accepts ANY content-type as raw bytes (event XML, multipart-with-JPEG, or JSON; Hik's format varies by model/firmware), records the verbatim body as a kind:"alarm" device_event, and best-effort extracts eventType / target / plate / dateTime / channelID. The point of this first cut is to see exactly what a given camera 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 for ANPR.