Skip to content

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)

  1. 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).
  2. Master switch scope: Match the deprecated scenes exactlyswitch.string_lights + light.front_flood_light + light.front_porch_light. The renderer's side/watch security light stays renderer-only.
  3. 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).
  4. Master state semantics: "on if any member is on" (one tap = all off).
  5. Backyard switch: dumb on/off (no bedtime logic of its own — the renderer + sleeping edge already handle bedtime, and the hold clears on sleep_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 in configuration.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 new exterior_lights_manual_hold flag.
  • 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.yaml already has a top-level template: block (line ~645), so the template switches go inline there — no new package file, no ci.yml HA_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.id is dusk or dawn, turn off input_boolean.exterior_lights_manual (then the render below reclaims the schedule).
  • Stand the renderer down while the override holds. If exterior_lights_manual is on and trigger.id is not dusk/dawn, stop before rendering — so the tick, sleep_on, sleeping→off, and start triggers 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_house also turns the flag off on the morning wake (one line added to its existing manual-flag clear list, matching corner_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.txt at pre-deploy test time. Add the three switch ids to tests/fixtures/entities_allow.txt with a justifying comment so the Tier-3 entity-ref test passes; after the config deploys and HA restarts, run make snapshot and commit the refreshed entities.txt, then drop the allowlist entries.
  • Removing the two scenes drops scene.outdoor_lights_on/off from 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.lights note 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_hold only. 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 to unavailable, so this is low-risk.
  • Foyer output-mode fixselect.foyer_light_output_mode is likely set to a dimming mode, making light.foyer_light present 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 test passes; CI deploys; live Alexa exposure list matches the CLAUDE.md index.