Teaching the house when not to actessay

At 3am, the house dutifully announced a visitor at the front door. There was no visitor. I checked, in the tradition of horror-movie protagonists everywhere. Smart-home content is mostly theater (lights cascade on cue, blinds glide, a disembodied voice confirms), but living with automation is a different sport, because a family doesn’t grade the house on its best trick. They grade it on the one time it fought them, or woke them. After years of running ours, my bar is fixed: invisible when it’s right, harmless when it’s wrong.

The brains are Home Assistant, running in a virtual machine on the homelab server; it’s the one workload I refuse to containerize, since it wants a whole OS and a fistful of USB radios for the various smart-device protocols. The devices themselves live on a quarantined network segment. And the automations are now built the way everything else in the lab is: drafted and debugged with an AI model that can read the configuration and the device histories. An AI also reads the house’s energy meters every 20 minutes and writes a short plain-language insight into a custom energy dashboard, alongside a 10pm nightly report that lands in a Discord channel (your end-of-day wind-down may differ from mine). Here’s one night’s report, as the family sees it:

The 10pm Daily Energy Report in Discord: a plain-language paragraph summarizing the day’s solar production, battery behavior, grid usage, and estimated savings, followed by compact tiles for solar, battery, grid, savings, climate, outdoor conditions, overnight projections, and month-to-date totals.

The 3am ghost took some explaining. My camera system (UniFi Protect) stores the timestamp of the last doorbell ring as a sensor; when its integration drops and reconnects (which it does for OTAs 2-3x/week, as I unwisely subscribe to the Early Access channel), the sensor comes back by replaying the old value, and to a state-change trigger that replay is indistinguishable from a fresh ring. The result is a house announcing last week’s visitor tonight. The fix: a ring only counts if it happened within the last 2 minutes. The trade-off is real (a ring that arrives during a camera outage goes unannounced), but I’ll take it over the ghosts.

The rule that governs everything else in the house came from a door. If a door in my home stays open for ≥10 minutes, I shut off the HVAC (classic dad move); when the last door closes, I restore it. The naive version, however, hid a trap: what if someone turned the HVAC off themselves while the door happened to be open? A naive restore would then switch it back on, overriding a human who had made a deliberate choice. The fix is a flag that only the shutoff automation ever sets and the restore consumes. No flag, no restore; if the automation wasn’t the one who acted, the house keeps its hands off. That’s the closest thing I have to a design law for this domain: the house never gets to overrule a human. My own little law of robotics in practice.

The restraint layer: sensor events flow into Home Assistant, pass through guard conditions before any action, and an AI writes energy insights to a dashboard plus a nightly Discord report.

A subtler bug came from a related HVAC config. A guard automation watches for the thermostat being set below our HVAC operating range (e.g., 50°F) and corrects it. But when the HVAC restores from off, our particular model thermostat (an Ecobee) briefly surfaces a stale internal setpoint before the real one lands, and the guard would see that flicker, wait its 1-minute grace period, and then “correct” a problem that had already corrected itself, stepping on the restore in the process. The fix was one condition: after the grace period, re-check that the problematic setting still exists before acting. Automations to save me from my automations.

Writing an automation that acts is trivial; the craft is entirely in the conditions that keep it from acting at the wrong moment. Most of my automation code is, by line count, restraint.