Last changed xy minutes ago

I’m trying to add a sensor to see the last time, my heat turned on.

The attribute of the sensors, I’d like to read is:

entity: sensor.pump
states: ‘Kikapcsolva’ / ‘Bekapcsolva’

I’d like the xy minutes ago to read when the sensor turned to ‘Bekapcsolva’ state.
I couldn’t find any solution, then I turned to “states.sensur.pump.last_changed” soution. It is a bit confusing, because it will be reset in both states, but I gave it a try.

ngbs_turned_on1:
friendly_name: Minutes ago
value_template: “{{ ((as_timestamp(now()) - as_timestamp(states.sensor.pump.last_changed))) | round(0) }}”

The problem is, it is not changing. It does reset the counter, when the sate changes, but the counter doesn’t rise. It keeps showing me 0. :frowning:

image

It’s like the as_timestamp(now()) not reading the correct timestamp.
If anyone has any idea, or a better solution to read ‘Bekapcsolva’ state, I’d be really glad. :slight_smile:

If your UI is Lovelace, the easiest would be to add

secondary_info: last-changed

to your entity.

Every day I’m trying to force myself using Lovelace, but I’m not that kind of a frontend guy, for 1,5 years now, I can’t seem to force myself into it. :frowning:

BTW, I would even give access to my 10+ TB Plex library if someone could do a lovelace ui config out of this. :slight_smile:

Why not give it a try?
Just go to Dev Tools/Info and activate Lovelace.
You can always revert back to the States UI.

Your template sensor will only update if the state of sensor.pump changes.
You can improve this by adding an entity_id that changes frequently.
You can use the sensor.time for it to update every minute.

Start small, think about a concept for different pages and then do one by one. The states UI will still be available, but I think it’s going to be deprecated anyway some time in the future, so I strongly suggest to start using lovelace.

That will only update every time the pump updates, which means it’ll never be anything but zero. Also, the math is wrong, that value will be in seconds. Integrate the sensor.time entity, and add it to the template to get it to update when your time changes.

sensor:
- platform: time_date
  display_options:
  - 'time'
- platform: template
  sensors:
    ngbs_turned_on1:
      friendly_name: Minutes ago
      entity_id: sensor.time
      value_template: "{{ ((as_timestamp(now()) - as_timestamp(states.sensor.pump.last_changed)) / 60) | round(0) }}"
3 Likes

great! that did it. I knew it will count seconds, but once I wanted to get to a point, where it even starts to count elapsed time. :slight_smile:
any chance to check only State = ‘Bekapcsolva’? not all state changes.

Not really. You’d have to make an automation that stores a timestamp for something as custom as that.

1 Like

I’ve even created a much sofisticated design.

ngbs_turned_on1:
        friendly_name: Time passed
        entity_id: sensor.time
        value_template: >-
                  {% set time = ((as_timestamp(now()) - as_timestamp(states.sensor.pump.last_changed))) | round(0) %}
                  {% set minutes = ((time % 3600) / 60) | int %}
                  {% set hours = ((time % 86400) / 3600) | int %}
                  {% set days = (time / 86400) | int %}
                  {%- if time < 60 -%}
                    Less than a minute
                  {%- else -%}
                    {%- if days > 0 -%}
                      {%- if days == 1 -%}
                        1 day
                      {%- else -%}
                        {{ days }} days
                      {%- endif -%}
                    {%- endif -%}
                    {%- if hours > 0 -%}
                      {%- if days > 0 -%}
                        {{ ', ' }}
                      {%- endif -%}
                      {%- if hours == 1 -%}
                        1 hour
                      {%- else -%}
                        {{ hours }} hours
                      {%- endif -%}
                    {%- endif -%}
                    {%- if minutes > 0 -%}
                      {%- if days > 0 or hours > 0 -%}
                        {{ ', ' }}
                      {%- endif -%}
                      {%- if minutes == 1 -%}
                        1 minute
                      {%- else -%}
                        {{ minutes }} minutes
                      {%- endif -%}
                    {%- endif -%}
                  {%- endif -%}

Based on this solution.

You can simplify this a ton:

      {%- macro formatvalue(name, value, sep='') %}
      {%- set name = '{}s' if value > 1 else name %}
      {{ '{} {}{}'.format(value, name, padding) if value >= 1 else '' }}
      {%- endmacro %}
      {%- set time = ((as_timestamp(now()) - as_timestamp(states.sensor.pump.last_changed))) %}
      {%- set minutes = formatvalue('minute', (time % 3600) // 60, '') %}
      {%- set hours = formatvalue('hour', (time % 86400) // 3600, ', ') %}
      {%- set days = formatvalue('day', time // 86400, ', ') %}
      {{ 'Less than 1 min' if time < 60 else days + hours + minutes }}

Let me break it down:

This is a macro. It does 3 things. It takes any ‘name’, like hour, minute or day and pluralizes it based on the value. It also adds a separator, like a comma and a space. You don’t even need the comma if you don’t want. it.

      {%- macro formatvalue(name, value, sep='') %}
      {%- set name = '{}s' if value > 1 else name %}
      {{ '{} {}{}'.format(value, name, padding) if value >= 1 else '' }}
      {%- endmacro %}

breaking down the macro:

      # start of the macro, 'formatvalue' can be used many times in your template
      # it takes 3 inputs, name, value and sep.
      # name is the word you want to pluralize based on value
      # value is the value that the pluralization is based on.
      # sep is the separator between words.
      {%- macro formatvalue(name, value, sep='') %}
      # if the value is greater than 1, change name to have an s
      {%- set name = '{}s' if value > 1 else name %}
      # if value is greater than zero, return a fully formatted value and name.
      # otherwise return an empty string.
      {{ '{} {}{}'.format(value, name, padding) if value >= 1 else '' }}
      # end of macro
      {%- endmacro %}

Breaking down the template:

      # get the time, we don't care if it's rounded or not.
      {%- set time = ((as_timestamp(now()) - as_timestamp(states.sensor.pump.last_changed))) %}
      # use the macro to get the minute phrase.  Use int division (//) to get an integer back.
      # does not add any separator after minutes because it's the last item in the phrase.
      {%- set minutes = formatvalue('minute', (time % 3600) // 60, '') %}
      # use the macro to get the hour phrase.  Use int division (//) to get an integer back.
      # also adds a , and space after the number of hours.
      {%- set hours = formatvalue('hour', (time % 86400) // 3600, ', ') %}
      # use the macro to get the day phrase.  Use int division (//) to get an integer back.
      # also adds a , and space after the number of days.
      {%- set days = formatvalue('day', time // 86400, ', ') %}
      # if time is less than a minute, say that, otherwise report the full time
      # use ~ to cast each variable to a string and add them together
      {{ 'Less than 1 min' if time < 60 else days ~ hours ~ minutes }}
7 Likes

so cool!
up to now ive used this:

      ha_uptime_macro:
        friendly_name: Ha uptime macro
        value_template: >
          {% set uptime = states('sensor.uptime_days') %}
          {% if uptime == '0.0' %} 
            Just restarted...
          {% else %}
            {% macro phrase(value, name) %}
            {%- set value = value | int %}
            {%- set end = 's' if value > 1 else '' %}
            {{- '{} {}{}'.format(value, name, end) if value | int > 0 else '' }}
            {%- endmacro %}

            {% set weeks = (uptime | int / 7) | int %}
            {% set days = (uptime | int) - (weeks * 7) %}
            {% set hours = (uptime | float - uptime | int) * 24 %}
            {% set minutes = (hours - hours | int) * 60 %}

            {{ [ phrase(weeks, 'week'), phrase(days, 'day'), phrase(hours, 'hr'), phrase(minutes, 'min') ] | select('!=','') | list | join(', ') }}
          {% endif %}

but in your example you even compress further, and the final phrase template could be left out, and the starting test for <1 minutes also?

          {% set uptime = states('sensor.uptime_days') %}
          {% macro phrase(value, name) %}
          {%- set name = '{}s' if value > 1 else name %}
          {%- set value = value | int %}
          {%- set end = 's' if value > 1 else '' %}
          {{- '{} {}{}'.format(value, name, end) if value | int > 0 else '' }}
          {%- endmacro %}

          {% set weeks = (uptime | int / 7) | int %}
          {% set days = (uptime | int) - (weeks * 7) %}
          {% set hours = (uptime | float - uptime | int) * 24 %}
          {% set minutes = (hours - hours | int) * 60 %}

          {{ 'Less than 1 min' if uptime < 60 else weeks + days + hours + minutes  }}
          {% endif %}

I get an unknown error rendering template…

You stripped out separation which is needed to remove your last line. The last line of your top template combines your results and adds them together with a separating comma. Which works for your stuff, so you should probably keep it. Otherwise you have to change your macro to account for the separator. Both do the same thing. Your current solution is slightly better IMO.

ok I see now upon closer inspection and comparison.
I kinda liked the elegance of the

{{ 'Less than 1 min' if time < 60 else days ~ hours ~ minutes }}

line…

then change your macro to accept separators and use the macro on each set x = line. Like my example.

Why I get result like this:

and warning:

2022-04-16 15:46:00 WARNING (MainThread) [homeassistant.helpers.template] Template variable warning: 'padding' is undefined when rendering '{%- macro formatvalue(name, value, sep='') %}
      {%- set name = '{}s' if value > 1 else name %}
      {{ '{} {}{}'.format(value, name, padding) if value >= 1 else '' }}
      {%- endmacro %}
      {%- set time = ((as_timestamp(now()) - as_timestamp(states.sensor.kitchen_thermostat_current_temperature.last_changed))) %}
      {%- set minutes = formatvalue('minute', (time % 3600) // 60, '') %}
      {%- set hours = formatvalue('hour', (time % 86400) // 3600, ', ') %}
      {%- set days = formatvalue('day', time // 86400, ', ') %}
      {{ 'Less than 1 min' if time < 60 else days + hours + minutes }}'

The topic is already a bit older, but I can’t get any further on my own.

I would like to include your “template” in an automation “Window X has been open for X minutes”.

Unfortunately I also get the error regarding padding.

'padding' is undefined when rendering '{%- macro formatvalue(name, value, sep='') %}

I have set padding as a test, but I think I am on the wrong track with it.

{% set padding = '' %}

@petro I’d love to buy you a coffee and have you take another look at our question.

Hi, although this is quite old (at least in the sense of HA releases), still my old school internet search query led me to this thread. So I’d like to sum up my lessons learnt from this and these two as well:

{%- macro formatvalue(name, value, sep='') %}
{%- set name = '{}s'.format(name) if value > 1 else name %}
{{- '{} {}{}'.format(value, name, sep) if value >= 1 else '' }}
{%- endmacro %}

{% set values = [
            states.binary_sensor.testsensor1.last_changed ] %}
{%- set time = (now() - (values | max)).total_seconds() %}
{%- set minutes = formatvalue('minute', ((time % 3600) // 60) | int, '') %}
{%- set hours = formatvalue('hour', ((time % 86400) // 3600) | int, ', ') %}
{%- set days = formatvalue('day', (time // 86400) | int, ', ') %}
{{- 'Less than 1 min ago' if time < 60 else days ~ hours ~ minutes ~ ' ago' }}

essentially the padding error was fixed by replacing it with “sep”, as this is the macro’s parameter.
I added the surfix ‘ago’ to the output.
By defining the array “values” first, you can even set up a group (though a kind hint, that someone postet a handy workaround to produce similar results under above linked thread). And then taking the max yields in the most recent update from all. Also from the other link petro suggested to use total_seconds() method to avoid timezone errors. Hope this helps fellows with the same query in the future :wink:

For anyone coming back now we have ‘import’ for macros…

I’m using these macros to achieve this now

File: core_entity.jinja

{# Returns the domain part of an entity_id #}
{%- macro get_domain(entity_id) -%}
  {{ entity_id.split('.')[0] }}
{%- endmacro -%}

{# Returns the name part of an entity_id #}
{%- macro get_name(entity_id) -%}
  {{ entity_id.split('.')[1] }}
{%- endmacro -%}

{# Returns the 'last_changed' of an entity_id #}
{%- macro get_last_changed(entity_id) -%}
    {%- set rv = '' -%}
    {%- if has_value(entity) -%}
        {%- set domain = get_domain(entity) -%}
        {%- set name = get_name(entity) -%}
        {%- set rv = states[domain][name].last_changed | relative_time-%}
    {%- endif -%}
    {{rv}}
{%- endmacro -%}

I use this in the YAML for output

{%- import "core_entity.jinja" as core_entities -%}
{{ core_entities.get_last_changed(entity) }}
2 Likes