As-built auto-arming home alarm on telep-mainframe: arms when no trusted phone is on the network, escalates person/car detections to Telegram, and plays a siren on the TV. Single-process intruder-alarm.service, python3 stdlib-only. Supersedes the spec in 2026-07-17-intruder-alarm-design.

For Agents

Service: intruder-alarm.service on telep-mainframe. Script: /home/levander/intruder-alarm.py (stdlib only, no pip deps). State dir: /home/levander/alarm/. Self-test: python3 /home/levander/intruder-alarm.py --selftest (31 assertions). Bot: same “Dezsi az őr” bot as frigate-notify; token read at runtime from ~/nvr/frigate-notify/config.yml (never hard-coded). This service is the sole getUpdates consumer for the bot (frigate-notify only sends). Router access: SSH to the router via key /home/levander/.ssh/router_alarm → LAN-only dropbear (see Prerequisite router access).

Presence detection

A device is “present” if seen by either of two independent sources (union), polled together:

  1. Router WiFi association table — SSH to telep-router, iwinfo <ap> assoclist on the telep1 APs phy0-ap0 (5 GHz) and phy1-ap0 (2.4 GHz).
  2. arp-scan on the mainframe’s LAN NIC enp5s0.

Why arp-scan alone false-arms — sleeping iOS phones

A sleeping iPhone stays WiFi-associated but stops answering ARP. arp-scan alone would see everyone “leave” and arm the house overnight. The router association table still sees the sleeping phone, so the union keeps it present. This is the whole reason the router path exists.

A failed poll is skipped, never "everyone left"

If a presence scan fails (SSH down, arp-scan error), that cycle is discarded rather than interpreted as an empty network. Transient blips cannot false-arm.

Excluded infrastructure MACs

Infra devices are excluded so they never appear as enrollable phones or count as “trusted present”: mainframe, printer, router, camera (18:69:45:a9:01:25).

Camera-VLAN gotcha — telep-cc leaked an enrollable device

The camera SSID telep-cc is AP phy1-ap1. An early version queried it too, so the Tapo camera (18:69:45:a9:01:25) showed up as a new device begging to be enrolled. Fix: only query the telep1 APs (phy0-ap0, phy1-ap0) and hard-exclude the camera MAC. See Camera VLAN (telep-cc).

Prerequisite: router access

The mainframe could not reach the router at all: the router only ran Tailscale-SSH (ACL-blocked from the mainframe) with no port-22 daemon. Fix:

  • Enabled a LAN-only, key-only dropbear on the router: uci dropbear with Interface=lan and PasswordAuth off.
  • Added a dedicated key on the mainframe: /home/levander/.ssh/router_alarm (public half in the router’s authorized_keys).

Enabling dropbear changed the router's SSH host key

Standing up the new dropbear rotated the router’s SSH host key, so existing known_hosts entries for telep-router now mismatch. Refresh known_hosts when SSHing to the router from any other client. (This new instance is also keys-only, partially closing the “dropbear password auth” security gap.)

Device naming & enrollment

New present MAC (not trusted, not ignored, not infra) → Telegram message with inline buttons Trust / Ignore; enrolled only on Trust, suppressed on Ignore. Confirm-each.

Names resolve through a fallback chain, hardened so a dig/network error can never leak into a device name:

  1. mDNSavahi-resolve (gives distinctive names like Mark-iPhone-ja).
  2. dnsmasq reverse DNSdig -x <ip> @192.168.1.1.
  3. Fallbackeszköz-<last4-of-mac>.

State machine

  • Dormant until ≥1 trusted device exists (empty trusted list = never arm, no intruder watch).
  • Any trusted device present → disarmed.
  • All trusted devices absent for 10 minarmed.
  • Any trusted device returning → disarmed instantly.
  • Arm/disarm Telegram messages name the device that triggered the transition.
  • Manual override commands: /arm, /disarm, /auto (return to automatic mode).

Intruder escalation (only while armed)

  • Frigate person or car🚨 BETŐRŐ! Telegram message + snapshot.
  • Repeated 1×/day, 2×/night (night = 20:00–08:00), ~45s apart, cooldown 60s.
  • All pending repeats cancel on disarm.

TV siren (always — armed or not)

  • Every person/car detection plays a 2-beep siren on the LG TV, independent of armed state.
  • aplay -D plughw:1,3 /home/levander/alarm/siren.wav — the NVIDIA HDMI audio out is ALSA card 1, device 3.
  • 60s cooldown.

Commands

CommandEffect
/statusLists devices (MAC + present marker) and armed state
/rename <mac|name> <newname>Rename a trusted device
/untrust <mac|name>Remove a trusted device
/arm / /disarmManual override
/autoReturn to automatic arming

Persistence (/home/levander/alarm/)

FileContents
trusted.jsonenrolled devices {mac: {name, ...}}
ignored.jsonMACs to never re-prompt
state.jsonarmed state / timers / telegram offset (survives restart)
siren.wav2-beep siren played on the TV