Hardening Home Assistant Automations Against Unavailable States

If you've been using Home Assistant long enough, you've been woken up at 2am by a notification that shouldn't have fired. A sensor came back online, a service restarted, HA rebooted — and suddenly your phone is buzzing about a “power outage” that never happened.

The root cause is almost always the same: automations that don't account for the full lifecycle of entity states.

This post covers a hardening system I've developed to eliminate those false triggers — including from_state/to_state guards, the availability template pattern, and how to bake these protections into blueprints so every automation gets them for free.


The Problem: Entities Don't Just Toggle

Most automations are written assuming an entity transitions cleanly between on and off. In reality, entities go through a much messier lifecycle:

unavailable → on → off → unavailable → unknown → on

This happens constantly:

Each of these transitions can look like a real state change to an automation. Without guards, every one of them can fire your notification, trigger your lights, or run your scripts.


The Two-Guard Pattern

The most important hardening technique is guarding both the from_state and to_state of every trigger:

conditions:
  - condition: template
    value_template: >
      {{ trigger.from_state.state not in ['', 'unavailable', 'unknown'] }}
  - condition: template
    value_template: >
      {{ trigger.to_state.state not in ['', 'unavailable', 'unknown'] }}

Most people only guard to_state — checking that the new state isn't unavailable. But the from_state guard is equally important. Without it, the transition unavailable → on passes right through, which is exactly what happens when a sensor comes back online after a restart.

Both states must be real values for the automation to proceed.


The Availability Template Pattern

For template sensors, the old pattern for handling unavailable sources looked like this:

# Old pattern — prone to issues
state: >
  {% set s = states('sensor.some_entity') %}
  {% if s in ['unavailable', 'unknown', 'none'] %}
    {{ None }}
  {% else %}
    {{ s == 'on' }}
  {% endif %}

The problem is that returning None from a state template just sets the state to the string "None" — not actually unavailable. So you still get state transitions that can trigger automations.

The correct approach is to use the availability template, which is specifically designed for this:

state: >
  {{ states('sensor.some_entity') == 'on' }}
availability: >
  {{ states('sensor.some_entity') not in ['unavailable', 'unknown'] }}

When availability returns false, the sensor itself becomes unavailable in HA. This means the transition on restart becomes unavailable → on instead of off → on — and the from_state guard catches it.

For sensors with multiple dependencies, use the list pattern:

availability: >
  {% set entities = [
      'binary_sensor.some_sensor',
      'input_boolean.some_boolean',
      'switch.some_switch',
  ] %}
  {{ entities | select('is_state', 'unavailable') | list | count == 0
     and entities | select('is_state', 'unknown') | list | count == 0 }}

Baking Guards Into Blueprints

Writing these conditions into every automation manually is error-prone and inconsistent. The better approach is to encode them into blueprints so every automation instance gets them automatically.

Here's a hardened binary sensor blueprint:

blueprint:
  name: When Binary Sensor is Toggled
  description: |
    Triggers user-specified actions when a binary sensor changes to on and off.
    Ignores unavailable/unknown states and attribute-only changes.
  domain: automation
  input:
    binary_sensor:
      name: Binary Sensor
      selector:
        entity:
          domain: binary_sensor
    on_action:
      name: On Action
      default: []
      selector:
        action: {}
    off_action:
      name: Off Action
      default: []
      selector:
        action: {}

trigger:
  - platform: state
    entity_id: !input binary_sensor

condition:
  - condition: template
    value_template: >
      {{ trigger.from_state.state not in ['', 'unavailable', 'unknown'] }}
  - condition: template
    value_template: >
      {{ trigger.to_state.state not in ['', 'unavailable', 'unknown'] }}

action:
  - choose:
      - conditions:
          - condition: state
            entity_id: !input binary_sensor
            state: "on"
        sequence: !input on_action
      - conditions:
          - condition: state
            entity_id: !input binary_sensor
            state: "off"
        sequence: !input off_action
    default: []
mode: single

Every automation using this blueprint is hardened by default. You can create similar blueprints for input booleans, media players, outlets — anything you trigger off regularly.

For even simpler cases, a generic entity state change blueprint covers everything:

blueprint:
  name: When Entity State Changes
  description: |
    Triggers user-specified actions when any entity's state changes.
    Ignores unavailable/unknown states and attribute-only changes.
  domain: automation
  input:
    entity:
      name: Entity
      selector:
        entity: {}
    actions:
      name: Actions
      default: []
      selector:
        action: {}

trigger:
  - platform: state
    entity_id: !input entity

condition:
  - condition: template
    value_template: >
      {{ trigger.from_state.state not in ['', 'unavailable', 'unknown'] }}
  - condition: template
    value_template: >
      {{ trigger.to_state.state not in ['', 'unavailable', 'unknown'] }}

action: !input actions
mode: single

Automations using this blueprint become pure configuration:

alias: When There is a New Channels DVR Recording
use_blueprint:
  path: zackwag/entity_state_change.yaml
  input:
    entity: sensor.channels_dvr_latest_recording
    actions:
      - action: script.channels_dvr_new_recording_handler
        metadata: {}

When NOT to Use These Guards

Not every automation should use these guards. The pattern is deliberately designed to ignore unavailable — but sometimes you want to know when something goes unavailable.

A backup monitoring automation is a good example:

triggers:
  - trigger: state
    entity_id: sensor.container_backup_status
    to: failed
  - trigger: state
    entity_id: sensor.container_backup_status
    to: unavailable
  - trigger: state
    entity_id: sensor.container_backup_status
    to: unknown

If your backup service goes dark, that's exactly the alert you want. Adding the unavailability guards here would defeat the purpose. Use your judgment — the guards are the right default, but they're not universal.


The Full Defense Stack

For critical alerts like UPS power monitoring, you can combine all of these techniques into a robust defense:

  1. availability on template sensors — prevents unavailable from masquerading as off
  2. from_state guard — blocks transitions out of unavailable
  3. to_state guard — blocks transitions into unavailable
  4. Connectivity condition — suppresses alerts when the monitoring service itself is down
  5. Persistent last values — prevents MQTT services from republishing retained values on restart

Each layer handles a different failure mode. The result is an alerting system you can actually trust — when your phone buzzes, something real happened.


Summary

The core principles:

Once you've applied this system across your automations, the 2am false alerts stop. And when a real alert fires, you'll actually pay attention to it.