Smart Home, Dumb Luck, Episode 5: Improving Lighting Automations in Home Assistant
Home Assistant is a tinkerer’s dream. But let’s be honest. It does not have high spouse approval ratings. Especially when he or she wrote all the old lighting automations. So, in this episode we fix that. I hope.
I started with screenshots from the Google Home app. Some of the routines still existed, but several of the devices they controlled had already moved to Home Assistant. The app showed missing devices where the lights used to be. I was going to need a fairly sophisticated model to reason through what these were. The alternative was unacceptable: Asking the spouse.
The initial job was straightforward:
- Give Codex access to Home Assistant through MCP.
- Show it the screenshots and identify the missing devices.
- Recreate the routines in YAML, preserving the familiar behavior before improving it.
That worked. Then I asked what would make the routines more sophisticated without making them fragile. The answer involved some randomness, but the more interesting work was deciding handoff and failure scenarios. Quality of life improvements for the spousal win.
This continues the local-first Home Assistant project I started in Episode 1. The infrastructure is useful only when the everyday behavior makes sense.
Screenshots got the old behavior across
The original routines were simple simulated occupancy. A solar event started a sequence, lights came on across multiple rooms, and delayed actions turned them off later. Away routines checked whether everyone was out. A separate exterior lighting routine ran every day.
I kept that distinction. Exterior lighting has a daily job regardless of who is home. Simulated occupancy needs permission from presence data before it touches anything.
| Behavior | Original routines | Redesigned routines |
|---|---|---|
| Timing | Fixed offsets from solar events | Bounded daily variation around solar events |
| Activity | Separate timed sequences | An ordered progression, with optional secondary activity |
| Presence | Everyone-away condition at the start | Settled away status plus ongoing presence checks |
| Manual control | Delayed actions remained in the sequence | Observed manual changes release the affected light |
| Recovery | Long waits between actions | Saved deadlines and progress checked by a coordinator |
I also added a meal-like progression using lights that had never had Google Home routines. An earlier activity period leads into a later one, with an optional short return afterward.
The first pass reproduced the old behavior. Before the larger redesign, I had the agent back up the Home Assistant virtual machine using the separate backup node I recently built and retain the original automation definitions. The old away routines were disabled rather than deleted. That gave the new design a rollback path without requiring a whole-machine restore for an ordinary configuration mistake.
Randomness became a daily plan
Adding a random delay to each action is easy. It also makes the sequence harder to understand. Independent shifts can scramble a progression that is supposed to resemble someone moving through a house.
The redesigned system generates a daily plan:
- Save the solar reference times and bounded timing variations.
- Shift related on and off events together to preserve their duration.
- Keep dependent activities in order.
- Make some secondary activity optional.
The architecture and flow looks like this:
Daily planner
-> solar reference times + bounded variation
-> saved daily plan
|
v
Coordinator
-> check presence and manual-control state
-> process due events in order
-> save completion and light ownership
|
v
Local Home Assistant device actionsThe goal is durability. A random expression evaluated again might choose a different answer. A saved plan still describes the same evening.
I can also inspect the plan in a dashboard. That sounds mundane until the alternative is trying to explain why a light came on by reconstructing several random evaluations from automation traces.
There is no language model choosing what the house should do each evening. Codex helped build the configuration. Home Assistant runs the resulting rules locally. That follows the same division of responsibility I explored when comparing agent-driven control with Home Assistant Assist. The design keeps the AI out of the runtime dependency chain.
Presence now controls a handoff
Presence needed its own setup. Both household members now have a Home Assistant person associated with their own iPhone location tracker. I stopped treating other devices as useful proxies for whether a person was home.
Both phones had to work over local Wi-Fi and cellular. I set up remote access through a Cloudflare Tunnel and confirmed the apps could reach Home Assistant from outside the LAN. It’s an activity I was familiar with from setting up Coder. This time I used built-in MCP servers and let Codex fly. Wow. About 10x faster.
Notably, this puts a limit on the local-first claim. The lighting plan executes locally. Off-LAN phone updates still depend on connectivity and the remote access path using Cloudflare. Why? Yep, spousal approval factor. I wasn’t about to ask her to join my tailnet and run Tailscale permanently on her iPhone.
The presence rules are deliberately conservative:
- Both people must have known away states continuously for a settling period.
- Unknown or unavailable presence does not count as away.
- Current presence is checked again as the plan runs.
Coming home cancels the remaining events in an active simulated occupancy session. It leaves the current lights alone. Someone walking through the door should not inherit a delayed shutoff that was scheduled while the house was empty. Leaving again does not resurrect the canceled session either.
The handoff can be summarized without the scheduling machinery:
Someone comes home during an active session
-> cancel the remaining session events
-> release the system's light ownership
-> leave the current lights as they are
Someone leaves again
-> keep that session canceledThe automation also checks current presence during reconciliation. It does not rely exclusively on receiving one arrival event at precisely the right moment.
Manual control gives a light back to the person
The most useful rule is ownership. The system claims a light only when it finds that light off and turns it on itself. A light already on belongs to whoever put it there.
| What Home Assistant observes | What the system does |
|---|---|
| A light is already on when its activity starts | Leaves it alone and does not claim it |
| The system turns an off light on | Records ownership for the planned shutoff |
| A manual change to an owned light | Releases that light and removes it from the current session |
| The master switch is turned off | Cancels the away simulation and leaves current lighting unchanged |
Other parts of the plan can continue after a single light is released. I don’t need one manual adjustment to cancel the whole evening.
This has a practical boundary. Home Assistant can only react to changes the device integration reports. It cannot infer a physical interaction it never saw. That is a reason to test the actual devices, not a reason to write a more elaborate template.
I also caught a smaller assumption during implementation. One light exposed only on/off control. A proposed dimmed setting was therefore removed. The entity’s reported capabilities won over the nicer-sounding plan.
Recovery uses saved deadlines instead of long waits
The redesigned coordinator checks saved deadlines and progress rather than sitting inside hours-long delays. Recovery follows three rules:
- Missed starts have a limited catch-up window.
- Overdue shutoffs are eligible only while the system still owns the light and presence permits the action.
- Unavailable devices remain pending. An attempted command does not count as completed work.
The daily exterior routine has its own saved shutoff deadline and ownership flag. It stays independent of the away simulation.
I checked the schedule calculations in Home Assistant’s actual template engine. Here’s where the evidence stands:
| Check | Status | What it establishes |
|---|---|---|
| Extreme timing variations and event ordering | Checked in the template engine | The tested boundaries preserve the progression |
| Cancellation and ownership transitions | Checked with fixtures | The tested state changes produce the intended decisions |
| Real daily planner | Ran and saved its plan | The planner works in the live configuration |
| Coordinator while everyone was home | Completed without exercising lights | The home-state path runs without trace errors |
| Complete away evening, physical return, and restart during activity | Still to observe | These need household burn-in, not another template fixture |
Configuration checks and transition fixtures are not a substitute for that burn-in.
The outcome is a system that the spouse should approve. It has a visible plan, bounded variation, and rules for giving control back. Making it look a little less repetitive was the easy part. Making it respect the people who live here took the better design work.
Now we just need to test it. I guess it’s time to leave the house.
By the Numbers
- 4 original Google Home routines provided the starting behavior.
- 3 new activity patterns extended the away simulation.
- 2 person records supply household presence.
- 1 saved daily plan keeps timing variation stable across ordinary restarts.
- 0 language model calls are required to execute the lighting routines.
- 0 complete away evenings have been observed under the redesigned system so far.