Outside-Lights Alexa Switches — Design¶
Date: 2026-07-21 Status: Approved, pending implementation plan Related: docs/superpowers/specs/2026-07-13-exterior-lights-renderer-design.md (the renderer this rides on), CLAUDE.md §9 (Exterior Lights) and "Alexa Voice Exposure"
Problem¶
Two on-demand scenes — scene.outdoor_lights_on / scene.outdoor_lights_off (covering switch.string_lights + light.front_flood_light + light.front_porch_light) — are the only Alexa handle on the outdoor lights. Scenes are clumsy over voice ("Alexa, activate Outdoor Lights On/Off" — two separate things you activate, never toggle), and they carry no state. The user wants natural toggle control: "Alexa, turn the outside lights on/off," plus dedicated voice control of the backyard string lights and the front flood, and voice control of the interior foyer light.
All the exterior fixtures are owned by script.exterior_lights_render, which re-asserts the dusk→dawn / dusk→bedtime schedule on a 30-min self-heal tick and at every civil dusk/dawn edge. A naïve switch that just toggles a fixture is reclaimed by that tick within ~30 min — the same weakness the scenes already have (documented in scenes.yaml).
Decisions (from brainstorming)¶
- Override behavior: Sticky until next schedule edge. A manual on/off suppresses only the 30-min self-heal tick; the next civil dusk/dawn (or bedtime) edge reclaims the fixtures on the schedule. Chosen over "best-effort" (too weak) and full Phase-2 per-fixture manual hold (too big).
- Master switch scope: Match the deprecated scenes exactly —
switch.string_lights+light.front_flood_light+light.front_porch_light. The renderer's side/watch security light stays renderer-only. - Foyer & flood representation: Foyer exposed as its plain
light.foyer_light(interior, not renderer-managed — a raw exposure sticks fine). Front flood wrapped in a template switch with the hold (renderer-managed, so it needs the hold to be useful). - Master state semantics: "on if any member is on" (one tap = all off).
- Backyard switch: dumb on/off (no bedtime logic of its own — the renderer +
sleepingedge already handle bedtime, and the hold clears onsleep_on).
Discovered during implementation (spec amended 2026-07-21)¶
Two pieces of anticipated infrastructure already exist and are reused rather than duplicated:
input_boolean.exterior_lights_manual("Exterior Lights Manual Override") already exists inconfiguration.yaml(currently inert), surfaced as a toggle + a "· manual" badge on the command dashboard's Lights tab, built as the hook for ticket #90. This work wires it up — no newexterior_lights_manual_holdflag.- Ticket #90 ("Add turn on/off the backyard lights script, Alexa-invocable") covers the backyard portion of this request. This change supersedes #90, delivering it as a template switch (better than a scene — a switch supports both on/off verbs).
configuration.yamlalready has a top-leveltemplate:block (line ~645), so the template switches go inline there — no new package file, noci.ymlHA_FILES/regex change.
Also corrected a bug in the original spec: gating only the 30-min self-heal tick would not hold a manual state against the sleep_on / sleeping→off / start renders (they'd re-assert the schedule at bedtime). The correct "sticky until next schedule edge" behavior stands the renderer fully down while the flag is on, reclaiming only at the civil dusk/dawn edges.
Architecture¶
One coherent change layered on the existing renderer. No new renderer, no per-fixture state machine.
1. Manual-override flag — reuse input_boolean.exterior_lights_manual¶
The existing (inert) input_boolean.exterior_lights_manual helper is wired up: - restore_state (no initial:), so a manual override survives an HA restart mid-evening (already defined this way). - Semantics: while on, automation.exterior_lights_render_triggers stands the renderer down on every trigger except the civil dusk/dawn edges — so a manual change persists (self-heal tick, sleep_on, sleeping→off, and start all skip rendering). - Cleared automatically at the next civil dusk or civil dawn edge (which then renders and reclaims the schedule), and by Good Morning (script.wake_house, matching the office-lamp manual pattern the flag's own comment documents). - Set on by any of the three template switches when operated (either verb = a manual action to hold).
Trade-off (documented): a single global flag means a manual backyard-on in the evening also suppresses the bedtime (sleep_on) turn-off until the next dusk/dawn — the string lights stay on for the night rather than cutting at bedtime. Acceptable: if you deliberately touched the outside lights, your choice holds until the day/night boundary. Per-group holds are explicitly out of scope.
2. Template switches¶
Three template switches, each with a unique_id (so each gets a core.entity_registry entry — required for the authoritative Alexa exposure store — and is renameable in the UI). Each turn_on/turn_off sequence: set the hold flag on, then homeassistant.turn_on/homeassistant.turn_off its target set. value_template = "any member on."
| Entity | Friendly / Alexa name | Members |
|---|---|---|
switch.outside_lights | Outside Lights (master, replaces the 2 scenes) | switch.string_lights, light.front_flood_light, light.front_porch_light |
switch.backyard_lights | Backyard Lights | switch.string_lights |
switch.front_flood_lights | Front Flood Lights | light.front_flood_light |
Names are hyphen-free / plain words — Amazon silently skips Alexa entities whose names contain special characters.
Placement: inline in the existing top-level template: block in configuration.yaml (line ~645). No new package file, no ci.yml HA_FILES/regex change. Each turn_on/turn_off sequence turns input_boolean.exterior_lights_manual on, then commands its target set.
3. Renderer wiring — automation.exterior_lights_render_triggers (automations.yaml)¶
No new triggers (sun elevation, sleeping, tick, start are already triggers). In the actions:, after the existing backyard-latch choose: block:
- Clear the override at the day/night edges. When
trigger.idisduskordawn, turn offinput_boolean.exterior_lights_manual(then the render below reclaims the schedule). - Stand the renderer down while the override holds. If
exterior_lights_manualis on andtrigger.idis notdusk/dawn,stopbefore rendering — so the tick,sleep_on,sleeping→off, andstarttriggers don't stomp the manual state. (This makes the existing self-heal-tick gate redundant for the manual case but it stays for the self-heal toggle #89.) script.wake_housealso turns the flag off on the morning wake (one line added to its existing manual-flag clear list, matchingcorner_lamp_manual/office_lamp_manual).
Trigger flow after the change: - Manual off at 21:00 → flag on, fixtures off, all non-edge renders skipped → stays off until civil dawn (dawn clears the flag + renders → security off anyway). Effectively "off for the night." - Manual on at noon → flag on, fixture on, renders skipped → civil dusk clears the flag + renders (security on), civil dawn turns off. A daytime "on" lasts until next dawn.
4. Deprecate the scenes¶
Delete outdoor_lights_on / outdoor_lights_off from scenes.yaml (and its header comment). Verify no remaining references before deleting: we_are_leaving and other scripts don't use them (grep-confirmed); also check the YAML dashboards and home_command_dashboard.json.
5. Alexa exposure (live .storage operation — NOT deployed by CI)¶
Via WS homeassistant/expose_entity (writes core.entity_registry.options["cloud.alexa"].should_expose — the authoritative store; homeassistant.exposed_entities alone can carry a stale flag HA ignores) followed by cloud/alexa/sync, then re-discover in the Alexa app:
- Add:
switch.outside_lights,switch.backyard_lights,switch.front_flood_lights,light.foyer_light - Remove:
scene.outdoor_lights_on,scene.outdoor_lights_off
Template switches only get a registry entry once deployed and instantiated, so exposure happens after the config deploy lands and HA restarts. Verify the live list afterward: ssh haos 'jq -r ".data.entities[] | select(.options[\"cloud.alexa\"].should_expose==true) | .entity_id" /config/.storage/core.entity_registry | sort'
6. Tests¶
- New template-switch entity_ids won't exist in
tests/fixtures/entities.txtat pre-deploy test time. Add the three switch ids totests/fixtures/entities_allow.txtwith a justifying comment so the Tier-3 entity-ref test passes; after the config deploys and HA restarts, runmake snapshotand commit the refreshedentities.txt, then drop the allowlist entries. - Removing the two scenes drops
scene.outdoor_lights_on/offfrom the registry — refresh the snapshot in the same pass so the entities file stays clean. - The template switches reference
input_boolean.exterior_lights_manual(exists) +switch.string_lights/light.front_flood_light/light.front_porch_light(all exist) — no new referenced-entity gaps.
7. Docs¶
- CLAUDE.md: §9 Exterior Lights (manual-hold behavior + the three switches), "Alexa Voice Exposure" index (add the 4, remove the 2 scenes, fix counts), and the
group.lightsnote if relevant. docs/lighting.md: the "Full Lighting Schedule" section and any Alexa/voice list.docs/automations.md: the exterior-lights render-triggers reference (hold gate + clear-on-edge).docs/superpowers/specs/2026-07-13-exterior-lights-renderer-design.md: note the manual-hold Phase-2-lite addition.
8. Ticket¶
Create the GitHub issue describing this work, add it to the "Blairmont Smart Home" project board, assign louisalexander, move it to "In Progress". Because it supersedes #90 (backyard lights, Alexa-invocable), reference #90 in the new issue and close #90 as superseded (delivered here as switch.backyard_lights).
Out of scope (explicit)¶
- Per-group / per-fixture holds — one global
exterior_lights_manual_holdonly. Turning any one switch off briefly suppresses self-heal for all exterior fixtures until the next edge; self-heal only recovers a switch that flapped tounavailable, so this is low-risk. - Foyer output-mode fix —
select.foyer_light_output_modeis likely set to a dimming mode, makinglight.foyer_lightpresent a phantom brightness slider. Correcting it to On/Off is an optional follow-up, not part of this change (exposing to Alexa works either way; on/off is unaffected). - Full Phase-2 exterior manual-override system (dashboard toggle, indefinite hold) — deferred.
Success criteria¶
- "Alexa, turn the outside lights off" turns off string + front flood + front porch and they stay off (no ~30-min reclaim), until the next civil dusk/dawn edge.
- "Alexa, turn on the backyard lights" and "…front flood lights" work as toggles with the same stickiness.
- "Alexa, turn on the foyer light" works.
- The two
outdoor_lights_*scenes no longer exist and are gone from Alexa. make testpasses; CI deploys; live Alexa exposure list matches the CLAUDE.md index.