Why does my template sensor always update twice?

I am desperately trying to create a template sensor that looks for unavailable entities and then looks up the corresponding devices. I have also created an automation that monitors state changes of this sensor and then notifies me when a device goes offline. I know it’s not perfect and it’s still a work in progress, but at least I got a little closer to my goal.

You’ll find the template sensor below, and here’s the problem: I’m seeing some behavior that I can’t really explain. The template sensor seems to work, but the automation always runs twice. The first time, a new device is added to the attribute list of unavailable devices, but the state remains the same. The second time, only the state changes, but the attributes list doesn’t. I don’t really understand how this is possible, since the logic for getting the state and the attribute is exactly the same. Perhaps someone here has an idea?

Here is the template sensor:

template:
  - sensor:
      - name: "Unavailable Devices"
        unique_id: unavailable_devices
        icon: "mdi:lan-disconnect"
        state_class: measurement
        unit_of_measurement: entities
        state: >
            {% set ignore_seconds = 60 %}
            {% set ignore_ts = (now().timestamp() - ignore_seconds)|as_datetime %}
            {% set entities = states
                |rejectattr('domain','in',['button','event','group','input_button','input_text','scene'])
                |rejectattr('last_changed','ge',ignore_ts) %}
            {% set unavailable = entities|map(attribute='entity_id')|reject('has_value')|list|sort %}
            {%- set ns = namespace(list = []) %} 
            {%- for entity in unavailable %}
              {%- if device_id(entity) %}
                {%- set name = device_attr(device_id(entity), 'name_by_user') %}
                {%- if name and name not in ns.list|join %}
                  {%- set ns.list = ns.list + [name] %}
                {%- endif %}    
              {%- endif %}
            {%- endfor %}
            {{ ns.list|count }}
        attributes:
          device_id: >
            {% set ignore_seconds = 60 %}
            {% set ignore_ts = (now().timestamp() - ignore_seconds)|as_datetime %}
            {% set entities = states
                |rejectattr('domain','in',['button','event','group','input_button','input_text','scene'])
                |rejectattr('last_changed','ge',ignore_ts) %}
            {% set unavailable = entities|map(attribute='entity_id')|reject('has_value')|list|sort %}
            {%- set ns = namespace(list = []) %} 
            {%- for entity in unavailable %}
              {%- if device_id(entity) %}
                {%- set name = device_attr(device_id(entity), 'name_by_user') %}
                {%- if name and name not in ns.list|join %}
                  {%- set ns.list = ns.list + [name] %}
                {%- endif %}    
              {%- endif %}
            {%- endfor %}
            {{ ns.list | sort}}

And here are the state changes the last time it triggered twice:

First run:

  from_state:
    entity_id: sensor.unavailable_devices
    state: '9'
    attributes:
      state_class: measurement
      device_id:
        - Eglo RGB 8
        - Galaxy Watch
        - Paulmann CT 6
        - Nous Steckdose 3
        - Nous Steckdose 4
        - Aqara Cube
        - Ikea Rodret 1
        - Galaxy
        - Xiaomi Lumi 2

The device “Sonoff Temperature Sensor” got added to the attributes:

  to_state:
    entity_id: sensor.unavailable_devices
    state: '9'
    attributes:
      state_class: measurement
      device_id:
        - Eglo RGB 8
        - Galaxy Watch
        - Paulmann CT 6
        - Nous Steckdose 3
        - Nous Steckdose 4
        - Aqara Cube
        - Ikea Rodret 1
        - Galaxy
        - Sonoff Temperature Sensor
        - Xiaomi Lumi 2

Second run:

  from_state:
    entity_id: sensor.unavailable_devices
    state: '9'
    attributes:
      state_class: measurement
      device_id:
        - Eglo RGB 8
        - Galaxy Watch
        - Paulmann CT 6
        - Nous Steckdose 3
        - Nous Steckdose 4
        - Aqara Cube
        - Ikea Rodret 1
        - Galaxy
        - Sonoff Temperature Sensor
        - Xiaomi Lumi 2

The state changes from 9 to 10:

  from_state:
    entity_id: sensor.unavailable_devices
    state: '10'
    attributes:
      state_class: measurement
      device_id:
        - Eglo RGB 8
        - Galaxy Watch
        - Paulmann CT 6
        - Nous Steckdose 3
        - Nous Steckdose 4
        - Aqara Cube
        - Ikea Rodret 1
        - Galaxy
        - Sonoff Temperature Sensor
        - Xiaomi Lumi 2

I think you need to see the state and attribute templates as two different automations on the same sensor that each control its own part of the sensor and not the whole template as one automations that is run each time a change occurs.

BTW. why are you doing a sort in the line below?
It looks kind of unnecessary and the list might be kind big too at that point.

Thanks. That’s very good to know for the future. I guess I need to change the automation so that it only detects changes in the attribute.

Yes, it is indeed not necessary. Thanks for pointing that out!