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, all passing.
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 Ed25519 signature covers the canonical payload of that action. 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, viewer, gate-only, audit-only.
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. Viewer reads cameras, plates, groups, rules and events. Gate-only fires the gate. Audit-only reads and exports the chain.
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="<your-secret-here>", # rotate on suspected compromise, not on a calendar schedule ), }, )
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. Nine views: dashboard, live, cameras, events, groups, rules, audit, network, MFA. 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 — Coming capabilities
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 passing.
Maturity note: These five modules are reference implementation / pilot-ready today. Production connectors (live RTSP ingest, WireGuard mesh, cloud VLM, S3 OTA channels) land at M1–M3 milestones as noted per module. Available in early access — request early access to get started.
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.
Cloud egress notice: This is the one exception to LAN-only operation. When VLM fallback is enabled, plate-crop images leave your network and are sent to the configured cloud model. The feature is off by default and requires explicit operator opt-in.
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 — 202 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. 202 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, supported deployment SKU, multilingual dashboard, EFF-reviewed privacy posture.
08 — Stated commercial policy
Our stated commercial policy — and will be written into the EULA at GA. 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 our stated commercial policy and will be in the EULA at GA, not just the marketing.
Get started
EFF privacy-posture review planned for M3 GA. SQLite + WAL. Ed25519 audit chain. LAN-only operation. Argon2id + TOTP. NaCl SecretBox backups. Sleep well.