Ring Alarm — Whole-House Audio Feedback¶
Date: 2026-07-05 Status: Design — approved, pending implementation plan Scope: automations.yaml (§22 Ring Alarm), scripts.yaml, configuration.yaml (helpers + dashboard registration), a new YAML dashboard, docs manual
Problem¶
Today the only audio feedback for the Ring alarm is a single one-shot Alexa announce on the kitchen Echo Show when alarm_control_panel.blairmont_alarm goes to triggered (automation #50). Everything else — arming, disarming, the entry/exit grace period, a failed arm — is silent from HA's side.
The Ring base station siren and the laundry-room keypad chirp natively during exit/entry delay and blare on trigger, but that sound only reaches the rooms near those two devices. The house has 7 Echoes (kitchen, family room, front room, garage, master bathroom, master bedroom, office) that can carry spoken alerts into every room the keypad can't.
Goal: give the alarm whole-house spoken feedback for its full lifecycle — louder/wider on trigger, arm/disarm confirmation, entry/exit boundary announcements, and a "failed to arm" heads-up — with every knob (enable, volume, repeat interval) live-tunable from a dedicated dashboard so it can be dialed in over days/weeks, and test buttons to preview each announcement without setting off the real alarm.
What the panel exposes (verified live)¶
alarm_control_panel.blairmont_alarm (ring-mqtt) reports:
- States:
disarmed,armed_home,armed_away,arming(exit delay),pending(entry delay),triggered. - Attributes:
exitSecondsLeft,entrySecondsLeft(live countdown),lastArmedBy,lastDisarmedBy,targetState.
Non-goals (deliberate)¶
- No literal per-second countdown ("30, 29, 28…"). Alexa
announcehas ~1–2s latency and cannot keep pace; the Ring keypad/base already chirps every second natively. HA does boundary announcements only (start of delay + one ~10s warning). - Sonos deferred. The 7 Echoes already cover the whole house. Sonos uses a separate cloud
tts.speak+ music-ducking path — added failure surface for redundancy we mostly already have. Ship Echoes first. - HA does not arm, disarm, or dispatch. Ring remains the brain (Ring Protect Pro dispatches). HA only reacts to state via ring-mqtt.
- Speaker assignment per event is fixed in config, not dashboard-tunable. The dashboard tunes enable + volume + interval; changing which room speaks is a code edit (YAGNI for a settings panel).
Architecture¶
Three layers, so both the real automations and the dashboard test buttons share one code path (mirrors how script.speak_briefing / script.night_lights_render centralize a capability):
1. script.alarm_speak(message, speakers, volume) — low-level fan-out¶
message(string) — text to speak.speakers(list) — target Echomedia_playerentities.volume(float 0.0–1.0) — level to set before announcing.
Behavior: media_player.volume_set on the targets → Alexa announce fan-out. Owns how the house talks. Nothing else.
Fan-out mechanism (validate at implementation): prefer the generic notify.alexa_media service with a target: list + data.type: announce (one call, all speakers). If the generic service isn't registered in this alexa_media build, fall back to per-device notify.alexa_media_<slug> calls (the pattern automation #50 uses today). data.type: announce = chime prefix + spoken, matching the #61 alexa_media migration.
2. script.alarm_audio_event(event, message, force) — mid-level policy¶
Given an event name and a ready-built message: - Stop unless input_boolean.alarm_audio_enabled (master) is on — unless force: true (test buttons bypass the master + per-event enable so a disabled event can still be previewed). - Stop unless that event's input_boolean.alarm_audio_<event>_enabled is on (again bypassed by force). - Resolve speakers from the event→speaker map (below) and volume from input_number.alarm_audio_<event>_volume. - Call script.alarm_speak.
Callers build the message because only they have the live vars (zones, target, seconds); this script owns enable-policy + speaker/volume resolution.
3. Automations — one per state transition¶
Each triggers on a state transition, builds the message, and calls script.alarm_audio_event. The trigger event additionally wraps the call in a repeat loop.
Events → behavior¶
ALL_ECHOES = the 7 Echo media_players (kitchen, family_room, front_room, garage, master_bathroom, master_bedroom, office).
| # | Trigger (state transition) | Speakers | Default volume | Spoken message |
|---|---|---|---|---|
| Trigger | to: triggered | ALL_ECHOES | 0.8 | "Security alert. The Ring alarm has been triggered. {zones}." — repeats every interval s while triggered |
| Exit delay | to: arming | front_room_echo | 0.6 | "Arming to {target}. You have {exitSecondsLeft} seconds to exit." + one "Ten seconds." near the end |
| Entry delay | to: pending | ALL_ECHOES | 0.8 | "Entry delay. Disarm now." |
| Armed | arming → armed_home/armed_away | master_bedroom_echo, office_echo | 0.3 | "Alarm armed to {home/away}." |
| Disarmed | to: disarmed (from armed_*/pending) | master_bedroom_echo, office_echo (announce) + ALL_ECHOES (volume reset only) | 0.3 | "Alarm disarmed." — also resets all 7 Echoes to the reset-volume default |
| Failed to arm | arming → disarmed | master_bedroom_echo, office_echo | 0.6 | "The alarm could not arm. Check doors and windows." |
Notes: - {zones} reuses the existing template in #50 (names any open Ring contact/motion sensor: binary_sensor.personnel_door, binary_sensor.foyer_motion_sensor — extend as Retrofit zones are added). - {target} = armed_home → "home", armed_away → "away" (from targetState). - Repeating trigger loop mirrors §51 (Night Lights Red Beacon): mode: restart, while: state == triggered → call alarm_audio_event → delay: {input_number.alarm_audio_trigger_interval} s. Loop ends when the alarm clears. - Disarmed vs Failed-to-arm both end in disarmed; disambiguated by from: (armed_*/pending → disarmed; arming → disarmed = failed/aborted). Distinct from: values prevent double-firing.
Volume strategy¶
Alexa announce plays at each Echo's current volume, so it must be forced. alexa_media can't cleanly snapshot-and-restore per-device volume, so we set a per-event level (from the helper) before announcing and reset at the natural all-clear:
- Per-event default volumes are the "Default volume" column above; each is a live
input_number(see helpers). - Reset point: on transition to
disarmed, reset all 7 Echoes toinput_number.alarm_audio_reset_volume(default 0.4). Disarming is the "all clear," so any loud event is followed by a clean reset — no fragile mid-announce restore.
Tunable settings (helpers)¶
All defined in configuration.yaml. No initial: on the numbers/booleans so dashboard tweaks persist across restarts (same convention as input_number.precool_high_threshold); set them once after first deploy.
| Helper | Type | Range / default | Purpose |
|---|---|---|---|
input_boolean.alarm_audio_enabled | bool | — | Master on/off for all alarm audio |
input_boolean.alarm_audio_trigger_enabled | bool | — | Enable trigger announcement |
input_boolean.alarm_audio_exit_enabled | bool | — | Enable exit-delay announcement |
input_boolean.alarm_audio_entry_enabled | bool | — | Enable entry-delay announcement |
input_boolean.alarm_audio_armed_enabled | bool | — | Enable armed confirmation |
input_boolean.alarm_audio_disarmed_enabled | bool | — | Enable disarmed confirmation |
input_boolean.alarm_audio_failed_enabled | bool | — | Enable failed-to-arm announcement |
input_number.alarm_audio_trigger_volume | number | 0–1, step 0.05 (0.8) | Trigger volume |
input_number.alarm_audio_exit_volume | number | 0–1 (0.6) | Exit-delay volume |
input_number.alarm_audio_entry_volume | number | 0–1 (0.8) | Entry-delay volume |
input_number.alarm_audio_armed_volume | number | 0–1 (0.3) | Armed volume |
input_number.alarm_audio_disarmed_volume | number | 0–1 (0.3) | Disarmed volume |
input_number.alarm_audio_failed_volume | number | 0–1 (0.6) | Failed-to-arm volume |
input_number.alarm_audio_reset_volume | number | 0–1 (0.4) | Echo volume restored on disarm |
input_number.alarm_audio_trigger_interval | number | 5–60 s, step 1 (12) | Seconds between trigger repeats |
input_button.alarm_audio_test_trigger | button | — | Preview trigger announcement |
input_button.alarm_audio_test_exit | button | — | Preview exit-delay announcement |
input_button.alarm_audio_test_entry | button | — | Preview entry-delay announcement |
input_button.alarm_audio_test_armed | button | — | Preview armed announcement |
input_button.alarm_audio_test_disarmed | button | — | Preview disarmed announcement |
input_button.alarm_audio_test_failed | button | — | Preview failed-to-arm announcement |
Test buttons: each input_button press fires an automation that calls script.alarm_audio_event(event, <canned sample message>, force=true) — plays that event's announcement on its assigned speakers at its current volume, regardless of enable state, with sample values for dynamic vars (e.g. "…60 seconds…"). This is the dial-in path: press, listen, adjust the slider, repeat — no real alarm needed.
Dashboard — separate YAML dashboard (NOT Command)¶
A new YAML-mode dashboard, distinct from the Command dashboard.
- File:
alarm_audio_dashboard.yaml(repo root, edited directly like the other YAML dashboards). - Registration: add under
lovelace.dashboardsinconfiguration.yaml— sidebar title "Alarm Audio",url_path: alarm-audio(must contain a hyphen orcheck_configfails and blocks deploy). - CI: add
alarm_audio_dashboard.yamlto the deployable HA files list /ha_configchanged-files matcher (deploy-ha job, fullha core restart), and to the dashboard entity-ref test intests/conftest.py(now 4 YAML dashboards).
Layout — one entities card per event (its built-in title = the event name; these are standard card headers, not standalone title cards), each grouping that event's enable toggle + volume slider + test button, plus a top card for the master toggle, reset volume, and trigger interval. Plain entities cards (no custom components) keep it robust:
┌ Master ───────────────────────┐
│ ⏻ Alarm Audio (master) │
│ 🔊 Reset volume [0.40] │
│ ⏱ Trigger interval [12s] │
└───────────────────────────────┘
┌ Trigger ──────────────────────┐ ┌ Exit delay ──────────────────┐
│ ⏻ Enabled │ │ ⏻ Enabled │
│ 🔊 Volume [0.80] │ │ 🔊 Volume [0.60] │
│ ▶ Test │ │ ▶ Test │
└───────────────────────────────┘ └──────────────────────────────┘
… Entry delay / Armed / Disarmed / Failed to arm, same shape …
Integration with existing automations¶
- Replace the single kitchen announce in automation #50 with the new repeating whole-house trigger loop. Keep #50's critical push to both phones + snapshot + persistent notification unchanged — only the audio action changes. No two competing trigger announcements.
- §51 Night Lights Red Beacon (visual) untouched, runs in parallel.
- Good Night / Good Morning routines unchanged — audio keys off the resulting state transition, so it also covers arming/disarming from the Ring app/keypad.
Failed-to-arm detection — risk & validation¶
Baseline signal: the arming → disarmed transition (exit-delay countdown aborted instead of reaching armed_*). Best-effort — a manual cancel during exit delay would also announce "could not arm." At implementation, verify ring-mqtt's behavior when arming with an open zone (stays disarmed / bypasses / exposes a fault attribute) and prefer a fault attribute if reliable. Otherwise the transition heuristic ships; a false positive on a rare manual cancel is low-cost.
Testing (pre-deploy suite)¶
- Tier 2 (structural): unique
id+aliason every new automation (including the 6 test-button automations). - Tier 3 (entity refs): the ~21 new
input_*helpers don't exist intests/fixtures/entities.txtuntil the instance restarts with the new config. Chicken-and-egg → add them totests/fixtures/entities_allow.txtwith a justifying comment; after first deploy,make snapshotand drop the allowlist entries. All 7media_player.*_echo+alarm_control_panel.blairmont_alarmalready exist.notify.alexa_media_*are services, not entity_ids (not checked). - New dashboard is entity-ref tested via
conftest.py— its referenced helpers must resolve (covered by the allowlist above). - Tier 1 (
check_config): helpers, both scripts, the automations, and the dashboard registration (hyphenatedurl_path) must pass container config check.
Docs (update in the same change — CLAUDE.md manual rule)¶
docs/notifications.md— the alarm audio alerts (what each says, when).docs/security.md— whole-house alarm audio behavior + the Alarm Audio tuning dashboard.docs/automations.md— §22 technical reference: the new automations, both scripts, the helpers.CLAUDE.md— add the "Alarm Audio" dashboard to the dashboards table + YAML-dashboard deploy list; note the audio primitive/helpers under §22.
Deploy¶
Edit automations.yaml, scripts.yaml, configuration.yaml, alarm_audio_dashboard.yaml; make test; commit; push. CI restarts HA (configuration.yaml/scripts.yaml changed). Docs deploy via docs.yml.
Open questions for implementation (not blocking the spec)¶
- Is generic
notify.alexa_media(withtarget:) registered here, or must we fan out per-device? - ring-mqtt's real "arm with open zone" behavior — confirm the failed-to-arm signal.
- Does
announcereliably respect avolume_setissued milliseconds prior, or is a shortdelayneeded between them?