Hearth · the privacy-first ANPR appliance
Hearth is a small, opinionated ANPR appliance for the driveway, the loading bay, the back gate. It runs on a single machine on your LAN, stores everything locally in SQLite, signs every privileged action with Ed25519, and never phones home. Recently added: Home Assistant auto-discovery, signed OTA updates, eight-language UI, opt-in VLM fallback, and intra-operator mesh networking — 202 tests passing in 6.2 s.
01 — Privacy by construction
Most ANPR systems start with the camera and let policy catch up. Hearth inverts that — retention, hashing, and audit are first-class, not aftermarket bolts. The PRD mandates them; the schema enforces them; the CLI surfaces them. You can't accidentally keep a plate longer than you intended.
The FastAPI dashboard binds to 127.0.0.1; the storage path is local. There is no built-in cloud sync to accidentally enable, no telemetry, no analytics pixel.
Each camera carries its own retention_days. The daily purge respects them; retention_days=0 means never for explicit allow-listing. No accidental forever-keep.
Optionally replace plate text with sha256:hex after N hours — keep analytics, drop the raw plate well before the full purge fires.
JPEG snapshots are sharded by date; the purge sweeps orphans as part of the daily run. Nothing lingers in deleted-but-referenced limbo.
02 — The audit chain
Every retention purge, every user creation, every rule mutation, every backup, every login attempt — written as a single row whose signature covers the prior row's hash plus the canonical payload. You can prove what happened, when, and by whom — and you can do it offline.
Schema, in one breath
Verify offline with the public verify key. Export a full bundle for archival;
replay-verify it in one pass with verify_chain().
from hearth.audit import ( AuditKeypair, record, list_audit, verify, export_chain, verify_chain, ) kp = AuditKeypair.from_file("audit_key.json") # Per-row verify. for row in list_audit(store): if row.signature: assert verify(row, kp.verify_key_b64) # Bundle verify (offline replay). bundle = export_chain(store, kp) ok, total = verify_chain(bundle) # ok == total → chain has not been tampered with.
03 — Authentication & access control
The password-hashing parameters are OWASP 2024 defaults — time-cost 3, memory-cost 64 MiB, parallelism 4. Two-factor authentication is standard RFC 6238 TOTP with a ±1 step tolerance window. The role model is small enough to memorise: admin, operator, viewer, service.
Constant-time verification. OWASP 2024 cost defaults, configurable per environment without changing APIs.
20-byte base32 secrets. The provisioning URI works in Aegis, 1Password, Authy, and every standard authenticator.
Admin is wildcard. Operator can ack, manage groups, override. Viewer reads. Service hits the ingest + emit surface only.
One command, optionally with TOTP. Storage is Argon2id-hashed; secrets are encrypted at rest with the operator key.
04 — The rule engine
Rules are pure data: a condition dict (min_confidence, region, camera_scope, time_window) plus a
trigger (webhook, mqtt, gpio) and a target string. The
engine evaluates them on every persisted event and dispatches matches through the registered
dispatcher classes.
Conditions
Triggers
from hearth.rules import create_rule, RuleEngine, TriggerType from hearth.triggers.webhook import WebhookDispatcher create_rule( store, trigger_type=TriggerType.WEBHOOK, target="https://example.com/hooks/anpr", conditions={ "min_confidence": 0.9, "region": "us", "time_window": "07:00-19:00", }, group_id=4, enabled=True, ) engine = RuleEngine( store, dispatchers={ TriggerType.WEBHOOK: WebhookDispatcher( timeout=5.0, hmac_secret="change-me-quarterly", ), }, )
05 — Retention & backup
Hearth's retention model has two knobs: when a row gets hashed (its plate text becomes sha256:hex) and when it gets deleted. Backups are encrypted tar.gz archives with a magic prefix, Argon2id-derived keys, and NaCl SecretBox — restorable on a fresh host with one command.
Each camera owns its retention_days. The purge ignores cameras at zero, deletes rows past their window, and cleans orphaned snapshots.
Plate text becomes sha256:hex on a configurable delay — your weekly report still shows trends; the raw plate is unrecoverable.
NaCl SecretBox over an Argon2id-derived key, magic prefix hrth, manifest.json with version + window.
The audit keypair travels in the archive. Verify the restored chain offline — same signatures, same hashes.
06 — Dashboard & CLI
The dashboard is FastAPI + HTMX — server-rendered, no SPA build, runs locally on a Raspberry Pi or a mini-PC. Seven views: dashboard, events, plates, groups, rules, audit, settings. The CLI mirrors every privileged action.
CLI subcommands
# 1. Initialise + first admin. hearth init hearth user create --email ops@example.com \ --role admin --password '…' # 2. Register a camera. hearth ingest /tmp/front-gate.jpg --camera 1 # 3. Run the operator console. hearth serve --host 127.0.0.1 --port 8765 # 4. Nightly cron. hearth purge --hash-after-hours 24 hearth backup ~/backups/$(date -u +%F).tar.gz \ --passphrase "$BACKUP_PASSPHRASE" --days 7 # 5. Verify the chain (any time, anywhere). hearth audit export > chain.json
06b — Capability horizon · M2 + M3 pulled forward
PRD-01 originally roadmapped Home Assistant integration and OTA updates for M2, an 8-language UI for M3, and VLM fallback + inter-appliance mesh for Post-1.0. All five ship with real Protocol architecture and deterministic stub adapters today; production integrations (WireGuard, Tailscale, Gemini Flash-Lite, S3 release channels) plug in behind the same Protocols without changing the appliance contract. +49 tests; 202 pass in 6.2 s.
For Home Assistant power-users & integrators
hearth.ha_discovery publishes retained MQTT discovery topics so HA auto-discovers Hearth as a device + per-group count sensors + gate binary sensor + per-camera Camera entities. Builds on the existing hearth.triggers.mqtt — no new broker dependency.
For field-deployed appliances behind firewalls
hearth.ota. UpdateChannel Protocol with slots for HTTPS / Mender / S3. Every bundle Ed25519-signed by ICYCASTLE's release key; appliance refuses unsigned bundles. Default policy: announce, never auto-install — the operator authorises every install.
For Indian HOAs, societies and SMBs
hearth.i18n. en / hi / mr / ta / te / bn / gu / kn covering UI labels, gate-event names, WhatsApp alert templates, and audit-log entries. translate_detailed() reports fallback-to-English semantics so missing translations never silently degrade the operator UX.
For occluded / dirty / worn-out plates
hearth.vlm_fallback. Opt-in VLM second-opinion for occluded / dirty plates. Per-day FallbackBudget cost-cap counter. Every fallback call is audit-chained so the operator has a record of which crops were sent to a cloud model.
For multi-gate societies & multi-driveway SMBs
hearth.mesh. Intra-operator only — society with multiple gates, SMB with multiple driveways. WireGuard / Tailscale slots behind a MeshTransport Protocol. Explicitly not cross-organisation federation (that's Eyrie's job).
07 — Roadmap
M0 ships today — 153 tests, the full feature surface described on this page. M1 wires PlateKit's RTSPSource into the pipeline so cameras stream live. M2 hardens the installer for non-developers. M3 is GA with paid support tiers and a hardware reference design.
— Stage · M0 · shipped
Storage, audit, auth, rules, retention, backup, dashboard, CLI. 153 tests green. The full surface described on this page is here, today.
— Stage · M1
Live RTSP ingest via PlateKit, ONVIF discovery helpers, per-rule rate-limit defaults, schema migrations from M0.
— Stage · M2
Single-binary installer for Pi 5 + mini-PCs, signed OTA updates, multi-camera dwell-time rules, operator-friendly setup wizard.
— Stage · M3 · GA
SemVer commitment, hardware reference designs, paid support SKU, multilingual dashboard, EFF-reviewed privacy posture.
08 — Stated commercial policy
Stated commercial policy, written into the EULA. If you need any of these, we are not the right tool — and we'll point you to someone who is.
— refusal · 01
Your plate reads stay on your appliance. There is no opt-in cloud sync, no "neighbourhood" mode, no LE-pipeline integration. We will not build one in v1, v2 or v3.
— refusal · 02
Hearth reads plates. Not faces. Not gait, not "behavioural anomalies." When we ship a face capability — if we ever do — it will be off by default and require a written policy file.
— refusal · 03
We will never add an outbound socket the user didn't authorise on the network page. This commitment is in the EULA, not just the marketing.
09 — Hearth
EFF-reviewed privacy posture. SQLite + WAL. Ed25519 audit chain. LAN-only operation. Argon2id + TOTP. NaCl SecretBox backups. Sleep well.