Home automation field notes All concepts

review

Ten readers, one afternoon

What ten independent readers found in one afternoon, what they got wrong, and why the reader is never the instrument.

Once a month or so the system gets a review. This time it got ten reviewers at once: ten independent, read-only readers, each handed one domain (security, watchdogs, automations at rest, automations in motion, templates, storage, network, radios, scripts, dashboards), each told the same three things. Measure, do not read configuration. Label every claim as measured or as read. Change nothing. Two hours later there were ten reports, a hundred and fifty kilobytes of them, and a synthesis. This page is the part of it worth keeping.

Ten reviewers, one afternoon, one small computer that nearly ran out of memory hosting them. What they found was useful. What they got wrong was, in the end, more useful still, because it taught us to treat every finding as a hypothesis with a command attached.

The readers were wrong sometimes, which is the point

Two reports contradicted each other about whether the login-attempt limit was set (it was; one had read a stale comment, the other the live settings store). One declared a television's account an administrator; it was not. One reported two management services enabled on the router that had been disabled for months.

None of this is an argument against the exercise. It is the reason the synthesis re-checked every load-bearing claim by hand, from outside where the claim was about the outside, before anything was changed. A finding is a hypothesis with a command attached, and the command is the part you keep.

The exercise also had a cost the readers could not see: ten of them working at once pushed the small box that hosts everything to the edge of its swap. Four at a time is the number for next time.

Frozen sensors that were not frozen

An August incident had left a firm rule in the notes: command-line sensors "freeze", holding their last value while looking healthy, and only a reload fixes them. The reviewer who tested this found eleven sensors apparently frozen for two hours, then rendered the same last_reported inside the template engine and found every one of them fresh.

The REST endpoint serves a cached snapshot of each state that only advances when the state or its attributes change; an unchanged poll leaves it untouched. A reload "fixed" the freeze because it created new state objects and therefore new snapshots. The August diagnosis was, in all likelihood, the measuring instrument. The on-box detector, which renders inside the engine, was right all along and had never fired.

Measure staleness where the state lives. Anything that reads "last reported" through an API cache will manufacture frozen sensors; ask the template engine, which sees the real object.

The zombie detector, blind since July

A daily audit pings mains-powered Zigbee devices and checks when battery ones were last heard, to catch nodes that die while their last state lives on. It had reported nothing for six weeks.

The reviewer found why: the coordinator's configuration had been rewritten by its own frontend the day the setting was added, dropping the option that timestamps every message, and the audit script had a deliberate downgrade: if more than half the devices lack a timestamp, treat it as "still propagating" and log an informational line. "No data for all twenty-nine devices" is not propagation. It is the detector describing its own blindness in a friendly tone.

"No data for everyone" is a fault. A watchdog that can distinguish "not yet" from "never" must say so loudly, and the settings a watchdog depends on belong under the same version control as the watchdog.

The forecast that never expired

A severe-weather outlook is scraped every three hours and a binary sensor says whether the country is under a level. The scraper picks the outlook valid now, or, failing that, the newest one. For four days there was no outlook valid now, so the newest, a four-day-old level two, was re-issued every three hours, the binary sensor stayed on, a storm alert for an open window stayed armed, and the house spirit went to bed anxious every evening. The fix was an expired attribute on the scraper's output and a consumer that checks the validity window instead of trusting the presence of a number.

A model against twelve neighbours

The remote flat's ventilation advice compares indoor particulate matter with outdoor, and had three outdoor sources in order of preference: a modelled value from a large provider, the median of a dozen community sensors nearby, and one enthusiast's sensor. Over the previous week the model ran 3.4 times higher than the median, above it in 98 percent of samples.

The advice was therefore structurally "keep the windows shut". Reordering the sources to prefer the sensors that actually stand in that neighbourhood flipped the advice the same minute, and the disagreement itself is now an attribute, because two sources that disagree by a factor of three are a finding, not a rounding error.

Two hundred thousand lightning strikes in a settings file

The lightning integration creates a transient entity for every strike within its radius. Each time the voice assistant enumerated entities, it wrote a permanent "do not expose" record for whatever transient strikes existed at that moment, and nothing ever removed them. The exposure settings file had grown to 43 megabytes and 224,727 entries, rewritten in full on every save.

A prune with the core stopped brought it back to seven kilobytes; the radius was halved and the strikes excluded from the recorder for good measure. The general shape is worth naming: transient entities meeting a permanent registry. Any place that stores "a setting per entity" will eventually hold a setting per thing that no longer exists.

Channels without retry

The critical alert channel, the one meant to survive the phone's battery saver, sent one message per alert with a "continue on error". A single timed-out send during a night storm dropped the alert silently; the phone leg delivered, the backup leg did not, and the nightly reviewer misdated the failure the next morning. The dead-man heartbeat missed four percent of its pings to the outside during one slow evening for the same reason.

Both now retry, and the critical channel checks the send's response rather than assuming; in the live test the first attempt timed out exactly as it had that night and the second delivered. The nightly reviewer itself gained the same treatment: three attempts and, on failure, a report that says no review instead of quietly keeping yesterday's.

Unattended updates and a dead reminder

The radio add-ons and the tunnel had been updating themselves at midnight. One such update recreated a container and, with it, silently lost a kernel setting injected from outside; a remote site lost its IPv6 path for four days. The self-heal that runs every ten minutes had failed just as silently: it masked its own error as success.

And the reminder that should have nagged every six hours could not fire, because its condition read the automation's own "last triggered" time, which the automation's own ten-minute timer refreshed on every run. A reminder that times itself from its last run instead of from its last notification is dead code that looks alive. It now times from the notification, the self-heal reports failure as failure, and the radio add-ons update only when someone is home to watch.

The exercise will be repeated, with four reviewers instead of ten, on the grounds that the box has feelings too.

A review is a list of measurements, not opinions. Every item above came with the command that produced it and was re-run before and after the change. The ones that would not survive that were dropped, and the ones the readers got wrong were the most useful reminder of all: the reader is not the instrument.