Sensor - last updated monitor

I’ve read a number of people’s solutions for monitoring when a sensor stops updating to alert me and I wasn’t quite satisfied with any of them. Many used an automation template to compare the ‘time’ or ‘date_time’ to the desired sensor ‘last_updated’, however this seems like it would require a large number of automation to keep track of so I can be notified WHICH sensor to check.

I think I’ve come up with an elegant way to do this which gives me lots of flexibility. If I set MQTT to force_update I could even graph it on Lovelace and have an overview of any outlier sensors that may have signal issues since the interval should be regular.

Mine are temp-humidity, I want to catch a change to either (clearly if ANY update arrives it’s still updating) so I used an IF condition to grab whichever updated most recently.

The solution I came up with was using template sensors (since I’m already making sensors for each MQTT item) that track the seconds since the last update as so:

sensor:

  - platform: template
    sensors:
      outside_front_sensor_age:
        friendly_name: "Outside Front Sensor Age"
        entity_id: sensor.time
        value_template: >-
          {% if states.sensor.outside_front_temperature.last_changed > states.sensor.outside_front_humidity.last_changed %}
            {{ (states.sensor.time.last_changed - states.sensor.outside_front_temperature.last_changed).total_seconds() | round(0) }}
          {% else %}
            {{ (states.sensor.time.last_changed - states.sensor.outside_front_humidity.last_changed).total_seconds() | round(0) }}
          {% endif %}
        unit_of_measurement: "Seconds"

  - platform: template
    sensors:
      outside_rear_sensor_age:
        friendly_name: "Outside Rear Sensor Age"
        entity_id: sensor.time
        value_template: >-
          {% if states.sensor.outside_rear_temperature.last_changed > states.sensor.outside_rear_humidity.last_changed %}
            {{ (states.sensor.time.last_changed - states.sensor.outside_rear_temperature.last_changed).total_seconds() | round(0) }}
          {% else %}
            {{ (states.sensor.time.last_changed - states.sensor.outside_rear_humidity.last_changed).total_seconds() | round(0) }}
          {% endif %}
        unit_of_measurement: "Seconds"

  - platform: template
    sensors:
      shed_driveway_sensor_age:
        friendly_name: "Shed Driveway Sensor Age"
        entity_id: sensor.time
        value_template: >-
          {% if states.sensor.shed_driveway_temperature.last_changed > states.sensor.shed_driveway_humidity.last_changed %}
            {{ (states.sensor.time.last_changed - states.sensor.shed_driveway_temperature.last_changed).total_seconds() | round(0) }}
          {% else %}
            {{ (states.sensor.time.last_changed - states.sensor.shed_driveway_humidity.last_changed).total_seconds() | round(0) }}
          {% endif %}
        unit_of_measurement: "Seconds"

Now, I can use a single automation to easily message me for all the problems of interest to me, and based on the friendly_name I can be alerted if it’s the battery low, sensor age, or something else:

#System Status Alerts

  - alias: 'Telegram Check Sensor Alert'
    trigger:
      # Low Battery Alerts
      # Binary Sensor Alerts (on = alarm)
      - entity_id: 
          # Acurite Temp & Humidity Sensors
          - binary_sensor.outside_front_sensor_battery_low
          - binary_sensor.outside_rear_sensor_battery_low
          - binary_sensor.shed_driveway_sensor_battery_low
          - binary_sensor.shed_backyard_sensor_battery_low
          - binary_sensor.attic_sensor_battery_low
        platform: state
        from: 'off'
        to: 'on'
        # Reduce flapping
        for:
          minutes: 15
      # Low Battery Alerts
      - platform: numeric_state
        entity_id:
          # Zigbee
          - sensor.washer_vibration_battery_level
          - sensor.dryer_vibration_battery_level
          # Z-Wave
          - sensor.dining_room_gate_sensor_battery_level
        below: 33
        # Reduce flapping
        for:
          minutes: 15
      # Monitor sensor age - not reporting in
      - platform: numeric_state
        entity_id:
          # Acurite Temp & Humidity Sensors Age Expired
          - sensor.outside_front_sensor_age
          - sensor.outside_rear_sensor_age
          - sensor.shed_driveway_sensor_age
          - sensor.shed_backyard_sensor_age
          - sensor.attic_sensor_age
        # Seconds for timeout
        above: 1800
    action:
      service: notify.telegram_my_user
      data_template:
        message: |
          Warning - Check Sensor:
          {{ trigger.to_state.attributes.friendly_name }}

The messages from this single automation then end up looking like this - clean and concise easily letting me know what to check:
image
image

To me, as a software engineer, this seems much easier and more modular for maintenance than having to keep track of a mess of automation…I can just make the sensors as I make the MQTT sensors and then add 1 line to my notification alerts. If I decide I need to be alerted via another method, I only have to change 1 automation and I can have it (for example) blink a light or send an email instead of Telegram messages. If I decide to change the interval, I can change it in 1 place on the alert automation. If I add a sensor, I don’t have to worry about copying the correct alert logic, I can just add the new sensor to the existing list. If I want to add more actions, I can simply reference the known working age calculations.

I also plan to make a watchdog automation to detect if ALL the sensors drop out and automatically try restarting RTL433, sometimes if the USB cable is bumped it seems to get stuck. This should be a lot easier now that I have sensors with numeric to compare instead of having to do a bunch of complex templates with conditions duplicating the same code as my alert…now I can simply reference the age-sensor values.

For my particular scenario (Accurite sensors) I have made a bash-script to generate my MQTT temp/humidity/battery sensors and filter/last_updated sensors given a numeric sensor-ID and location-name string. I’ve posted it on GitHub if someone wants to use it as a basis for making other bulk sensors.

I wish this was an easily referenced attribute I could use built into hassio, but in the meantime I guess this is nearly as convenient.

2 Likes

I’m getting the following error when trying to do this:

TemplateError(‘UndefinedError: ‘None’ has no attribute ‘last_changed’’) while processing template ‘Template("{% if states.sensor.temporar_temperatur.last_changed > states.sensor.temporar_luftfuktighet.last_changed %} {{ (states.sensor.time.last_changed - states.sensor.temporar_temperatur.last_changed).total_seconds() | round(0) }} {% else %} {{ (states.sensor.time.last_changed - states.sensor.temporar_luftfuktighet.last_changed).total_seconds() | round(0) }} {% endif %}")’ for attribute ‘_state’ in entity ‘sensor.temporary_sensor_age’

Any ideas?

EDIT: Got it to work by adding the sensor.time, which I had forgotten. But now it updates intermittently and also displays negative values sometimes.

I’m not using MQTT.

Can someone explain why I get negative values from this calulation:

{{ (states.sensor.time.last_updated - states.sensor.temporar_luftfuktighet.last_updated).total_seconds() | round(0) }}

In a pattern like this:

The time the sensor was last updated could never be more recent than the current time, so there has to be something with the way the values are presented in the formula?

EDIT: Solved, needed to use now() instead of sensor.time.

Glad you figured out a fix, the “why” took me a long time to understand when I had this happen to me.

For anyone else, I believe what happens is the time is updated at an interval (probably top of the minute 00 seconds) and if your sensor happens to update in the middle of the minute now the “sensor” time is greater than the “time” time.

Exactly, template sensors are updated when source entities are updated. sensor.time is updated every minute, but time will only be stated as HH:MM. So every minute, the template age sensor will update because of sensor.time, but it will also update whenever any of the entities within the template sensor is updated, which turned out to be every 48 seconds in my case (but only if there is a change in measurement, otherwise it won’t report).

So what happened was that every minute, the template sensor updated because of sensor.time, which produced positive values since this will be higher than the sensors last update time, but the template sensor also updated with 48 second intervals because of the source sensor, and then it will produce negative values since sensor.time will have a lower value than the current time.

now() is also updated every minute, but outputs the current time exactly, which makes the template sensor age 0 whenever the source sensor reports, which of course is correct. No more negative values.

Cheers , this helped me a lot. As mentioned in this post already you need to update the age sensor a bit to the latest HA as the following with now() :

     goodwee_sensor_age:
        friendly_name: "Goodwee logger sensor age"
        value_template: >-
          {% if states.sensor.pv_watt.last_changed > states.sensor.pv_volt.last_changed %}
            {{ (now()- states.sensor.pv_watt.last_changed).total_seconds() | round(0) }}
          {% else %}
            {{ (now() - states.sensor.pv_volt.last_changed).total_seconds() | round(0) }}
          {% endif %}
        unit_of_measurement: "Seconds"
3 Likes