Help - Automation/Condition no longer working

Hi!
I used this automation a while ago, which sent me a notification when a Zigbee device was no longer reachable.
The peculiarity was that every so many hours it sent me the notification with the updated time.

For example:

Zigbee Devices Offline:
Bedroom Light - from 5 hours

After 2 hours…

Zigbee Devices Offline:
Bedroom Light - from 7 hours

Unfortunately it doesn’t work anymore. If I trigger the automation manually, only the message arrives but without the entity.

At the scheduled times, however, the message does not even arrive, the automation does not trigger (probably a problem with the condition?)

- alias: ALERT Zigbee Offline Devices
  id: zigbee_device_missing_alert
  trigger:
    - platform: time
      at: "07:00"
    - platform: time
      at: "16:00"
    - platform: time
      at: "20:00"
  condition:
    - condition: template
      value_template: > 
        {% set ns = namespace(break = false) %}
        {% for state in states -%}
          {%- if state.attributes.last_seen %}
            {%- if (as_timestamp(now()) - as_timestamp(state.attributes.last_seen) > (5 * 60 * 60) ) and ns.break == false %}
              {%- set ns.break = true %}
              true
            {%- endif -%}
          {%- endif -%}
        {%- endfor %} 
  action:
    - service: notify.notification_service
      data:
        title: Z2M DEVICE ALERT
        message: >
          Zigbee Devices Offline:
          {% macro GetDroppedZigbee() -%}
          {% for state in states.sensor -%}
          {%- if ("linkquality" in state.name and state_attr(state.entity_id, "last_seen") != None and (as_timestamp(now()) - as_timestamp(state_attr(state.entity_id, "last_seen")) > (5 * 60 * 60))) -%}
          {{ state.name | regex_replace(find=' linkquality', replace='', ignorecase=False) }} -- Da {{ ((as_timestamp(now()) - as_timestamp(state.attributes.last_seen)) / (3600)) | round(0) }} ore {{- '\n' -}}
          {%- endif -%}
          {%- endfor %}
          {%- endmacro -%}
          {{ GetDroppedZigbee() }}

What could be the problem? I just can’t solve it
Thanks

  1. In Home Assistant, go to Settings → Automations & Scenes → Automations.
  2. Find your automation in the list and click it.
  3. In the upper right corner, click Traces.
  4. Confirm that a trace was produced at one of the scheduled hours (07:00, 16:00, 20:00).

If a trace was produced, but no message was sent:

  1. In Zigbee2MQTT, go to Settings → Advanced → Last Seen.
  2. Confirm it is not set to disable and is set to ISO_8601.

If it is already set to ISO_8601:

  1. In Home Assistant, go to Developer tools → States.
  2. Type linkquality in the Filter entities field.
  3. You should see a list of sensor entities.
  4. Look in the third column (Attributes) and confirm they all have a last_seen attribute.

The automation runs at the scheduled time, but is interrupted.

In Zigbee2mqtt last seen is enabled (iso_8601).

I filtered the linkquality entities in developer tool states, but I only have 2 columns, one with the name of the entity and next to it I have the numeric value of the linkquality.
But for each zigbee device, I have a sensor sensor.name_last_seen

I have no errors, I think it stops when it gets to the condition template.

I checked, I don’t have the last_seen attribute, but only last_seen entities for each zigbee device.

Could the last_seen attribute have been removed with Home Assistant or Zigbee2mqtt updates? This automation had always worked

Then it means the condition doesn’t find any sensors whose last_seen value is more than 5 hours old.

?

I don’t know what you mean by “last_seen entities”. Post an example of one of these “last seen” entities.

I have a last_seen from 2 days ago.

This is the last_seen entity of this sensor

Thanks, now I understand what you are saying.

If there is a dedicated “Last seen” entity, why have you created a Template Condition that checks for a last_seen attribute?

        {% for state in states -%}
          {%- if state.attributes.last_seen %}
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^

The template in your macro does the same thing:

          {% for state in states.sensor -%}
          {%- if ("linkquality" in state.name and
              state_attr(state.entity_id, "last_seen") != None and
                    ^^^^                   ^^^^^^^^^
              (as_timestamp(now()) - as_timestamp(state_attr(state.entity_id, "last_seen")) > (5 * 60 * 60))) -%}
                                                        ^^^^                   ^^^^^^^^^
          {{ state.name | regex_replace(find=' linkquality', replace='', ignorecase=False) }} -- 
            Da {{ ((as_timestamp(now()) - as_timestamp(state.attributes.last_seen)) / (3600)) | round(0) }} ore {{- '\n' -}}
                                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^

NOTE

I have never enabled Zigbee2MQTT’s Last Seen option so I have never seen what happens in Home Assistant when it’s enabled. Your templates led me to believe that the Linkquality entity has a last_seen attribute. However, now I suspect that might not be true, especially now that I have see you have a Last Seen entity.


EDIT

I searched the forum for “last_seen” and I found several posts, one only a year old and others several years old, and none of them attempt to reference a last_seen attribute like your templates do. All reference the state of a Last Seen sensor entity.

When did you create those templates?

It’s an old automation, before surely the last_seen was an attribute, now an entity.

How would you modify the condition? I need the result to be as I described in the first post…

There are several similar automations, but they only notify once when the last_seen sensor is unavailable. Mine also notifies how long it has been offline.

When did it stop reporting offline devices?

By using the state value of all Last Seen sensors.

Copy-paste this into the Template Editor and confirm it reports the entity_id of all of your Last Seen sensors.

{{ states.sensor | selectattr('object_id', 'search', 'last_seen')
  | map(attribute='entity_id') | list }}

If it does what it should then let me know what this reports:

{% set entities = states.sensor | selectattr('object_id', 'search', 'last_seen')  | map(attribute='entity_id') | list -%}
{% set values = entities | map(attribute='state') | map('as_datetime') | list -%}
{% set z = zip(entities, values) | list -%}
{% for e, v in z if now() - v > timedelta(hours=5) -%}
  {{ state_attr(e, 'friendly_name') | replace(' linkquality', '') }} -- {{ ((now() -  v).total_seconds() / 3600) | round(0) }}
{% endfor -%}

If it works properly, I can show you how to use it an automation.

This in the Template editor tells me all the last_seen entities

{{ states.sensor | selectattr('object_id', 'search', 'last_seen')
  | map(attribute='entity_id') | list }}

This one doesn’t work, the error is UndefinedError: ‘str object’ has no attribute ‘state’

{% set entities = states.sensor | selectattr('object_id', 'search', 'last_seen')  | map(attribute='entity_id') | list -%}
{% set values = entities | map(attribute='state') | map('as_datetime') | list -%}
{% set z = zip(entities, values) | list -%}
{% for e, v in z if now() - v > timedelta(hours=5) -%}
  {{ state_attr(e, 'friendly_name') | replace(' linkquality', '') }} -- {{ ((now() -  v).total_seconds() / 3600) | round(0) }}
{% endfor -%}

The error mentioned above is why the condition no longer works. There is a different way to test for the presence of the last seen attribute though. This might work as expected:

        {% set ns = namespace(break = false) %}
        {% for state in states -%}
          {%- if state.attributes.get('last_seen') != none %}
            {%- if (as_timestamp(now()) - as_timestamp(state.attributes.last_seen) > (5 * 60 * 60) ) and ns.break == false %}
              {%- set ns.break = true %}
              true
            {%- endif -%}
          {%- endif -%}
        {%- endfor %} 

But the other answers here will likely be better ways to reach the same goal.

Let me know if this version also produces an error.

{% set entities = states.sensor | selectattr('object_id', 'search', 'last_seen')  | map(attribute='entity_id') | list -%}
{% set values = entities | map('states') | map('as_datetime') | list -%}
{% set z = zip(entities, values) | list -%}
{% for e, v in z if now() - v > timedelta(hours=5) -%}
  {{ state_attr(e, 'friendly_name') }} -- {{ ((now() -  v).total_seconds() / 3600) | round(0) }}
{% endfor -%}

The first version contained a mistake. In the second version I replaced map(attribute='state') with map('states').

According to the OP, there’s no last_seen attribute. It’s a sensor entity whose entity_id contains the string “last_seen”. I provided a test template to list those sensors and the OP confirmed it did.

TypeError: unsupported operand type(s) for -: ‘datetime.datetime’ and 'NoneType

OK, that means some of your Last Seen sensors don’t have a state value (or it’s not a datetime string). The template will need to be modified to exclude those sensors. Let’s try the has_value filter.

{% set entities = states.sensor 
  | selectattr('object_id', 'search', 'last_seen')
  | map(attribute='entity_id')
  | select('has_value') | list -%}
{% set values = entities
   | map('states')
   | map('as_datetime') | list -%}
{% set z = zip(entities, values) | list -%}
{% for e, v in z if now() - v > timedelta(hours=5) -%}
  {{ state_attr(e, 'friendly_name') }} -- {{ ((now() -  v).total_seconds() / 3600) | round(0) }}
{% endfor -%}

Sorry this is taking several attempts. I don’t have Last Seen sensors in my system so I can’t easily test the template. We’ll get it to work eventually.


NOTE

If this version also fails with an error then I will need to see a few examples of your Last Seen sensors. It will help me understand what kind of data the template must process. Specifically, the sensor’s state value as it appears in Developer Tools → States.

Yeah this work!

1 Like

Now I’m going to do some tests. I need to figure out how to compose the notification phrase like the one in my old template

Something like this?

{% set entities = states.sensor 
  | selectattr('object_id', 'search', 'last_seen')
  | map(attribute='entity_id')
  | select('has_value') | list -%}
{% set values = entities
   | map('states')
   | map('as_datetime') | list -%}
{% set z = zip(entities, values) | list -%}
{% for e, v in z if now() - v > timedelta(hours=5) -%}
  {{ state_attr(e, 'friendly_name') | replace(' Last seen', '') }} -- Da {{ ((now() -  v).total_seconds() / 3600) | round(0) }} ore
{% endfor -%}
1 Like

Thanks!

I’m fixing it and testing it well, the action theme seems to be working.
But now the problem is in the condition, because the automation works if it is triggered manually, but it doesn’t work automatically at the preset times. The automation always stops at the condition that is not satisfied

Test this version of the template in the Template Editor and confirm the resulting list contains the sensor’s name and time.

{% set entities = states.sensor | selectattr('object_id', 'search', 'last_seen')
  | map(attribute='entity_id') | select('has_value') | list -%}
{% set values = entities | map('states') | map('as_datetime') | list -%}
{% set z = zip(entities, values) | list %}
{% set ns = namespace(x=[]) %}
{% for e, v in z if now() - v > timedelta(hours=5) -%}
  {% set ns.x = ns.x + [(state_attr(e, 'friendly_name') | replace(' Last seen', ''), ((now() -  v).total_seconds() / 3600) | round(0))] %}
{% endfor -%}
{{ ns.x }}

It should look something like this:

[('Sirena Allarme', 13)]

If it does then it can be used in an automation like this:

alias: ALERT Zigbee Offline Devices
id: zigbee_device_missing_alert
triggers:
  - trigger: time
    at:
      - "07:00:00"
      - "16:00:00"
      - "20:00:00"
conditions: []
actions:
  - variables:
      ls: >
        {% set entities = states.sensor | selectattr('object_id', 'search', 'last_seen')
          | map(attribute='entity_id') | select('has_value') | list -%}
        {% set values = entities | map('states') | map('as_datetime') | list -%}
        {% set z = zip(entities, values) | list %}
        {% set ns = namespace(x=[]) %}
        {% for e, v in z if now() - v > timedelta(hours=5) -%}
          {% set ns.x = ns.x + [(state_attr(e, 'friendly_name') | replace(' Last seen', ''), ((now() -  v).total_seconds() / 3600) | round(0))] %}
        {% endfor -%}
        {{ ns.x }}
  - if: "{{ ls | count > 0 }}"
    then:
      - action: notify.notification_service
        data:
          title: Z2M DEVICE ALERT
          message: >
           {% for n, t in ls -%}
             {{ n }} -- Da {{ t }} ore
           {% endfor -%}
2 Likes

Thank you so much! I’ll let you know if it works