No description
  • HTML 58.4%
  • JavaScript 21%
  • Python 15.3%
  • CSS 4.3%
  • Shell 0.7%
  • Other 0.3%
Find a file
2026-06-15 22:15:30 +00:00
backup fix: correct COPY paths in backup Dockerfile (context is ./backup) 2026-06-15 21:35:47 +00:00
db/init feat: Phase 2 — performance hardening 2026-06-07 23:29:57 +00:00
graphify-out fix: graphify community labels now properly named (175 nodes, 29 communities) 2026-06-09 16:12:47 +00:00
ingestion_worker merge: origin/test/e2e-validation into main (resolved .gitignore conflict) 2026-06-15 21:34:57 +00:00
proxy Initial backup to Forgejo 2025-12-04 04:06:16 +00:00
web_app fix: make all fetchone()[0] calls null-safe 2026-06-15 22:15:30 +00:00
.dockerignore chore: production hardening - safe fixes 2026-06-07 22:53:28 +00:00
.env.example chore: production hardening - safe fixes 2026-06-07 22:53:28 +00:00
.gitignore merge: origin/test/e2e-validation into main (resolved .gitignore conflict) 2026-06-15 21:34:57 +00:00
AGENTS.md feat: integrate Graphify knowledge graph for codebase navigation 2026-06-09 15:46:09 +00:00
docker-compose.yml feat: Phase 5 — System Health dashboard & automated DB backups 2026-06-09 02:54:51 +00:00
README.md feat: Phase 3.3 — polish, performance & docs 2026-06-08 03:50:34 +00:00

OIEP LPR Platform — Deployment Guide

Version: 3.0 (UI Redesign & Feature Expansion) Date: June 8, 2026 Target OS: Linux (Ubuntu/Debian recommended)

This guide provides instructions for deploying the Open-Source IoT Event Platform (OIEP) — a real-time license plate recognition monitoring dashboard with analytics, watchlists, and data export.


Overview

The OIEP Platform ingests LPR (License Plate Recognition) events from Frigate via MQTT, stores them in TimescaleDB, and provides a rich web interface for searching, monitoring, and analyzing vehicle detection data.

What's New in v3.0

  • Complete UI redesign — dark monitoring theme with collapsible sidebar navigation
  • 6 application pages: Search, Dashboard, Live Feed, Watchlist, Plate Details, Config
  • 19 API endpoints — full REST API with pagination, filtering, and caching
  • Real-time SSE feed — live event stream using Server-Sent Events
  • Watchlist with CRUD — persistent watchlist backed by database (survives restarts)
  • Interactive charts — confidence distribution, weekly trends, sparklines (Chart.js)
  • Data export — CSV and JSON export of search results
  • Plate heatmap — 7-day x 24-hour activity grid per plate
  • Flask-Caching — response caching for dashboard, search, and live endpoints
  • Bootstrap 5.3.3 + Bootstrap Icons — modern responsive frontend
  • 7 modular JS files — search, live-feed, plate-detail, watchlist, export, common, charts

Screenshots

Dashboard Live Feed Search
Dashboard Live Feed Search
Watchlist Plate Details Config
Watchlist Plate Details Config

(Screenshot files are placeholders — add actual screenshots to docs/screenshots/ directory.)


1. Prerequisites

Ensure the deployment server meets the following requirements:

  • Operating System: Linux (e.g. Ubuntu 22.04 LTS)
  • Docker Engine: v24.0+
  • Docker Compose: v2.0+
  • Git: Installed
  • MQTT Broker: Have the IP + credentials ready (Frigate embedded broker or Mosquitto)

2. Deployment Steps

Step 2.1 — Clone the Repository

cd ~
git clone <YOUR_FORGEJO_REPO_URL> lpr-db-app
cd lpr-db-app

Replace <YOUR_FORGEJO_REPO_URL> with your actual clone URL.


Step 2.2 — Configure Environment Variables

Copy the example environment file and edit it:

cp .env.example .env
nano .env

Update the values in .env:

# Database
DB_POSTGRES_USER=oiep_user
DB_POSTGRES_PASSWORD=CHANGE_ME          # <-- CHANGE THIS
DB_POSTGRES_DB=oiep_db

# MQTT Broker
MQTT_BROKER_HOST=10.10.30.253           # <-- CHANGE THIS (Frigate IP)
MQTT_BROKER_PORT=1883
MQTT_USERNAME=frigate_user              # <-- CHANGE THIS
MQTT_PASSWORD=CHANGE_ME                 # <-- CHANGE THIS

# Timezone
TZ=America/Winnipeg

Save & exit: Ctrl+O, Enter, Ctrl+X.

Security Note: The .env file is gitignored and should never be committed to version control.


Step 2.3 — Build and Launch the Stack

docker compose up -d --build
  • --build → forces rebuild
  • -d → detached mode

3. Verification

Step 3.1 — Ensure Containers Are Running

docker ps

Expected:

  • dbhealthy
  • ingestion_worker, web_app, proxyUp (healthy)

Step 3.2 — Check Health Status

docker inspect --format='{{.State.Health.Status}}' lpr-db-app-db-1
docker inspect --format='{{.State.Health.Status}}' lpr-db-app-web_app-1

Both should report healthy.


Step 3.3 — Confirm Ingestion Worker Connected to MQTT

docker logs -f lpr-db-app-ingestion_worker-1

Expected output:

2026-06-07T12:00:00+0000 [INFO] Connected to MQTT Broker (Code: 0)
2026-06-07T12:00:00+0000 [INFO] Configuration loaded/updated. Refreshing subscriptions...

Press Ctrl+C to exit.


Step 3.4 — Access the Web Interface

Navigate to:

http://<YOUR_SERVER_IP>/

You should see the OIEP Platform interface with the dark monitoring theme and sidebar navigation.


4. Configuration & Maintenance

Dynamic Configuration (No Restart Required)

Visit:

http://<YOUR_SERVER_IP>/config

You can update:

  • MQTT topic
  • JSON validation schema

Click Update Configuration — the ingestion worker reloads automatically within ~10 seconds.


Viewing Logs

docker logs -f lpr-db-app-ingestion_worker-1
docker logs -f lpr-db-app-web_app-1
docker logs -f lpr-db-app-db-1

Logs use structured format: YYYY-MM-DDTHH:MM:SS [LEVEL] Message

The web app logs each request: METHOD /path STATUS RESPONSE_TIME


Searching Plates

The search page supports:

  • Wildcard patterns: Use % as a wildcard (e.g., %885% matches any plate containing "885")
  • Date range filters: Start and end date pickers
  • Camera filter: Filter by specific camera name
  • Confidence threshold: Minimum confidence level
  • Sorting: By time (asc/desc) or confidence (desc)
  • Pagination: Results are paginated (100 per page, max 500) with Previous/Next controls

Live Feed

The Live Feed page connects to /api/live/stream via Server-Sent Events (SSE). It polls the database every 3 seconds for new events and renders them in real time with automatic scrolling.


Watchlist

The Watchlist page provides full CRUD operations:

  • Add: Enter a plate string and optional note
  • Edit: Click the edit icon to update the note
  • Delete: Click the delete icon to remove an entry
  • Status: Shows last_seen timestamp and total_hits count
  • Persistence: Backed by the plate_watchlist database table — survives restarts and Gunicorn worker scaling

Plate Details

Click any plate to view its detail page:

  • Total detection count and first/last seen timestamps
  • Per-camera breakdown
  • Average confidence score
  • 7-day x 24-hour heatmap grid (day-of-week by hour)

Viewing Ingestion Errors

If messages fail JSON parsing or schema validation, they are logged to the ingestion_errors table. Check via the dashboard or directly:

docker exec lpr-db-app-db-1 psql -U oiep_user -d oiep_db -c "SELECT * FROM ingestion_errors ORDER BY error_time DESC LIMIT 10;"

Database Retention & Compression

The database automatically manages data lifecycle:

  • Retention: Data older than 1 year is automatically dropped
  • Compression: Data older than 30 days is automatically compressed to save space

These policies are applied during initial setup and require no manual intervention.


Dashboard Performance

The dashboard uses TimescaleDB continuous aggregates (materialized views) for fast data loading:

  • Volume cache: Refreshed every 5 minutes
  • Trend cache: Refreshed every 5 minutes
  • Weekly cache: Refreshed every 1 hour

All frontend API calls are fetched in parallel for instant page loads. Flask-Caching adds in-memory response caching (30-60s TTL) on top of database aggregates.


Stopping the Stack

docker compose down

Persistent data stays in the oiep_pgdata volume.


⚠️ Factory Reset (Deletes ALL Database Data)

docker compose down -v

5. API Reference

The platform exposes a REST API for all data operations. A browsable API documentation page is available at /api/docs.

Quick Reference

Method Endpoint Description
GET /health Health check
POST /api/config Update MQTT/config settings
GET /api/search/plates Search plates with filters + pagination
GET /api/stats/plate_frequency Daily/weekly/monthly frequency for a plate
GET /api/dashboard/volume Today/week/month counts + unique plates
GET /api/dashboard/trend Hourly trend (24h)
GET /api/dashboard/top_plates Top N plates (7d)
GET /api/dashboard/quality Avg confidence, cameras, errors
GET /api/dashboard/confidence_distribution Confidence histogram
GET /api/dashboard/weekly_trend 7-day per-camera trend
GET /api/live/events Recent live events
GET /api/live/stream SSE real-time event stream
GET /api/plates/<plate> Plate summary (count, cameras, confidence)
GET /api/plates/<plate>/heatmap 7-day x 24h heatmap grid
GET /api/watchlist Get all watchlist items
POST /api/watchlist Add plate to watchlist
PATCH /api/watchlist/<plate> Update watchlist entry
DELETE /api/watchlist/<plate> Remove plate from watchlist
GET /api/export/search Export search results (CSV or JSON)

Caching

Response caching is enabled via Flask-Caching (SimpleCache):

  • 30s TTL: Search, live events, plate details, heatmap, watchlist
  • 60s TTL: Dashboard endpoints (volume, trend, top plates, quality, confidence distribution, weekly trend)

6. Architecture

Data Flow

┌─────────────┐     MQTT      ┌──────────────────┐     PostgreSQL    ┌─────────────┐
│   Frigate   │ ──────────►   │  Ingestion        │ ────────────────► │ TimescaleDB │
│   (LPR)     │               │  Worker            │                   │             │
└─────────────┘               └──────────────────┘                   └──────┬──────┘
                                     │                                      │
                                     │ Deduplication                        │ Queries
                                     │ Manitoba Plate Logic                 │ (connection pooled)
                                     ▼                                      │
                              alpr/filtered ◄───────────────────────────────┘

Stack Overview

Client ──► Nginx (proxy:80) ──► Flask + Gunicorn (web_app:8000)
                                    │
                                    ├── psycopg2 connection pool (4 base + 8 overflow)
                                    ├── Flask-Caching (SimpleCache, 30-60s TTL)
                                    ├── TimescaleDB continuous aggregates
                                    └── 7 JS modules (search, live-feed, plate-detail, watchlist, export, common, charts)

Client ──► Nginx ──► Ingestion Worker (MQTT consumer)
                                    │
                                    ├── paho-mqtt
                                    ├── JSON validation
                                    ├── Manitoba plate filtering
                                    └── TimescaleDB writes

Services

Service Image Port Memory Limit Description
db timescale/timescaledb:2.23.1-pg16 5432 (internal) 1G TimescaleDB with pg_trgm + continuous aggregates
ingestion_worker Custom N/A 512M MQTT consumer, plate processing, DB writer
web_app Custom (Flask+Gunicorn+Caching) 8000 (internal) 256M REST API + web interface (connection pooled, cached)
proxy nginx:1.27-alpine 80, 443 64M Reverse proxy

Dependencies

Layer Key Packages
Web App Flask, Flask-Caching, Gunicorn, psycopg2
Ingestion paho-mqtt, psycopg2
Frontend Bootstrap 5.3.3, Bootstrap Icons 1.11.3, Chart.js
Database TimescaleDB 2.23.1 (pg16), pg_trgm

Security Features

  • All services run as non-root users in Docker containers
  • .env file excluded from version control
  • Resource limits prevent container resource exhaustion
  • XSS protection on all user-facing pages
  • Connection pooling limits DB connections

✔️ Deployment Successful

Your OIEP platform v3.0 should now be fully operational. Visit http://<YOUR_SERVER_IP>/ to access the dashboard.