Sensor since last changed using relative time?

Im trying to use relative_time() to show a time (in a sensible format) since a sensor changed state.

Ive created a sensor template and set the value_template to "relative_time(states.device_tracker.SPIDERMAN.last_changed)}}"

This kind of works and i see a time for the sensor in the UI but this never updates.
Checking the value_template value in the template editor shows the correct time since last_changed.

I therefore think its an update problem. Reading other people with this problem has led me to the entity_id parameter of the sensor template. But having set this to both device_tracker.SPIDERMAN and sensor.time (which supposedly triggers every minute?) the value still doesnt update.

How can I fix this to show an accurate, updating value of when the sensor last changed state in the UI?

can you share your template sensor definition? It would help us help you.

What does that mean? What format are you after?

If you set the device class to time stamp you can display a relative time in a lovelace card.

@lolouk44

here is my config. By “sensible” i mean minutes when its minutes, hours when its hours etc - this seems to be done internally in relative_time hence using it:

  - platform: template
    sensors:
      car_last_updated:
        friendly_name: 'Car Last Seen'
        icon_template: mdi:clock-outline
        value_template: "{{relative_time(states.device_tracker.bc_30_7e_53_cc_66.last_changed)}}"
        entity_id: sensor.date_time

@DavidFW1960 - i already have it showing as “6 seconds” - trying your suggestion leads to “invalid date/time” when adding to above config

I use this:

{{ (as_timestamp(now()) - as_timestamp(states.device_tracker.bc_30_7e_53_cc_66.last_changed | default(0)) | int ) | timestamp_custom("%Hh %Mm", false)}}

Let me know if that helps

3 Likes

@lolouk44 thanks for the input but that doesnt work
it simply never seems to get updated after home assistant loads

As i said, if i copy the value_template into the template editor it shows the correct value but the sensor inside a group doesnt update its value

Are you using this inside a sensor template?
can you share your config?

thanks

you then need to list which sensor HA needs to watch to update the template sensor:

 - platform: template
    sensors:
      car_last_updated:
        entity_id: device_tracker.bc_30_7e_53_cc_66
        friendly_name: 'Car Last Seen'
        icon_template: mdi:clock-outline
        value_template: "{{relative_time(states.device_tracker.bc_30_7e_53_cc_66.last_changed)}}"
        entity_id: sensor.date_time
1 Like

thank you for your codes. it helped guide me into the right direction.

1 Like

That is great! What if the time is in days?

1 Like

change the timestamp_custom to display days as well:
timestamp_custom(%d days, "%Hh %Mm", false)
More info on time formats here:
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes

Ok thanks. But it does add a day always, it’s weird…
Don’t know why I can’t simply transform my last_updated into a proper datetime so that relative_time works…

it will. what you’ll need to do is a test on your value with a series of if statement to cater your display to only the units you actually want to see.
I know I’ve seen this topic being discussed in the past on this forum, but can’t remember where exactly but a quick search returned these:.

Have fun

last_changed is not always a datetime object. Relative time will not update properly in the UI without using now(). So you’ll need to add it in or do the math yourself.

If you are using a simple UI element to display your sensor, like entities card… You can utilize the device_class: timestamp and the UI will handle the rest.

sensor:
- platform: template
  sensors:
    xxx_last_changed:
      value_template: "{{ states.sensor.xxx.last_changed }}"
      device_class: timestamp
1 Like

Thanks! I am actually in a loop to check whether any zigbee device is unavailable for a long time or not.
So I would prefer not to create a template sensor for each of them…

This does not work (yet) :slight_smile:

message: |
  Some devices haven't been seen lately:
  {% for state in states.sensor -%}
    {%- if state.attributes.linkquality %}
      {%- if  "linkquality" in state.name and (as_timestamp(now()) - as_timestamp(state.attributes.last_seen) > (12 * 60 * 60) ) -%}
           .       {{ relative_time(state.attributes.last_seen) }} ago for {{ state.name | lower }}     .
      {%- endif -%}
    {%- endif -%}
  {%- endfor %}

But it works like these (even though it’s not what I want):

message: |
  Some devices haven't been seen lately:
  {% for state in states.sensor -%}
    {%- if state.attributes.linkquality %}
      {%- if  "linkquality" in state.name and (as_timestamp(now()) - as_timestamp(state.last_changed) > (12 * 60 * 60) ) -%}
           .       {{ relative_time(state.last_changed) }} ago for {{ state.name | lower }}     .
      {%- endif -%}
    {%- endif -%}
  {%- endfor %}
1 Like

Try this:

message: |
  Some devices haven't been seen lately:
  {% for state in states.sensor -%}
    {%- if state.attributes.linkquality %}
      {%- if  "linkquality" in state.name and (as_timestamp(now()) - as_timestamp(state.last_changed) > (12 * 60 * 60) ) -%}
           .       {{ relative_time(strptime(state.attributes.last_seen, "%Y-%m-%dT%H:%M:%S%z")) }} ago for {{ state.name | lower }}     .
      {%- endif -%}
    {%- endif -%}
  {%- endfor %}

This works because relative_time is expecting a datetime object, and the attributes are stored as strings. You apparently need to convert to a datetime, and the only way I see available to do that in the template is via strptime.

EDIT: Updated to use last_seen in both places and account for lqi=0.

message: |
  Some devices haven't been seen lately:
  {% for state in states.sensor -%}
    {%- if not state_attr(state.entity_id, 'linkquality') == None and state.attributes.last_seen %}
      {%- if  "linkquality" in state.name and (as_timestamp(now()) - as_timestamp(strptime(state.attributes.last_seen, "%Y-%m-%dT%H:%M:%S%z")) > (12 * 60 * 60) ) %}
         {{ relative_time(strptime(state.attributes.last_seen, "%Y-%m-%dT%H:%M:%S%z")) }} ago for {{ state.name | lower }},
      {%- endif -%}
    {%- endif -%}
  {%- endfor %}
1 Like

Thank you, I will gove it a try.
Could you please explain me this line you added please?
{%- if not state_attr(state.entity_id, 'linkquality') == None and state.attributes.last_seen %}

tl;dr: To make sure we also include devices with a link quality of 0.

Long Version: the original code has this: if state.attributes.linkquality, to narrow down and only test sensors which have a linkquality attribute. This evaluates to FALSE under two conditions: 1) If linkquality is not an attribute (what we want) or 2) it’s value is some other “falsy” value, in this case that could mean if linkquality == 0 (which is bad, because we also want to include these in the list).

So to make sure the devices with linkquality = 0 are included, I use a more explicit built-in function (see the Template docs page) to test if the sensor has the attribute linkquality. The test is a double negative – state_attr() function is a “safe” function to get the attribute value–we’re just making sure its a value and not “None”.

The 2nd half (state.attributes.last_seen) just uses the original way to test if the device has a last_seen attribute and it’s not zero, because we’re not going to be able to do the next step without that.

Hope that helps!

thanks for this. I had an issue with my ever solar monitor which would disconnect if the usb rs485 stick was physically disturbed. I wanted a binary sensor which would tell me whether the update time was longer than 45 seconds in the past so that i could be notified to go and reboot the raspberry pi which had stopped being able to collect the data from the inverters. Just about managed to cobble one together now.