Please help with template for last motion

I’m trying to create a template sensor that shows the number of minutes since last motion in the house.

This template works as expected when I try it in the Template Editor. But the template sensor with the exact same code always stays on value 0 whole day. Can you help?

  - platform: template
    sensors:
      since_last_motion:
        friendly_name: 'Minutes since last motion'
        value_template: >
          {%- set sensors = [states.binary_sensor.pir_woonkamer, states.binary_sensor.pir_toilet, states.binary_sensor.pir_hal, states.binary_sensor.pir_badkamer, states.binary_sensor.door_trapkast] %}
          {% for sensor in sensors %}
            {% if as_timestamp(sensor.last_changed) == as_timestamp(sensors | map(attribute='last_changed') | max) %}
              {{ (( as_timestamp(now()) - as_timestamp(sensor.last_changed)) / 60 ) | int }}
            {% endif %}
          {% endfor %}
1 Like

I have the following (the sensor group contains all sensors relevant). I must say I also copied this from someone but it works…

  - platform: time_date
    display_options:
      - 'date_time'

  - platform: template
    sensors:
      last_motionv2:
        friendly_name: Last motionv2
        value_template: '{% if states.group.motion_check.last_updated is undefined %}{{"00:00:00"}}{% else %}{{ ((as_timestamp(states.sensor.date_time.last_updated)-as_timestamp(states.group.motion_indoors.last_updated))|int) }}{% endif %}'
        entity_id:
          - sensor.date_time
        unit_of_measurement: 's'

@Tyfoon Thanks, trying it now!

If you don’t want to make a motion group like what @Tyfoon has… your template was pretty close

  - platform: template
    sensors:
      since_last_motion:
        friendly_name: 'Minutes since last motion'
        value_template: >
          {%- set sensors = [states.binary_sensor.pir_woonkamer, states.binary_sensor.pir_toilet, states.binary_sensor.pir_hal, states.binary_sensor.pir_badkamer, states.binary_sensor.door_trapkast] %}
          {%- set filtered_sensors = sensors | selectattr('last_changed','==', sensors | map(attribute='last_changed') | max) | list %}
          {{ (( as_timestamp(now()) - as_timestamp(filtered_sensors[0].last_changed)) / 60 ) | int }}

To be honest, i’m not sure why your template wasn’t working as it looks correct. This template is essentially the same.

It’s possible that one of your sensors isn’t defined and it’s choosing that one as the max. What are you getting with your template in the template editor?

Tyfoon option does the same as my original sensor: it works in the template editor, but the sensor always shows 0 in the front end & history. What might be going on here?

I’m guessing that your sensors always update. The int is truncating everything less than a minute to zero.

One would think so, but that is not the case. In the template editor, I saw the value increasing to 6 minutes.

Ah, probably need to add

entity_id: sun.sun

Otherwise it will only update when those sensors update, effectively making it always zero. sun.sun updates once a minute.

  - platform: template
    sensors:
      since_last_motion:
        friendly_name: 'Minutes since last motion'
        entity_id: sun.sun
        value_template: >
          {%- set sensors = [states.binary_sensor.pir_woonkamer, states.binary_sensor.pir_toilet, states.binary_sensor.pir_hal, states.binary_sensor.pir_badkamer, states.binary_sensor.door_trapkast] %}
          {%- set filtered_sensors = sensors | selectattr('last_changed','==', sensors | map(attribute='last_changed') | max) | list %}
          {{ (( as_timestamp(now()) - as_timestamp(filtered_sensors[0].last_changed)) / 60 ) | int }}
1 Like

Makes sense, I will try that.

It works, thanks!

1 Like

Hi there,
thanks for sharing this discussion. Your solution works really good also in my case using Fibaro Z-Wave sensors.

However, I tried to convert the outcome from seconds to hours and days, using other templates of the forum (Convert seconds to days, hours, minutes). Unfortunately I can’t get the output to state anything other than “Less then a minute”. I of course adjusted the divisor, sensor name, etc.

Have you maybe had any luck with that question in the meanwhile?

Thanks!

Can you show some of your code and what exactly you are trying to achieve?

I am using the following by your example in my sensor.yaml to display the time in minutes since my motion sensors detected the last motion and this works perfectly well:

    since_last_motion:
      friendly_name: 'Letzte Bewegung im Haus (min)'
      value_template: >
        {%- set sensors = [states.binary_sensor.bewegungssensor_kuche_home_security_motion_detected, states.binary_sensor.bewegungssensor_wohnzimmer_home_security_motion_detected, states.binary_sensor.bewegungssensor_abstellraum_home_security_motion_detected] %}
        {%- set filtered_sensors = sensors | selectattr('last_changed','==', sensors | map(attribute='last_changed') | max) | list %}
        {{ (( as_timestamp(now()) - as_timestamp(filtered_sensors[0].last_changed)) / 60 ) | int }}

I have now tried to merge my code, with the one that I linked in my previous post, to convert minutes into minutes/hours/days. With merging, i.e. having all in one step, I did not succeed at all. Building a sequence, i.e. have my piece of code followed by:

    since_last_motion_mhd:
    friendly_name: 'Since Last Motion'
    value_template: >- 
      {% set time = (states.sensor.since_last_motion | int) | int %}
      {% 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 -%}
          {{ days }}d
        {%- endif -%}
        {%- if hours > 0 -%}
          {%- if days > 0 -%}
            {{ ' ' }}
          {%- endif -%}
          {{ hours }}h
        {%- endif -%}
        {%- if minutes > 0 -%}
          {%- if days > 0 or hours > 0 -%}
            {{ ' ' }}
          {%- endif -%}
          {{ minutes }}m
        {%- endif -%}
      {%- endif -%}

I get to the point that it displays “Less than a minute”, but somehow it does not update anymore. I think it’s because of the sequence.

Any ideas or suggestions?

reading this whole thread makes me wonder why one wouldn’t simply use the last_triggered attribute of an automation which is triggered by all motion sensors? (thinking the automation will probably be there already)

No need for complex templates and always exact (not simply updating each minute)?

 {{relative_time(states.automation.update_last_motion.attributes.last_triggered)}}
 {{relative_time(state_attr('automation.update_last_motion','last_triggered'))}}

I now this is not core HA but you can easily use the variable integration to even record which sensors triggered lastly. But, even if you wouldn’t use the variable, check the last_changed of this sensor to see how easily that could be used for the op’s quest:

EDIT: Scratch that. Missing morning wake up crutch.

Watch your last_changed attributes and see if it’s changing constantly.

Marius’ idea sounds nice as well.

For this template: it results in minutes since last motion, not seconds. Does that help at all?

thanks for all your feedback.

@petro: in my attempt the two code elements run in sequence. the first part defines the sensor “since_last motion”, by combining all motion activities of all sensors into one, counting seconds and returning the result divided by 60, in minutes, as expected.

the second part takes this sensor as input to convert it into hours, days and weeks. So to your question, yes the status of the input sensor updates correctly every minute, as “since_last_motion” also displays the updates correctly. Only the second part does not seem to get beyond the initial sate of “Less than a minute”.

@Mariusthvdb & @Emphyrio - I just recently started to get back into coding after several years, so please excuse my missing knowledge if this is stupid, but using the last_triggered attribute of an automation puts me back to start, doesn’t it?

It should not really matter if I use last_triggered of a sensor, or from the automation? Both return regular updates in seconds and my problem is not converting seconds into minutes. My problem is to dynamically convert it into min/hour/day/week - whatever is relevant at that time.

In my Lovelace I display for example the state of my main entrance (OPEN/CLOSED) and the time since the last change. This looks nice, as long as the time is shorter than 60 min, after that it can become difficult to read, if the door is closed since, e.g. 3456 minutes…

Bildschirmfoto 2021-01-07 um 08.59.58

it does matter, as it is much simpler to write :wink: that is, if you already have an automation triggering on the same sensors you intend to use here. And even if you dont have that yet, I think you will soon, because you would want to do something within that info, and for that you would need an automation.

the relative_time() template I suggested above does handle the minutes correctly, and, when over 60, it will show 1 hour ago.

  • relative_time converts datetime object to its human-friendly “age” string. The age can be in second, minute, hour, day, month or year (but only the biggest unit is considered, e.g., if it’s 2 days and 3 hours, “2 days” will be returned). Note that it only works for dates in the past .

It will do so with all units, (and that makes the relative_time() function too rough for precise feedback)

If you truly want better feed back, and have it say like ‘2 weeks, 1 day, 13 hours and 20 minutes ago’ you would need the other template by Petro floating here and have it use that same last_triggered:

          {%- set up = as_timestamp(now())-as_timestamp(state_attr('automation.update_last_motion','last_triggered')) %}

          {%- macro phrase(name,divisor,mod=None) %}
            {%- set value = ((up//divisor) % (mod if mod else divisor))|int %}
            {%- set end = 's' if value > 1 else '' %}
            {{- '{} {}{}'.format(value,name,end) if value|int > 0 else ''}}
          {%- endmacro %}

          {%- set values = [phrase('week',60*60*24*7),
                            phrase('day',60*60*24,7),
                            phrase('hour',60*60,24),
                            phrase('min',60),
                            phrase('sec',1,60)]
                            |select('!=','')|list %}
          {{values[:-1]|join(', ') ~ ' and ' ~ values[-1] if values|length > 1 else
            values|first}}

Thanks again for the quick feedback. Now I understand your point. I will play a bit with both suggestions. Thanks