Skip to content

Camera Adapter Architecture

This document defines the pluggable camera adapter pattern, design decisions, and vendor-specific considerations for both edge collectors and the central ingest service.

Document Status: Living Document Last Updated: 2025-12-30


Table of Contents

  1. Adapter Pattern Overview
  2. Dual-Mode Architecture
  3. Design Decisions
  4. Detection Normalization
  5. Vendor Implementations
  6. Vendor Routing (Push Mode)
  7. Error Handling Strategy
  8. Adding New Vendors

1. Adapter Pattern Overview

The system uses a pluggable adapter pattern to support multiple camera vendors. Adapters are shared between:

  • Edge Collector - Runs on Raspberry Pi at each site (supports pull AND push from cameras)
  • Central Ingest Service - Fallback for sites without edge devices (push only)

Each adapter:

  • Handles vendor-specific protocols and authentication
  • Supports both pull mode (adapter polls camera) and push mode (camera pushes to adapter)
  • Normalizes detections to a common Detection format
  • Passes normalized detections to the appropriate buffer/forwarder

Implementation Details: See Camera Adapters PRP for the abstract base class definition, dataclass schemas, and factory patterns.


2. Dual-Mode Architecture

Adapters support two detection delivery modes. Both modes can be used on the edge collector; the central ingest service uses push mode only.

Pull Mode (Edge Initiates)

Edge adapter polls cameras for new detections. Preferred for edge deployments.

┌──────────────┐       HTTP GET        ┌──────────────┐
│    Camera    │◄──────────────────────│    Edge      │
│              │   (adapter polls)     │   Adapter    │
│              │──────────────────────►│   (pull)     │
└──────────────┘      Response         └──────────────┘

Benefits: - Edge controls connection timing and rate - No ports to open on camera or edge (outbound only from edge) - Works behind NAT without configuration - Simpler error handling (retry on next poll)

Use when: Edge device is deployed and camera supports REST API polling.

Push Mode (Camera Initiates)

Camera pushes detections to adapter via webhook/stream.

┌──────────────┐      HTTP POST        ┌──────────────┐
│    Camera    │──────────────────────►│    Edge or   │
│              │   (camera webhook)    │   Central    │
│              │                       │   Adapter    │
└──────────────┘   /webhook/{vendor}   └──────────────┘

Benefits: - Near real-time detection delivery (no polling interval) - Lower resource usage (no continuous polling) - Required for some camera models that only support push

Use when: - Camera only supports webhook/push delivery - Real-time latency is critical - Central ingest service (no edge device at site)

Deployment Options

Option A: Edge Collector (Preferred)

Edge device on LAN handles camera communication, pushes normalized detections to central.

┌─────────────────────────────────────────────────────────────────┐
│                         LOCAL NETWORK                           │
│                                                                 │
│  ┌─────────────┐    PULL or PUSH     ┌─────────────────────┐   │
│  │   Camera    │◄───────────────────►│   Edge Collector    │   │
│  │  (Hikvision)│                     │   (Raspberry Pi)    │   │
│  └─────────────┘                     │                     │   │
│                                      │  ┌───────────────┐  │   │
│  ┌─────────────┐    PULL or PUSH     │  │  Shared       │  │   │
│  │   Camera    │◄───────────────────►│  │  Adapters     │  │   │
│  │   (Unifi)   │                     │  └───────┬───────┘  │   │
│  └─────────────┘                     │          │          │   │
│                                      │          ▼          │   │
│                                      │  ┌───────────────┐  │   │
│                                      │  │ Detection Buffer  │  │   │
│                                      │  │   (SQLite)    │  │   │
│                                      │  └───────┬───────┘  │   │
│                                      └──────────│──────────┘   │
└─────────────────────────────────────────────────│──────────────┘
                                                  │ PUSH (HTTPS)
                                                  │ (edge → central)
                                       ┌─────────────────────┐
                                       │   Central Server    │
                                       │   (Main App)        │
                                       └─────────────────────┘

Benefits: - Edge controls secure transmission over internet - No ports/VPN/tunnels required (edge initiates outbound to central) - Central server receives normalized detections only - Local buffering survives network outages - Supports both pull and push from cameras

Option B: Central Ingest Service (Fallback)

For sites that cannot deploy an edge device, cameras push directly to central.

┌─────────────────────────────────────────────────────────────────┐
│                          INTERNET                               │
│                                                                 │
│  ┌─────────────┐         PUSH          ┌─────────────────────┐  │
│  │   Camera    │──────────────────────►│  Central Ingest     │  │
│  │  (Hikvision)│  POST /webhook/hik    │  Service            │  │
│  └─────────────┘                       │                     │  │
│                                        │  ┌───────────────┐  │  │
│  ┌─────────────┐         PUSH          │  │  Shared       │  │  │
│  │   Camera    │──────────────────────►│  │  Adapters     │  │  │
│  │   (Unifi)   │  POST /webhook/unifi  │  └───────┬───────┘  │  │
│  └─────────────┘                       │          │          │  │
│                                        │          ▼          │  │
│                                        │  ┌───────────────┐  │  │
│                                        │  │  Forwarder    │──┼──►  Central
│                                        │  │  (internal)   │  │    Main App
│                                        │  └───────────────┘  │
│                                        └─────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

Trade-offs: - Requires exposing ingest service to internet - Camera must support webhook/push - No local buffering (detections lost if central unreachable) - Same adapter code as edge (shared codebase)

Unified Ingest Service

A single ingest service supports both edge and central deployments. Adapters live within the ingest package:

Status: Proposed structure. Implementation tracked in Phase 5 (Edge Collector Core).

shared/
├── ingest/                # Unified ingest service
│   ├── adapters/          # Camera adapters (pull + push)
│   │   ├── base.py
│   │   ├── hikvision.py
│   │   └── unifi.py
│   ├── normalizer.py      # Detection normalization
│   ├── receiver.py        # Webhook endpoints (push mode)
│   ├── buffer.py          # SQLite buffer (edge mode)
│   ├── uploader.py        # HTTPS client to central
│   ├── config.py          # Layered config loader
│   └── main.py            # Entry point (--mode edge|central)
└── schemas/
    └── detection.py       # Detection dataclass

deploy/
├── edge/                  # Edge deployment artifacts
│   ├── install.sh         # Deployment script for Pi
│   ├── systemd/
│   └── config/
└── central-ingest/        # Central ingest deployment
    ├── docker-compose.yml
    └── config/

Architecture: See Ingest Service for deployment modes and configuration.


3. Design Decisions

Why Adapter Pattern?

Problem: Different camera vendors use different protocols, authentication methods, and detection formats.

Decision: Use abstract adapter pattern with vendor-specific implementations.

Rationale: - Isolates vendor-specific code from core logic - Adding new vendors doesn't modify existing code - Enables testing with mock adapters - Clear separation of concerns

Why Support Both Pull and Push Modes?

Problem: Different deployment scenarios require different detection delivery methods.

Decision: Each adapter implements both pull_events() and parse_push_event() methods.

Rationale: - Pull mode preferred for edge - Edge controls timing, no ports to open, works behind NAT - Push mode required for some cameras - Some vendors only support webhook delivery - Push mode for central fallback - Sites without edge devices need direct camera-to-central path - Shared code - Same adapters work in edge and central ingest service

Mode Edge Central Ingest
Pull Yes (preferred) No
Push Yes (when needed) Yes (only option)

Why Normalize Before Central?

Problem: Central server would need to understand all vendor formats.

Decision: Normalize detections to common format before sending to central main app.

Rationale: - Central main app has one consistent data model - Edge/ingest handles vendor quirks locally - Simpler central API - Vendor changes don't affect central main app

Why Async I/O?

Problem: Multiple cameras may send detections simultaneously; blocking I/O would bottleneck.

Decision: Use async/await throughout adapter code.

Rationale: - Handle multiple camera connections concurrently - Non-blocking detection processing - Better resource utilization on Pi - Natural fit for event-driven patterns


4. Detection Normalization

All adapters must normalize vendor detections to a common Detection format before passing to the Detection Buffer.

Required vs Optional Fields

Field Required Description
detection_id Yes Unique identifier (from camera or UUID)
timestamp Yes Detection time (UTC)
camera.id Yes Camera identifier
camera.mac Yes MAC address for deduplication
plate.text Yes Normalized plate text (uppercase, no spaces)
plate.confidence Yes OCR confidence (0.0-1.0)
plate.region No Plate region/country
vehicle.direction No "entering", "exiting", or "unknown"
vehicle.vehicle_type No "car", "truck", "motorcycle", "bus", "van"
vehicle.color No Vehicle color
vehicle.brand No Vehicle make (e.g., "Toyota", "Ford")
vehicle.model No Vehicle model (e.g., "Camry", "F-150")
images.full_scene No Full frame JPEG
images.plate_crop No Cropped plate JPEG

Normalization Rules

Plate Text: - Convert to uppercase - Remove spaces, dashes, dots - Preserve alphanumeric characters only - Example: "ABC-1234" → "ABC1234"

MAC Address: - Lowercase, no separators - Example: "00:1A:2B:3C:4D:5E" → "001a2b3c4d5e"

Timestamps: - Convert to UTC datetime - Handle vendor-specific formats - Account for camera timezone configuration

Direction Values: | Vendor Value | Normalized | |--------------|------------| | "in", "entry", "entering", "approach" | "entering" | | "out", "exit", "exiting", "leaving" | "exiting" | | "unknown", "", None | "unknown" |

Vehicle Types: | Vendor Value | Normalized | |--------------|------------| | "car", "sedan", "suv", "automobile" | "car" | | "truck", "lorry", "pickup" | "truck" | | "motorcycle", "motorbike", "bike" | "motorcycle" | | "bus", "coach" | "bus" | | "van", "minivan" | "van" |

Implementation Details: See Camera Adapters PRP for normalization functions and Detection dataclass.


5. Vendor Implementations

Each vendor adapter implements both pull and push modes.

5.1 Hikvision ISAPI

Authentication: HTTP Digest Auth

Mode Protocol Endpoint
Pull HTTP GET /ISAPI/Traffic/channels/{id}/vehicleDetect/plates
Push HTTP POST (webhook) Camera configured to POST to edge/central

Pull Mode: - Poll endpoint returns recent detections as XML - Adapter tracks last detection ID to avoid duplicates - Polling interval configurable (default 2 seconds) - Images included in response (base64 in XML)

Push Mode: - Camera sends EventNotificationAlert XML on detection - Multipart payload includes images - Requires camera webhook URL configuration

Key Characteristics: - Direct camera connection (no NVR required) - Images embedded in event XML (base64) - Rich vehicle metadata (direction, type, color, brand, model) - Timestamps in camera local time

Known Quirks: - Uses HTTP Digest authentication (not Basic) - Some models send duplicate detections within 1 second - Requires partner account for ISAPI documentation

Configuration Requirements: - Host IP/hostname - HTTP port (default 80) - Username and password (Digest auth) - Camera channel ID - Timezone (for timestamp conversion) - Mode: pull or push - Webhook URL (push mode only)

5.2 Unifi Protect

Authentication: Bearer token from Unifi Console

Mode Protocol Endpoint
Pull HTTP GET /proxy/protect/api/events?type=licensePlate
Push WebSocket Camera/NVR pushes via WebSocket stream

Pull Mode: - Query events API with timestamp filter - Adapter tracks last detection timestamp - Images fetched separately via /proxy/protect/api/thumbnails/{id} - Polling interval configurable (default 5 seconds)

Push Mode: - Subscribe to WebSocket event stream from NVR - Real-time detection delivery - Images still fetched via separate API call

Key Characteristics: - Detections come through NVR (not direct to camera) - Images fetched separately via API - Self-signed SSL on console

Known Quirks: - Uses self-signed certificates (must disable verification) - WebSocket may disconnect; needs reconnection logic - Rate limiting on image downloads - Limited vehicle metadata compared to Hikvision - Detections aggregated at NVR level

Configuration Requirements: - Unifi Console URL - API key or bearer token - Camera Protect IDs (from bootstrap API) - Mode: pull or push

Implementation Details: See Camera Adapters PRP for HikvisionAdapter and UnifiAdapter implementations.


6. Vendor Routing (Push Mode)

When receiving pushed detections (webhook), the system must identify the camera vendor to route to the correct adapter for parsing.

Vendor is specified in the webhook URL path:

POST /api/v1/ingest/hikvision   → HikvisionAdapter.parse_push_event()
POST /api/v1/ingest/unifi       → UnifiAdapter.parse_push_event()
POST /api/v1/ingest/{vendor}    → AdapterFactory.get(vendor).parse_push_event()

Why this works: - When configuring camera webhook URL, the vendor is known - No payload inspection needed before routing - Easy to add new vendors - Clear in logs/metrics which vendor sent what

Camera Configuration

Configure camera webhook URL to include vendor:

Vendor Edge Webhook URL
Hikvision http://edge-ip:8080/api/v1/ingest/hikvision
Unifi http://edge-ip:8080/api/v1/ingest/unifi

For central ingest service (internet-facing):

Vendor Central Ingest Webhook URL
Hikvision https://ingest.example.com/api/v1/ingest/hikvision?key=xxx
Unifi https://ingest.example.com/api/v1/ingest/unifi?key=xxx

Fallback: Auto-Detection

For /api/v1/ingest/auto endpoint, inspect payload/headers to identify vendor:

Vendor Detection Signal
Hikvision Content-Type: application/xml + <EventNotificationAlert> root element
Unifi X-Unifi-* headers or JSON with Unifi-specific fields

Auto-detection is provided for convenience but explicit routing is preferred.

Implementation Details: See Camera Adapters PRP for webhook receiver and routing patterns.


7. Error Handling Strategy

Connection Failures

Strategy: Automatic reconnection with exponential backoff.

Behavior: 1. On disconnect, mark adapter as OFFLINE 2. Wait with exponential backoff (1s → 2s → 4s → ... → 60s max) 3. Add jitter to prevent thundering herd 4. Log each reconnection attempt 5. Reset backoff on successful reconnection

Detection Parsing Errors

Strategy: Log and continue; never crash on malformed detections.

Behavior: 1. Catch parsing exceptions per-detection 2. Log warning with vendor and error details 3. Track error rate for alerting 4. Continue processing next detection 5. Return None for unparseable detections

Image Fetch Errors

Strategy: Images are optional; continue without them.

Behavior: 1. Attempt image fetch with timeout 2. Retry once on transient errors 3. Log failure but don't fail detection 4. Detection stored without images if fetch fails

Implementation Details: See Camera Adapters PRP for reconnection patterns and error handling code.


8. Adding New Vendors

Requirements Checklist

Before implementing a new vendor adapter:

  1. [ ] Obtain vendor API documentation
  2. [ ] Understand authentication method
  3. [ ] Identify pull mechanism (REST API endpoint for polling)
  4. [ ] Identify push mechanism (webhook format, if supported)
  5. [ ] Determine image delivery (inline, separate fetch, RTSP)
  6. [ ] Test with physical camera or simulator
  7. [ ] Document vendor quirks and limitations

Implementation Steps

  1. Create adapter class extending CameraAdapter
  2. Implement pull mode: pull_events() async generator
  3. Implement push mode: parse_push_event() method
  4. Add normalization for vendor-specific values
  5. Implement reconnection logic (for pull mode)
  6. Add configuration schema
  7. Register vendor in adapter factory
  8. Add vendor routing for push mode (/api/v1/ingest/{vendor})
  9. Write unit tests with mocked responses
  10. Write integration tests (if test camera available)
  11. Document in this file and update PRP

Configuration Registration

New adapters are registered in the adapter factory. The factory maps adapter type strings to adapter classes and is used by both edge collector and central ingest service.

Implementation Details: See Camera Adapters PRP for adapter template, factory pattern, and test examples.


Appendix: Vendor Comparison

Feature Hikvision Unifi
Pull Mode Yes (REST API) Yes (REST API)
Push Mode Yes (webhook) Yes (WebSocket)
Auth HTTP Digest Bearer Token
Images in Event Yes (base64) No (separate fetch)
Vehicle Metadata Rich (direction, type, color, brand, model) Limited (direction only)
Multi-camera Per-camera connection Via NVR
Reliability Good Good
Documentation Good (partner only) Community wiki


Document Maintainer: Architecture Team Review Cycle: Update when adding vendors