Struggling with templating for a "Last Changed" sensor

Hi all,

I’m trying to create a sensor that shows the last triggered motion sensor. I’ve managed to get the last state change using this:

                    {%- set pirs = [states.binary_sensor.back_room_motion, states.binary_sensor.downstairs_toilet_motion, states.binary_sensor.hallway_motion, states.binary_sensor.kitchen_motion, states.binary_sensor.landing_motion] %}
                    {% for pir in pirs %}
                      {% if as_timestamp(pir.last_changed) == as_timestamp(pirs | map(attribute='last_changed') | max) %}
                        {{ pir.name }}
                      {% endif %}
                    {% endfor %}

But obviously that counts every state change, rather than the last sensor to have changed to an “on” status so it also triggers when a motion sensor resets to off, which is not what I want.

I’ve tried the following:

                    {%- set pirs = [states.binary_sensor.back_room_motion, states.binary_sensor.downstairs_toilet_motion, states.binary_sensor.hallway_motion, states.binary_sensor.kitchen_motion, states.binary_sensor.landing_motion] %}
                    {% for pir in pirs %}
                      {% if is_state(pir.state, 'on') %}
                        {% if as_timestamp(pir.last_changed) == as_timestamp(pirs | map(attribute='last_changed') | max) %}
                          {{ pir.name }}
                        {% endif %}
                      {% endif %}
                    {% endfor %}

My thinking here was that it would first check if the state had turned to “on” before comparing timestamps but this doesn’t return anything at all, so i’ve obviously ballsed that one up!

Is someone kindly able to point me in the wrong direction please?

2 Likes

As far as I know, the only available data is the entity’s current state and the time it changed to its current state.

What you want is the last time it changed to a specific state, namely on, and I don’t see that data as being available here:

<template TemplateState(<state binary_sensor.basement_motion=off; friendly_name=Basement Motion, device_class=motion @ 2020-11-02T16:16:18.460654-05:00>)>

All you can do is report the motion sensors that are currently on and how long they’ve been in that state.

Thanks for your reply.

I think that makes sense, this templating stuff has always been my downfall. Is there another way that this can be accomplished then maybe? I just want it to output the last triggered “on” motion sensor which seems fairly trivial but I haven’t be able to get it working so far.

I like your idea about the template sensor for last change. I do display that information via a history graph:

motion
You just have to scroll over the graph to get the time of the last change:
motion2

I do like the idea of easily identifying the last state change. I would show that in addition to my current graph since I like to be able to easily see the pattern.

I think this might meet your requirements.

  • The template is evaluated whenever one of the five listed binary sensors changes state.
  • The template selects all that are currently on.
  • It sorts them by last_changed in reverse order (reverse chronological order) and converts it to a list.
  • If there’s more than one item in the list, it reports the first (zeroth) item’s friendly_name (“name”) and last_changed converted to local time.
  • If there are no items in the list, it reports the sensor’s existing value. (NOTE: This value will initially be unknown until one of the binary sensors changes state.)
- platform: template
  sensors:
    recent_motion:
      friendly_name: 'Recent Motion'
      value_template: >-
        {% set x =  [ states.binary_sensor.back_room_motion,
                      states.binary_sensor.downstairs_toilet_motion, 
                      states.binary_sensor.hallway_motion,
                      states.binary_sensor.kitchen_motion,
                      states.binary_sensor.landing_motion ]
              | selectattr('state', 'eq', 'on')
              | sort(reverse=true, attribute='last_changed')
              | list %}
        {% if x | count > 0 %}
          {{ x[0].name }}, {{ x[0].last_changed.timestamp() | timestamp_local() }}
        {% else %}
          {{ states('sensor.recent_motion') }}
        {% endif %}

NOTE

If you create a group containing the desired motion sensors, the template can be reduced to this:

      value_template: >-
        {% set x = expand('group.my_motion_sensors')
              | selectattr('state', 'eq', 'on')
              | sort(reverse=true, attribute='last_changed')
              | list %}
        {% if x | count > 0 %}
          {{ x[0].name }}, {{ x[0].last_changed.timestamp() | timestamp_local() }}
        {% else %}
          {{ states('sensor.recent_motion') }}
        {% endif %}
4 Likes

Thankyou! That definitely looks closer. Unfortunately now though it changes back to “unknown” when no motion is detected again. Is there a way of making it store the last value rather than returning to unknown?

Basically i’m using it for an automation which auto arms the alarm at between certain times at night if we forget to. To be certain that we’re all in bed I check 2 things:

  1. That the harmony remote shows that everything is turned off.
  2. The last motion sensor triggered has been the landing (upstairs) for 15 minutes.

That way if we’re downstairs and doing something other than watching TV or listening to music the last motion will be from downstairs and so the alarm won’t arm. If everything is off, the last motion was upstairs and it has been that way for 15 minutes then it’s pretty certain that we’re in bed and it’s safe to arm the alarm.

My original template in the opening post was working great until one night the kitchen sensor took longer to reset (I don’t know why but they sometimes do this). We came up to bed and the landing reset before the kitchen did. When the kitchen then finally set to “off” the sensor thought the last motion was in the kitchen when really it had just taken longer to reset so the alarm was never auto-armed. If the sensor only took “on” changes into account this would never happen as the last “on” trigger would always be the last place that we walked by, regardless of reset times.

Hope that makes sense, I thought it might help to put a bit of context to things. This is the automation it’s used in:

alias: Alarm Auto On at Night
description: ''
trigger:
  - platform: state
    entity_id: sensor.last_motion
    to: Landing Motion
    for: '00:15:00'
condition:
  - condition: and
    conditions:
      - condition: time
        after: '22:00:00'
        before: '06:00:00'
      - condition: state
        entity_id: remote.home
        state: 'off'
      - condition: state
        entity_id: alarm_control_panel.yale_smart_alarm
        state: disarmed
action:
  - service: alarm_control_panel.alarm_arm_home
    data:
      entity_id: alarm_control_panel.yale_smart_alarm
  - service: notify.notify
    data:
      message: Alarm has been auto-armed for the night.
mode: restart

The example I provided does not behave like that. In fact, I have had it running since yesterday and it reports the name of the most recently active motion sensor even though I’ve restarted Home Assistant at least twice (I upgraded from 0.117.2 to 0.117.4 this morning).

Did you modify it in any way, such as change its name? If you did rename it, you must change sensor.recent_motion in this line to the new name:

        {% else %}
          {{ states('sensor.recent_motion') }}
        {% endif %}
1 Like

You’re absolutely right, for some reason that wasn’t working properly earlier, I even came back and tested it a few minutes ago. Same thing, it would just revert to “unknown”, It did this whether I added it to config and in the templating dev tools.

I restarted HA and it’s now working as it should… It saves the last state absolutely fine now so I have no idea what happened there. Thanks for your help with this! Much appreciated.

I’ll get the hang of this templating stuff one day…

1 Like

To add to the template sensor above as given by @123 (thanks), this fails in the following situation:

Sensor A becomes active (e.g. Kitchen) and afterwards sensor B (e.g. hallway) becomes active. If hallway now goes back to off earlier than the kitchen sensor (i.e. kitchen still on), it will switch back to kitchen while the last was actually hallway. This can happen if you have different motion sensors with different off times.

By adding a simple ((now - last_changed) < 1), you prevent this situation as the kitchen sensor is still on and last_changed >1.

Below what I use:

template:
  sensor:
    - name: "Last area motion detected"
      icon: "mdi:motion-sensor"
      state: >-
        {% set x = expand('group.motion')
                    | selectattr('state', 'eq', 'on')
                    | sort(reverse=true, attribute='last_changed')
                    | list %}
        {% if (x | count > 0) and (((as_timestamp(now()) - as_timestamp(x[0].last_changed)) | int) < 1) %}
          {% if (area_name(x[0].entity_id) != None) %}
            {{ area_name(x[0].entity_id) }}
          {% else %}
            {{ x[0].name }}
          {% endif %}
        {% else %}
          {{ states('sensor.last_area_motion_detected') }}
        {% endif %}

Or without groups (i.e. all motion classed sensors):

template:
  sensor:
    - name: "Last area motion detected"
      icon: "mdi:motion-sensor"
      state: >-
        {% set x = expand(states.binary_sensor | selectattr("attributes.device_class", "eq", "motion"))
                    | selectattr('state', 'eq', 'on')
                    | sort(reverse=true, attribute='last_changed')
                    | list %}
        {% if (x | count > 0) and (((as_timestamp(now()) - as_timestamp(x[0].last_changed)) | int) < 1) %}
          {% if (area_name(x[0].entity_id) != None) %}
            {{ area_name(x[0].entity_id) }}
          {% else %}
            {{ x[0].name }}
          {% endif %}
        {% else %}
          {{ states('sensor.last_area_motion_detected') }}
        {% endif %}

Not really a failure; your requirements are different. The solution post determines the most recent and currently active motion sensor (meaning where is there motion happening right now, regardless of a sensor’s cooldown period).

In the example you provided, that would be the kitchen sensor because it’s still active whereas hallway, even though it occurred after kitchen, is no longer active. If one wants to know the most recent sensor to record a state-change, regardless if it’s currently on or off, you could simply remove the selectattr that specifies the sensor’s state must be on.

Mine is doing the same ! Odd. And I have restarted once but not twice. So maybe a second restart might be the charm here, too

This threads solution is pretty old at this point. This can now be done easily with the new template sensors.

This goes in configuration.yaml. Not the sensor section or sensor.yaml.

template:
- trigger:
  - platform: state
    entity_id:
      - binary_sensor.balcong
      - binary_sensor.sit
      - binary_sensor.front_door
      - binary_sensor.kitchen
      - binary_sensor.attic
    to: 'on'
  sensor:
  - name: Last Motion
    unique_id: last_motion
    state: >
      {{ trigger.to_state.name }}, {{ trigger.to_state.last_changed.timestamp() | timestamp_local() }}

It will also resume after restart.

If you want them as separate sensors with the area:

template:
- trigger:
  - platform: state
    entity_id:
      - binary_sensor.balcong
      - binary_sensor.sit
      - binary_sensor.front_door
      - binary_sensor.kitchen
      - binary_sensor.attic
    to: 'on'
  sensor:
  - name: Last Motion Area
    unique_id: last_motion_timestamp
    device_class: timestamp
    state: >
      {{ area_name(trigger.to_state.entity_id) }}
  - name: Last Motion Sensor
    unique_id: last_motion_timestamp
    state: >
      {{ trigger.to_state.name }}
  - name: Last Motion Timestamp
    unique_id: last_motion_timestamp
    state: >
      {{ trigger.to_state.last_updated }}

2 Likes