A self-hosted home alarm panel that puts you in control.
Django + React. Integrates with Home Assistant, MQTT, Z-Wave JS, Zigbee2MQTT, and Frigate.
- Self-hosted, no cloud lock-in. Your alarm logic, codes, and audit log live on your hardware.
- Encryption at rest for every secret (broker passwords, API tokens, door PINs) — Fernet, single key, auto-generated on first boot (ADR 0079).
- Visual rule builder with drag-and-drop (React QueryBuilder) — no YAML, no code paths to memorize.
- Real-time — every state change pushes through Django Channels to the UI; arming, triggering, and sensor events update instantly.
- Schema-driven UI — integration settings forms render from backend
config_schemadefinitions, so adding a new integration only requires a backend handler. - Built for hardware — first-class Ring Keypad v2 support over Z-Wave JS with per-device action mapping.
- Eight alarm states:
disarmed,arming,armed_home,armed_away,armed_night,armed_vacation,pending,triggered. - Per-state delay configuration (entry, exit, trigger).
- Multiple settings profiles — switch between "Home", "Vacation", or any custom profile and every dependent setting (delays, sensor behavior, allowed arming states, audio/visual feedback) follows.
- Real-time WebSocket updates so the dashboard, control panels, and HA MQTT alarm entity stay in lockstep.
- Five rule kinds: trigger, disarm, arm, suppress, escalate.
- Priority-based execution with optional
stop_processingscoping by group (ADR 0084). - Cooldowns and circuit breakers per rule, surfaced in the runtime state UI.
- Self-registering action handlers:
alarm_trigger,alarm_arm,alarm_disarm,send_notification,ha_call_service,zwavejs_set_value,zigbee2mqtt_light/_switch/_set_value. - Rules Test page — simulate any rule against a synthetic state snapshot before turning it on.
- Four code types: permanent, temporary (date-windowed), one-time (max-uses=1), service.
- Day-of-week bitmask + time-of-day window restrictions.
- Per-state allow lists — e.g. a guest code that only works for arm/disarm but not while triggered.
- Argon2-hashed (lower-cost variant tuned for short PINs, see commit 260f96f).
- Per-lock assignment with optional slot index (smart locks expose limited slots — Latchpoint manages the mapping).
- Same scheduling primitives as user codes (start/end, day-of-week, time window, max uses).
- Source tracking (
manualvs.synced) and durable audit log ofcode_used/code_failed/code_syncedevents.
- Pluggable handler registry: Pushbullet, Discord, Slack, Webhook, Home Assistant.
- Full CRUD in the UI; secrets encrypted via the handler's
encrypted_fieldsdeclaration. - Durable outbox with exponential backoff and idempotency keys — notifications survive restarts.
- Per-provider test endpoint and audit log.
| Integration | Capabilities |
|---|---|
| Home Assistant | Entity discovery + sync, notification services, MQTT alarm entity (auto-discovery + state publishing), live status probe |
| MQTT | Broker connection (TLS supported), shared transport for Z2M / Frigate / HA alarm entity. Pinned client_id within MQTT 3.1 limits (commit c98187f) |
| Z-Wave JS | WebSocket-based device control, entity sync, Ring Keypad v2 control panel support |
| Zigbee2MQTT | Inventory sync, light + switch control via MQTT |
| Frigate | Person/vehicle detection events with zone + confidence tracking, deterministic rule evaluation against the local detection table |
- Physical keypad support — currently Ring Keypad v2 over Z-Wave JS.
- Per-device action mapping (
disarm→disarmed,arm_home→armed_home,cancel→cancel_arming, etc.). - Beep/indicator volume control via Z-Wave Indicator CC.
- Test endpoint to verify keypad output without altering alarm state.
- Registry-driven background tasks (cleanup, integration health probes, status digests).
- Per-task health snapshot with consecutive-failure counter, last error, last duration.
- Append-only run history for incident review.
- Failure events surface back into the alarm event log (
scheduler_task_failed,scheduler_task_stuck).
- Email + password login (Argon2id).
- TOTP/2FA opt-in.
- Role-based access (admin, resident, guest, service).
- Server-rendered onboarding wizard for first boot.
- Every state transition, code use, sensor trigger, integration outage, and rule firing lands in the alarm event log.
- Filterable by type, state, sensor, user, time range.
- Failed code attempts tracked separately for security review.
- Multiple alarm settings profiles, switchable from the settings UI — integrations, delays, notification providers, and arming behavior all scope to the active profile.
- Per-profile encrypted notification provider config.
- Schema-driven generic settings form (
IntegrationSettingsForm) so new integrations only need a backendconfig_schema.
- DB-backed config (ADR 0079) — connection URLs, tokens, broker passwords, and operational settings live in
AlarmSettingsEntryJSON blobs per profile, never in environment variables. - Encryption at rest — secret fields use Fernet via
backend/alarm/crypto.py. Single env varSETTINGS_ENCRYPTION_KEY(auto-generated on first boot if absent). - Settings registry —
backend/alarm/settings_registry.pyis the single source of truth for setting definitions, defaults, types, JSON schemas, and which fields are encrypted. - Action handler registry — rule actions self-register at
backend/alarm/rules/action_handlers/, keyed bytype. Adding a new action is one file. - Import boundary —
alarm/rules/andalarm/use_cases/must NOT import fromintegrations_*ortransports_*(enforced by test). - Real-time — Django Channels 4 + Daphne serve the WebSocket; Zustand on the frontend mirrors state through TanStack Query.
Backend — Python 3.12 · Django 6 · Django REST Framework · Django Channels · PostgreSQL 15 · paho-mqtt · zwave-js-server-python · homeassistant-api · httpx · Argon2 · Gunicorn + Uvicorn
Frontend — React 19 · TypeScript · Vite · TanStack Query 5 · Zustand 5 · React Router 7 · Tailwind 4 · Radix UI · React Hook Form + Zod · React QueryBuilder 8 · MSW (test mocks)
cp .env.example .env
docker compose up -d # postgres + django + vite-dev-serverFrontend at http://localhost:5427, backend at http://localhost:8000.
Bootstrap an admin login:
docker compose exec -T -w /app/backend backend python manage.py shell -c "
from accounts.models import User
u, _ = User.objects.get_or_create(email='admin@testhome.local', defaults={'is_staff': True, 'is_superuser': True, 'is_active': True})
u.set_password('adminpass'); u.is_staff = True; u.is_superuser = True; u.is_active = True; u.save()
"| Field | Value |
|---|---|
| URL | http://localhost:5427/login |
admin@testhome.local |
|
| Password | adminpass |
The entire UI runs in your browser with no backend, no database, and no Docker — every page populated with hand-crafted fixture data, every mutation in-memory only. Architecture is documented in ADR-0089.
cd frontend
npm install
npm run dev:demo # local browser tour at http://localhost:5427
# or build a static bundle for hosting:
npm run build:demo # outputs dist-demo/, deployable anywhere staticHard refresh resets all state to the initial fixtures — no changes are saved. A GitHub Actions workflow auto-deploys the bundle to GitHub Pages on every published release.
Pre-built images are pushed to ghcr.io/latchpoint/latchpoint:
docker pull ghcr.io/latchpoint/latchpoint:latest
docker run -d -p 80:80 --env-file .env ghcr.io/latchpoint/latchpoint:latestTags include latest (default branch), sha-... (commit), and git tag
versions. See .env.example for environment variables and
inline notes on generating SETTINGS_ENCRYPTION_KEY.
Set TZ in .env to an IANA timezone name (e.g. TZ=America/Chicago).
This drives POSIX libc inside the application containers and Django's
TIME_ZONE setting — which in turn drives the scheduler, rule conditions,
and the {{now}} template variable. When unset, everything defaults to
UTC. The image carries tzdata, so no host bind-mounts of /etc/localtime
or /etc/timezone are needed.
Postgres stays on UTC regardless of TZ. Django runs with USE_TZ=True,
so the ORM stores all datetimes as UTC; the API emits ISO-8601 with offset
and the browser localizes the display. See
ADR-0090.
A reference compose file:
services:
db:
image: postgres:15
environment:
POSTGRES_DB: alarm_db
POSTGRES_USER: alarm
POSTGRES_PASSWORD: your-secure-password
volumes:
- db_data:/var/lib/postgresql/data
app:
image: ghcr.io/latchpoint/latchpoint:latest
env_file: [.env]
ports: ['80:80']
depends_on: [db]
volumes:
db_data:After first boot:
docker exec <container> python backend/manage.py migrate
docker exec -it <container> python backend/manage.py createsuperuseruvx ruff check backend/ # backend lint
uvx ruff format --check backend/ # backend format check
cd frontend && npx eslint src/ # frontend lint (0 errors required)
cd frontend && npx tsc -b # frontend typecheck (project-build mode)Use
tsc -b, nottsc --noEmit—frontend/tsconfig.jsonis a solution file with"files": [], so baretsc --noEmitwalks zero source files.
./scripts/docker-test.sh # full backend suite
docker compose run --rm --entrypoint sh backend \
-c "cd backend && python manage.py test alarm.tests.test_template_render -v 2"
cd frontend && npx vitest run # frontend suiteThe Playwright harness in scripts/screenshots/
captures every page in docs/screenshots/. Default target is the frontend-only
demo (no backend required) — start npm run dev:demo in frontend/, then run
the harness.
backend/
accounts/ # users, RBAC, alarm codes, TOTP/2FA
alarm/ # state machine, rules engine, settings registry
crypto.py # Fernet helpers (set_value_with_encryption, masking)
rules/action_handlers/ # self-registering rule actions
settings_registry.py # SettingDefinition (defaults, schemas, encrypted_fields)
control_panels/ # Ring Keypad v2 device + action mapping
locks/ # door codes, slot assignments, code events
notifications/ # provider registry, dispatcher, durable outbox
scheduler/ # registered tasks + health snapshots
transports_mqtt/ # MQTT broker connection manager
integrations_home_assistant/
integrations_zwavejs/
integrations_zigbee2mqtt/
integrations_frigate/
frontend/src/
pages/ # routed page components
features/ # feature modules (rules, codes, integrations, ...)
stores/ # Zustand stores (theme, auth, ws)
services/ # API clients + endpoint constants
components/ui/ # shared Radix-based primitives
docs/adr/ # 79 architecture decision records
docs/screenshots/ # README screenshots (captured by scripts/screenshots/)
schema/ # SQL + seed_entities.json
scripts/ # docker-*.sh helpers, screenshot harness
See LICENSE if present in the repo. Otherwise treat as all-rights-reserved until a license file is added.






















