[ Home Assistant: Simple DIY Alarm System ]
[ Overview |
Step 1 — Panel |
Step 2 — Detection |
Step 3 — Response |
Step 4 — RGB feedback |
Step 5 — PTZ tracking |
Step 6 — Privacy mode |
Optional |
Caveats ]
[ Overview ]
A simple, self-contained DIY alarm system built on top of Home Assistant.
Uses whatever contact + motion sensors you already have, an
alarm_control_panel entity for arm/disarm state, and TTS + flashing
lights as the response when something trips.
Three modes, distinct trigger sets:
armed_home perimeter only (you're inside, motion is fine)
armed_night perimeter only, interior doors excluded
so you can pee at 3am without setting it off
armed_away everything — perimeter, interior, and motion
The example below assumes a small set of sensors (front + rear contact,
one bedroom contact, one kitchen motion) but the pattern scales to any
number of sensors — just append entity_ids to the right trigger lists.
Example sensor map
Sensor Entity Type Location
─────────────────────────────────────────────────────────────────────────────────────
Front Door binary_sensor.front_door_door contact Living Room
Rear/Sliding binary_sensor.rear_door_door contact Living Room
Bedroom Door binary_sensor.bedroom_door_door contact Bedroom (interior)
Kitchen Motion binary_sensor.camera_e1_5295_motion_sensor motion Kitchen
The bedroom door is interior, so it's excluded from armed_night — otherwise a
2am bathroom trip would trigger the siren on yourself.
[ Step 1 — The Alarm Panel ]
Drop this in configuration.yaml. It creates the alarm_control_panel.home_alarm entity that everything else hangs off of. After saving, restart Home Assistant.
# configuration.yaml
alarm_control_panel:
- platform: manual
name: Home Alarm
code: !secret alarm_code # set this in secrets.yaml, e.g. alarm_code: "1234"
code_arm_required: false # don't require code to arm, only to disarm
arming_time: 30 # 30s grace period to leave after arming
delay_time: 20 # 20s entry delay before triggering
trigger_time: 600 # alarm sirens for 10 minutes
disarmed:
trigger_time: 0
armed_home:
arming_time: 0 # arming home = instant, you're already there
delay_time: 0 # no entry delay in home mode (instant alarm)
armed_night:
arming_time: 0
delay_time: 20
armed_away:
arming_time: 30
delay_time: 20
After restart you'll have alarm_control_panel.home_alarm as a state machine, plus a Lovelace card you can drop on a dashboard for arm/disarm buttons. The arm code is enforced on disarm only — arming with a single tap, disarm requires the PIN. Adjust to taste.
[ Step 2 — Detection automations ]
Three automations watch the sensors. Each one fires alarm_control_panel.alarm_trigger when its conditions are met — the panel itself handles the entry-delay countdown and state machine. Place these in automations.yaml (or wherever you keep automations).
# automations.yaml
# Perimeter trigger — fires in ALL armed modes
- alias: "Alarm: Perimeter breach"
description: "Front or rear door opened while alarm is armed"
trigger:
- platform: state
entity_id:
- binary_sensor.front_door_door
- binary_sensor.rear_door_door
to: "on"
condition:
- condition: state
entity_id: alarm_control_panel.home_alarm
state:
- armed_home
- armed_away
- armed_night
action:
- action: alarm_control_panel.alarm_trigger
target:
entity_id: alarm_control_panel.home_alarm
# Interior trigger — only in away mode (home/night = you're inside)
- alias: "Alarm: Interior motion"
description: "Kitchen motion while armed away"
trigger:
- platform: state
entity_id: binary_sensor.camera_e1_5295_motion_sensor
to: "on"
condition:
- condition: state
entity_id: alarm_control_panel.home_alarm
state: armed_away
action:
- action: alarm_control_panel.alarm_trigger
target:
entity_id: alarm_control_panel.home_alarm
# Bedroom door — only in away mode (otherwise the bathroom trip rule)
- alias: "Alarm: Bedroom door (away only)"
trigger:
- platform: state
entity_id: binary_sensor.bedroom_door_door
to: "on"
condition:
- condition: state
entity_id: alarm_control_panel.home_alarm
state: armed_away
action:
- action: alarm_control_panel.alarm_trigger
target:
entity_id: alarm_control_panel.home_alarm
[ Step 3 — Response automations ]
When the panel transitions states, these fire the actual response — entry warning, full alarm, and a disarm confirmation. The pattern is to watch state changes on the panel itself, not on the sensors, so you don't have to duplicate "is it armed?" logic everywhere.
# When the alarm enters "pending" (entry delay countdown), warn first
- alias: "Alarm: Entry warning"
trigger:
- platform: state
entity_id: alarm_control_panel.home_alarm
to: pending
action:
- action: media_player.play_media
target:
entity_id: media_player.nest_display
data:
media_content_id: >-
media-source://tts/tts.piper?message=Alarm+will+trigger+in+twenty+seconds.+Disarm+now.&language=en_US
media_content_type: audio/mp3
# When the alarm actually triggers — full response
- alias: "Alarm: Triggered response"
trigger:
- platform: state
entity_id: alarm_control_panel.home_alarm
to: triggered
action:
# 1. Push notification with high priority
- action: notify.mobile_app_magikh0e_phone
data:
title: "🚨 ALARM TRIGGERED"
message: "Security system has been triggered at home."
data:
priority: high
ttl: 0
# 2. Loud TTS on every available speaker
- action: media_player.volume_set
target:
entity_id:
- media_player.nest_display
- media_player.m5stack_atom_echo_a17a48
data:
volume_level: 1.0
- action: media_player.play_media
target:
entity_id:
- media_player.nest_display
- media_player.m5stack_atom_echo_a17a48
data:
media_content_id: >-
media-source://tts/tts.piper?message=Intruder+Alert!&language=en_US
media_content_type: audio/mp3
# 3. After TTS finishes, cast a YouTube video to the Nest display
# (Google Cast handles video/youtube content type natively).
- delay: "00:00:03"
- action: media_player.play_media
target:
entity_id: media_player.nest_display
data:
media_content_id: https://www.youtube.com/watch?v=04F4xlWSFh0
media_content_type: video/youtube
# 4. Flash all lights red as a deterrent + visibility
- repeat:
count: 30
sequence:
- action: light.turn_on
target:
entity_id:
- light.living_room_lights
- light.bedroom_lights
data:
brightness: 255
rgb_color: [255, 0, 0]
- delay: "00:00:01"
- action: light.turn_off
target:
entity_id:
- light.living_room_lights
- light.bedroom_lights
- delay: "00:00:01"
# When disarmed, send confirmation
- alias: "Alarm: Disarmed"
trigger:
- platform: state
entity_id: alarm_control_panel.home_alarm
to: disarmed
action:
- action: notify.mobile_app_magikh0e_phone
data:
message: "Alarm disarmed at {{ now().strftime('%I:%M %p') }}"
[ Step 4 — RGB light feedback ]
Visual feedback for state changes — bulbs flash blue when you arm, green when you disarm, breathe amber during the entry-delay countdown, and the existing red strobe handles intruder alerts. Makes the alarm state obvious from across the room without staring at a dashboard. First, group your RGB bulbs into a single light.alarm_lights entity so the automations target one thing instead of repeating an entity_id list everywhere. Add this to configuration.yaml alongside the alarm_control_panel block from Step 1.
# configuration.yaml
light:
- platform: group
name: Alarm Lights
entities:
- light.living_room_lights
- light.bedroom_lights
- light.kitchen_lights
After restart you'll have light.alarm_lights covering every bulb. Now the feedback automations — drop these in automations.yaml.
# automations.yaml — RGB light feedback
# Armed → quick double blue flash to confirm the arm action.
# Fires for any of the three armed states so you get the same visual
# regardless of how you armed (manual / auto-arm / presence-based).
- alias: "Alarm: Light feedback — armed"
mode: restart
trigger:
- platform: state
entity_id: alarm_control_panel.home_alarm
to:
- armed_home
- armed_night
- armed_away
action:
# Snapshot current light state so we can restore it after the flash
- action: scene.create
data:
scene_id: alarm_lights_before
snapshot_entities: light.alarm_lights
- repeat:
count: 2
sequence:
- action: light.turn_on
target:
entity_id: light.alarm_lights
data:
brightness: 200
rgb_color: [0, 100, 255] # blue
transition: 0
- delay: "00:00:00.4"
- action: light.turn_off
target:
entity_id: light.alarm_lights
data:
transition: 0
- delay: "00:00:00.4"
# Restore whatever the lights were doing before
- action: scene.turn_on
target:
entity_id: scene.alarm_lights_before
# Disarmed → single green pulse confirming "all clear"
- alias: "Alarm: Light feedback — disarmed"
mode: restart
trigger:
- platform: state
entity_id: alarm_control_panel.home_alarm
to: disarmed
action:
- action: scene.create
data:
scene_id: alarm_lights_before
snapshot_entities: light.alarm_lights
- action: light.turn_on
target:
entity_id: light.alarm_lights
data:
brightness: 200
rgb_color: [50, 255, 80] # green
transition: 0
- delay: "00:00:01.5"
- action: scene.turn_on
target:
entity_id: scene.alarm_lights_before
# Pending (entry-delay countdown) → amber breathing pulse
# Stops automatically when the panel leaves "pending" — either you
# disarmed in time, or the alarm tripped and Step 3's red flash takes
# over. mode: restart so a fresh entry resets cleanly.
- alias: "Alarm: Light feedback — entry delay"
mode: restart
trigger:
- platform: state
entity_id: alarm_control_panel.home_alarm
to: pending
action:
- action: scene.create
data:
scene_id: alarm_lights_before
snapshot_entities: light.alarm_lights
- repeat:
while:
- condition: state
entity_id: alarm_control_panel.home_alarm
state: pending
sequence:
- action: light.turn_on
target:
entity_id: light.alarm_lights
data:
brightness: 255
rgb_color: [255, 140, 0] # amber
transition: 0
- delay: "00:00:00.6"
- action: light.turn_off
target:
entity_id: light.alarm_lights
data:
transition: 0
- delay: "00:00:00.6"
# Restore prior state if we exited because of disarm
# (if we exited because of trigger, Step 3 takes over and
# this scene.turn_on is harmlessly overwritten by the red flash)
- action: scene.turn_on
target:
entity_id: scene.alarm_lights_before
Color cheat sheet blue [0, 100, 255] armed (any mode) green [50, 255, 80] disarmed / all clear amber [255, 140, 0] pending (entry delay countdown) red [255, 0, 0] triggered (Step 3, already wired) The scene.create → flash → scene.turn_on sandwich is the canonical "pulse without disrupting" pattern in HA. Without it, every arm/disarm would turn off whatever lights you had on. With it, your reading lamp keeps its warm-2700K glow once the blue confirmation flash finishes. Refactoring tip The Step 3 red-flash response still uses light.living_room_lights and light.bedroom_lights directly. Now that light.alarm_lights exists, you can swap those entity_ids for the group and lose a few lines.
[ Step 5 — PTZ camera + presence-driven tracking ]
A pan/tilt/zoom camera turns "something tripped the motion sensor" into
"I have a face on file." When a presence or motion sensor fires, the
camera pivots to that zone's preset, settles, snaps a still, and sends
the image to your phone alongside the alert push.
This pairs naturally with the alarm: while armed_away, every
zone trip captures evidence; while disarmed, the camera stays
quiet so it isn't snapping you reading on the couch.
One-time setup — store presets on the camera
Use the camera's web UI (or a tool like ONVIF Device Manager) to drive
the gimbal to each zone in turn and save it as a numbered preset.
Cameras typically support 8–255 preset slots; small cameras
like a Reolink E1 hold 64. The token you'll reference in YAML is
just the preset slot number as a string.
Preset 1 → Living Room
Preset 2 → Kitchen
Preset 3 → Front Entry
Preset 4 → Hallway
Preset 0 → "home" / idle position (camera returns here
after a few minutes of quiet)
Motion vs. presence sensors
PIR motion (Aqara P1, Hue, Zigbee multi-sensors) —
cheap, battery-powered, fires on initial movement then has a
cooldown. Misses a stationary intruder. Best for hallways and
doorways where the target moves through.
mmWave presence (Aqara FP2, LD2410/LD2412 ESPHome boards,
Apollo MSR, Everything Presence One) — radar-based,
detects breathing/sub-cm motion. Holds "on" while
someone's in the room even if they sit still. Best for rooms
where dwell time matters. Mostly mains-powered.
Both expose binary_sensor.<name>_presence or
_occupancy — the YAML pattern below works the same
for either.
Example sensor-to-zone map
Sensor Zone Preset
─────────────────────────────────────────────────────────────────
binary_sensor.living_room_presence Living Room 1
binary_sensor.kitchen_motion Kitchen 2
binary_sensor.front_door_door Front Entry 3
binary_sensor.hallway_presence Hallway 4
The DRY version — one automation, a lookup table mapping each sensor to its preset + label, and a single snap/notify action chain that runs regardless of which sensor tripped. Adding a new zone is a two-line edit to the table, not a new automation.
# automations.yaml — PTZ tracking
- alias: "PTZ: Track motion to zone"
description: "When armed_away, pan to the tripped zone, snap, and notify"
mode: queued # queue triggers so rapid sequential motion doesn't lose snapshots
max: 5
trigger:
- platform: state
entity_id:
- binary_sensor.living_room_presence
- binary_sensor.kitchen_motion
- binary_sensor.front_door_door
- binary_sensor.hallway_presence
to: "on"
condition:
- condition: state
entity_id: alarm_control_panel.home_alarm
state: armed_away
variables:
# Lookup table: sensor entity_id → { preset_token, human label }
zones:
binary_sensor.living_room_presence: { preset: "1", label: "Living Room" }
binary_sensor.kitchen_motion: { preset: "2", label: "Kitchen" }
binary_sensor.front_door_door: { preset: "3", label: "Front Entry" }
binary_sensor.hallway_presence: { preset: "4", label: "Hallway" }
zone: "{{ zones[trigger.entity_id] }}"
stamp: "{{ now().strftime('%Y%m%d_%H%M%S') }}"
file_path: "/config/www/snapshots/ptz_{{ stamp }}.jpg"
web_path: "/local/snapshots/ptz_{{ stamp }}.jpg"
action:
# 1. Move the gimbal to the zone preset
- action: onvif.ptz
target:
entity_id: camera.ptz_main
data:
preset_token: "{{ zone.preset }}"
# 2. Wait for the camera to settle and re-focus
- delay: "00:00:03"
# 3. Capture a still
- action: camera.snapshot
target:
entity_id: camera.ptz_main
data:
filename: "{{ file_path }}"
# 4. Push notification with snapshot attached
- action: notify.mobile_app_magikh0e_phone
data:
title: "🎥 Motion: {{ zone.label }}"
message: "{{ now().strftime('%I:%M %p') }} — snapshot captured"
data:
image: "{{ web_path }}"
ttl: 0
priority: high
Return-to-home After a flurry of zone-trips the camera ends up parked at whichever preset fired last. A separate automation parks it back at preset 0 (or wherever you like to leave it) once everything has been quiet for a while.
# automations.yaml — PTZ idle return
- alias: "PTZ: Return to home"
description: "Park the gimbal at the home preset after 5 min of no zone trips"
trigger:
- platform: state
entity_id:
- binary_sensor.living_room_presence
- binary_sensor.kitchen_motion
- binary_sensor.front_door_door
- binary_sensor.hallway_presence
to: "off"
for: "00:05:00"
condition:
# Only re-park if every zone is currently quiet
- condition: state
entity_id: binary_sensor.living_room_presence
state: "off"
- condition: state
entity_id: binary_sensor.kitchen_motion
state: "off"
- condition: state
entity_id: binary_sensor.front_door_door
state: "off"
- condition: state
entity_id: binary_sensor.hallway_presence
state: "off"
action:
- action: onvif.ptz
target:
entity_id: camera.ptz_main
data:
preset_token: "0"
Bonus — attach a snapshot to the main alarm trigger The triggered-response automation in Step 3 already pushes a notification when something fires the alarm. With PTZ wired up, you can chain a snapshot of whichever zone tripped first into that push so the alert that wakes you up has a face attached. Insert this between the existing "Push notification" step and the TTS step:
# 1b. Snapshot whichever zone tripped most recently
- action: camera.snapshot
target:
entity_id: camera.ptz_main
data:
filename: "/config/www/snapshots/alarm_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg"
- action: notify.mobile_app_magikh0e_phone
data:
title: "🚨 ALARM TRIGGERED"
message: "Snapshot from PTZ — {{ now().strftime('%I:%M %p') }}"
data:
image: "/local/snapshots/alarm_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg"
priority: high
ttl: 0
PTZ caveats
1. Mechanical wear.
The gimbal motors aren't rated for thousands of trips per day.
The queued / max: 5 mode on the tracking automation is a
soft limit; if a zone fires every minute, consider a
throttle condition (e.g., "not within 30s of last
run") to keep the motors alive.
2. Settling time.
The 3-second delay before the snapshot is the camera's pan
speed plus its autofocus settle time. Fast PTZs (Reolink RLC-823A)
can do ~1.5s; slower gimbals (Hikvision IP Speed Domes) need 4–5s.
Tune to your specific camera.
3. Snapshot path.
/config/www/ is HA's web-exposed directory — files
dropped there are reachable at /local/<path> from
the LAN, which is what the mobile app fetches the notification
image from. Different paths break the attached-image flow.
4. Storage.
One snapshot per zone-trip adds up. Pair this with a daily
automation that wipes /config/www/snapshots/ entries
older than N days, or pipe to S3 / a NAS share if you want
to keep evidence longer.
5. mmWave false positives.
Ceiling fans, HVAC vents, water vapor from showers, and curtain
movement can all trigger 24GHz radar. Place sensors thoughtfully
and use the per-sensor sensitivity sliders. PIR is more
selective by nature but pays for it with dwell-time blindness.
[ Step 6 — Privacy mode (camera off when home) ]
Step 5 wakes the camera up when the alarm is armed and someone trips a
zone. Step 6 is the inverse: as soon as anyone is home, the
camera goes into a privacy mode that keeps it from filming you making
coffee, walking to the bathroom, or reading on the couch.
Two signals decide whether someone's home:
Phone presence — the Home Assistant companion app
publishes person.<you> with state home /
not_home based on phone GPS + your defined Home zone. Combine
multiple device_tracker.* entities into a single
person.* for redundancy (phone + watch + laptop — if
any of them is home, you're home).
Indoor presence sensors — the same mmWave / PIR sensors
driving Step 5's tracking. Useful for guests, kids, and roommates
who don't have HA on their phone. Also catches the case where your
phone died but you're physically inside.
A template binary_sensor folds both into a single
binary_sensor.someone_home that the privacy automations watch.
The delay_off grace period prevents the sensor from flickering
off every time a presence sensor cools down between motion events.
What "privacy mode" means in YAML
Privacy mode is a stack — layer as many of these as your hardware
supports, because each one alone has a failure mode the others cover:
1. PTZ to a wall-facing preset (do this always).
Save a preset (e.g., slot 99) where the gimbal physically points
at a blank wall, the ceiling, or into a sock. For cameras
without a hardware shutter, this preset IS your privacy
mechanism — the lens still sees, but it sees nothing
useful. Pick a wall, not a ceiling, if your wall is closer than
the camera's minimum focus distance — out-of-focus blur
is bonus privacy.
2. Hardware shutter (if your camera has one).
Eufy indoor cams, Reolink E1 Outdoor, and a few others have a
motorized lens cover. Most integrations expose this as
switch.<cam>_privacy_mode. This is the only
privacy mechanism that's cryptographically certain — the
lens is physically blind regardless of firmware state.
3. Stop recording at the camera.
Vendor switches like switch.<cam>_record,
_motion_detection, _record_audio,
_ftp_upload, _email. Toggling these stops the
camera from persisting clips locally or shipping them to a
cloud you don't want them on.
4. Stop the stream into HA.
camera.turn_off tells the integration to stop pulling
RTSP from the camera. Kills any HA-side recording (Frigate,
LLAT, NVR add-on) without touching the camera itself.
5. Disable the Step 5 tracking automation.
Belt-and-suspenders: even if a frame somehow leaked through,
no automation will snap it and push it to your phone.
The example below stacks all five.
# configuration.yaml — composite "someone is home" sensor
template:
- binary_sensor:
- name: "Someone Home"
unique_id: someone_home
# ON when ANY tracker says home OR ANY presence sensor is active
state: >-
{{ is_state('person.magikh0e', 'home')
or is_state('person.partner', 'home')
or is_state('binary_sensor.living_room_presence', 'on')
or is_state('binary_sensor.bedroom_presence', 'on')
or is_state('binary_sensor.kitchen_motion', 'on')
or is_state('binary_sensor.hallway_presence', 'on') }}
# 5-minute grace so a brief presence dropout doesn't
# spin the camera up while you're still in the room.
delay_off: "00:05:00"
# automations.yaml — privacy mode
# Engage privacy mode the moment anyone is home
- alias: "Privacy: Engage when someone home"
description: "Wall-face the gimbal, kill recording + stream, disable tracking"
mode: single
trigger:
- platform: state
entity_id: binary_sensor.someone_home
to: "on"
action:
# 1. Pivot the lens to the wall-facing privacy preset.
# For cameras without a hardware shutter, this is the
# primary privacy mechanism — make sure the preset is
# actually saved and points somewhere blind.
- action: onvif.ptz
target:
entity_id: camera.ptz_main
data:
preset_token: "99"
# 2. Hardware shutter (if your camera has one — skipped
# silently otherwise via continue_on_error)
- action: switch.turn_on
target:
entity_id: switch.ptz_main_privacy_mode
continue_on_error: true
# 3. Stop recording at the camera. List every persistence
# switch your model exposes — recording, motion-triggered
# clips, audio capture, FTP upload, email alerts. Skip any
# switch your camera doesn't have via continue_on_error.
- action: switch.turn_off
target:
entity_id:
- switch.ptz_main_record
- switch.ptz_main_motion_detection
- switch.ptz_main_record_audio
- switch.ptz_main_ftp_upload
- switch.ptz_main_email
continue_on_error: true
# 4. Stop HA from pulling the RTSP stream. Kills any HA-side
# NVR / Frigate / LLAT recording. The camera itself is
# still up but no frames flow into HA.
- action: camera.turn_off
target:
entity_id: camera.ptz_main
# 5. Disable the Step 5 tracking automation so motion sensors
# can't trigger a snapshot pipeline (would fail anyway with
# the stream off, but no point spamming the log)
- action: automation.turn_off
target:
entity_id: automation.ptz_track_motion_to_zone
# 6. Confirm via push (one-time, no repeat) so you know it
# engaged. Comment out once you trust it.
- action: notify.mobile_app_magikh0e_phone
data:
title: "🔒 Privacy mode"
message: "Camera blinded — someone home"
# Disengage when the house has been empty for the grace window
- alias: "Privacy: Disengage when no one home"
description: "Reverse all five privacy layers once everyone has left"
mode: single
trigger:
- platform: state
entity_id: binary_sensor.someone_home
to: "off"
action:
# 1. Re-enable HA's stream pull first — the rest needs the
# camera entity to be available
- action: camera.turn_on
target:
entity_id: camera.ptz_main
# 2. Lift the hardware shutter
- action: switch.turn_off
target:
entity_id: switch.ptz_main_privacy_mode
continue_on_error: true
# 3. Re-enable camera-side recording + alerts
- action: switch.turn_on
target:
entity_id:
- switch.ptz_main_record
- switch.ptz_main_motion_detection
- switch.ptz_main_record_audio
- switch.ptz_main_ftp_upload
- switch.ptz_main_email
continue_on_error: true
# 4. Park the gimbal at the home / idle preset
- action: onvif.ptz
target:
entity_id: camera.ptz_main
data:
preset_token: "0"
# 5. Re-enable Step 5 tracking
- action: automation.turn_on
target:
entity_id: automation.ptz_track_motion_to_zone
- action: notify.mobile_app_magikh0e_phone
data:
title: "🎥 Privacy mode lifted"
message: "House is empty — surveillance back on"
Privacy caveats
1. Phone GPS is not instant.
Cellular GPS-based device_tracker updates can lag
30–90 seconds when you walk in the door, especially
in dense urban areas. The mmWave sensor backstop kicks
privacy mode in fast even when GPS is still catching up.
If you're paranoid, add a Wi-Fi-based zone trigger
(router DHCP-lease integration) for instant detection.
2. Hardware shutter beats software.
A camera that physically closes its lens (Eufy, Reolink E1
Outdoor) gives you cryptographic certainty the lens is
blind. Software-only privacy modes are still trusting
the firmware not to silently keep streaming.
3. Test the privacy preset.
Before trusting it, view the live stream while privacy mode
is engaged and confirm the camera is actually pointed
somewhere harmless. This matters most for no-shutter
cameras — the preset IS your only privacy mechanism,
and any drift past the wall edge means the lens is back on
the room. Step 5's gimbal drives accumulate step-counter
error over time. Re-save the preset every few months and
spot-check the live stream as a routine.
4. Guest mode.
If a guest is over and you want surveillance off but their
phone isn't tracked, the indoor sensors still flag
someone_home and engage privacy mode. That's the
right default. To toggle off manually for a long
vacation while a house-sitter is over, expose
input_boolean.privacy_override and OR it into the
template above.
5. Fail-safe orientation.
If HA crashes mid-engage, the camera might be left in
whatever state it was last commanded into. Decide whether
your default-on-failure is "privacy preserved" or
"surveillance running" and arrange the trigger
directions accordingly. The example above prefers
surveillance-on as the failure default (trigger on
off restores the camera) — flip if you'd
rather privacy-on be the default.
[ Optional — Auto-arm ]
A scheduled auto-arm at midnight, in case you forget. Fires only if the panel is currently disarmed — won't disturb a manually-set state. Pair this with a presence-based armed_away automation for full hands-off coverage.
# Auto-arm at midnight if not already armed
- alias: "Alarm: Auto-arm at night"
trigger:
- platform: time
at: "00:00:00"
condition:
- condition: state
entity_id: alarm_control_panel.home_alarm
state: disarmed
action:
- action: alarm_control_panel.alarm_arm_night
target:
entity_id: alarm_control_panel.home_alarm
data:
code: !secret alarm_code
[ ⚠ Caveats ]
Things to know before you trust this with your sleep schedule.
1. This is not a monitored alarm.
No central station, no police dispatch — just push notifications to
your phone. For real security, layer this with a monitored service
(SimpliSafe, Ring, etc.) or route notifications through a service
you'll actually wake up for.
2. Single point of failure.
If HA is down, your network is dropped, or your phone is on
do-not-disturb, the alarm doesn't work. Real systems carry cellular
backup. This one carries hopes and dreams.
3. Wi-Fi cameras drop offline.
If your motion sensor is built into a Wi-Fi camera, it'll go
unavailable periodically. Worth layering on a "device unavailable"
alert so you know if a sensor stops reporting:
- alias: "Alarm: Sensor offline"
trigger:
- platform: state
entity_id: binary_sensor.camera_e1_5295_motion_sensor
to: "unavailable"
for: "00:05:00"
action:
- action: notify.mobile_app_magikh0e_phone
data:
message: "Kitchen motion sensor has been offline for 5+ min"
4. TTS provider — local Piper.
The examples use tts.piper, a local neural TTS that runs
in HA via the Wyoming protocol. Install it from Settings →
Add-ons → Piper, then configure the integration so the
tts.piper entity exists. Everything stays on-LAN — no
internet round-trip per announcement, no subscription. If you'd
rather use a hosted voice, swap to tts.home_assistant_cloud
(Nabu Casa) or tts.google_translate_en_com (free, but
phones home to Google for every utterance). The
media_content_id URL prefix is the only thing that
changes.
5. No real siren.
TTS through a Nest display + flashing lights is a deterrent — not a
real deterrent. A Zigbee siren ($25 — Heiman, Frient, etc.)
wired into the response automation is a serious upgrade. Add a
switch.alarm_siren to the armed_triggered action and the
burglar gets a 110 dB welcome instead of a friendly TTS voice.
6. Code in plaintext.
!secret alarm_code reads from secrets.yaml — that file
belongs in your local backup workflow. Don't commit it to git, don't
paste it into screenshots. The code only protects against casual
dashboard access; anyone with shell on your HA box can read it.
