Home automation field notes All concepts

triggers

Edge triggers lie to you

A pending timer lives in memory, and memory does not survive the restart you perform to fix something else. Edge triggers are promises; level triggers are facts. This is the difference, with casualties.

Most automation platforms give you triggers that fire on a transition: when a value crosses a threshold, when a state changes, when something has been true for a duration. These are edge triggers, and they are the natural way to express intent. They are also, strictly speaking, opinions held in RAM, and RAM’s loyalty ends at the next restart.

An edge trigger is a promise the system makes to itself at a particular moment, and like most promises made at a particular moment it does not survive a restart, a reload, or a Tuesday. A level trigger is not a promise. It is a fact that keeps being true until it is not. Almost every bug on this site can be summarised as somebody trusting a promise.

They also share a property the documentation mentions approximately never: the pending timer lives in memory. Reload your automations, restart the process, or let the entity blink to unavailable for one second, and the countdown silently starts again, or never arms at all.

Why this bites precisely when it matters

Consider a safety cut-off: "if this heater has been on for four hours, switch it off". Written as a state trigger with a four-hour duration, it works perfectly in testing.

Then reality:

Measured on one such device: the plug dropped to unavailable nineteen times in a single week, in the same second as a nearby sensor, because they shared a radio path. The safety cut-off had effectively never been armed for its full duration.

The fix: store a timestamp, compare against the clock

Instead of asking the platform to remember a countdown, record when the condition began, and re-evaluate periodically against the current time.

on genuine off→on edge:   store start_time = now
on off:                   clear start_time
every 5 minutes:          if start_time is set
                          and (now − start_time) > limit
                          → act

This is level-triggered thinking. It survives restarts, reloads and unavailability blips, because the state lives in persistent storage rather than in a scheduler.

Guard the edge itself

When you latch the start time, ignore transitions that came from unknown or unavailable, otherwise a device reconnecting looks like a fresh start and resets your clock. The same guard prevents a restart from producing a spurious "it just turned on".

A trap inside the trap: a freshly created datetime helper does not initialise to a far-past sentinel, it initialises to today at midnight (disputed by a review panel, then settled against the platform's source: the no-restore fallback is literally today's date with 00:00:00), which reads as "on for many hours" and fires your failsafe immediately. Set an explicit sentinel and treat implausible values as unset.

Keep both triggers

The edge trigger is still worth having: it reacts immediately. Add the periodic level check beside it, sharing the same action, and let the condition decide. You get fast response in the normal case and correctness in the abnormal one.

triggers:
  - state change → id: edge        # fast path
  - every 10 min → id: level       # restart-proof path
  - on startup   → id: level

condition: the situation is genuinely true right now,
           measured from a stored timestamp, not from a timer

Then stop the periodic path from nagging

A level trigger will happily re-fire every cycle while the condition persists. Add a cooldown that applies only to the periodic path, so the edge still fires immediately but the repeat is bounded.

The same defect in threshold alerts

Numeric threshold triggers ("above 90%") are edges too. If your disk is already at 94% when the system starts, no crossing occurs and the alert never fires, the exact scenario where you restarted because the disk was filling. Derive the tier from the current value on a schedule instead of waiting for a crossing that already happened.

If you take one sentence from this page, take this one: the state of the world is not an event, and a house that only listens for events will sleep through most of the world.

Rule of thumb: if an automation protects you from something, it must be able to notice a situation it did not personally watch begin.