Template sensor to tell me how long a door remained open for the last time

Is there a way to define a template sensor (without an automation) that can tell me how long a door remained open? it will be updated for the next event (open/close cycle).

So for example:
Door was opened last for 23 minutes. for the next cycle it can be less or more, will update when there is a new event. While the door is open and not yet close, the sensor may still have the last value.

I can do it with a couple of automations and input.tezt to use as variables, but i am wondering if there is a more direct way to do it with a template sensor. Or with the history_stats integration

thanks

This should work

# Update once a minute for door open
template:
  - sensor:
      - name: "Door Open For Time"  
        state: >-
          {% if is_state('binary_sensor.Mechanical_Room_Door', 'on') %}
            {{ (utcnow() | timestamp - states.binary_sensor.mechanical_room_door.last_changed | as_timestamp) | int(0) / 60 }} 
          {% else %}
            0
          {% endif %}

thanks, i tried it, but it doesn’t work if the door is open for less than one minute, i’d like to get number of seconds. But the utcnow function updates once per minute

i tried this to no success. looks like the variable is not retained

  - sensor:
      - name: "Door Open For Time"  
        state: >-
          {% if is_state('binary_sensor.lumi_lumi_sensor_magnet_02012603_on_off', 'on') %}
            {% set start = now() | as_timestamp %}
            {{ 'open' }}
          {% else %}
            {{ ((now() | as_timestamp) - start) | int }} 
          {% endif %}

They don’t retain the value. If you want to do it this way you have to use a helper. I don’t think your logic will work because once a minute when the door is open it will reset start or the helper to now(). 1 minute is the default. If you want, you can use a time pattern as a trigger and set it to X seconds.

If you want seconds in what I gave you, remove the / 60 and you will have seconds. It will still only update once a minute without a trigger.

I created two sensors to do the trick and it works:

template:
  - trigger:
      - platform: state
        entity_id: binary_sensor.lumi_lumi_sensor_magnet_02012603_on_off
        from: 'off'
        to: 'on'
    sensor:
      - name: Door Last Opened
        state:  "{{ now() }}"

  - trigger:
      - platform: state
        entity_id: binary_sensor.lumi_lumi_sensor_magnet_02012603_on_off
        from: 'on'
        to: 'off'
    sensor:
      - name: Door Opened Duration
        state:  "{{ (now() | as_timestamp - states('sensor.door_last_opened') | as_timestamp) | int  }}"

it will be great if there is a method to eliminate the first sensor, is there a way to pull the last timestamp when a sensor was in a particular state from the database, or can i use global variables on trigger sensors?

There is but I do not remember how to get it. It is in the history of the entity. If you search in the forum I think you will find the thread.

I think i have an elegant solution now:

  - trigger:
      - platform: state
        entity_id: binary_sensor.lumi_lumi_sensor_magnet_02012603_on_off
        from: 'on'
        to: 'off'
    sensor:
      - name: door last opened2
        state: "{{ (trigger.to_state.last_updated | as_timestamp - trigger.from_state.last_updated | as_timestamp) | int }}"

i’d love to use trigger.for.seconds instead, but it returns null, so i have to do the timedelta manually

1 Like

Would you be willing to share the full chunk of config you use here? I’m trying to do a very similar thing but not having any luck, the sensor just shows “unavailable.”

it needs to be a part of a template sensor:
so add “template:” so inside your sensors.yaml for completeness it should be:

template:
  - trigger:
      - platform: state
        entity_id: binary_sensor.door
        from: 'on'
        to: 'off'
    sensor:
      - name: Backyard door open duration
        state: "{{ (trigger.to_state.last_updated | as_timestamp - trigger.from_state.last_updated | as_timestamp) | int }}"

You need to cycle it at least once (close/open/close) it will give you the value AFTER you close it

aha, I had an errant - before the word sensor. It’s working now, thanks!

1 Like

Because trigger.for references the State Trigger’s for option.

Your State Trigger doesn’t employ a for option so that’s why the trigger.for object is null.


BTW, you’re fortunate that there was a very recent change made to Trigger-based Template Sensors.

In the past, the Trigger-based Template Sensor’s initial value on startup would be unknown until the binary_sensor changed state (i.e. its computed state did not survive a restart). Now it reports the last-known state on startup.

yes, i just realized that it exists only for platform event, not platform state. so, i guess the shortest way to do it is a manual timestamp delta as above. There was a discussion in some forum about a timedelta() function in jinja so you could do that more direct. But not sure if that exists or not

?

Where in the documentation does it indicate an Event Trigger supports for?

trigger.for is reported for State, Numeric State, and Template triggers but only if the trigger’s for option is configured.

it is on template not on event. it was my misunderstading of trigger.for don’t grill me on it, simple mistake :slight_smile:

I wasn’t; it was a simple question.

Perhaps you were thinking of this:

  - trigger:
      - platform: state
        entity_id: binary_sensor.door
        from: 'on'
        to: 'off'
    sensor:
      - name: Backyard door open duration
        state: "{{ (trigger.to_state.last_updated - trigger.from_state.last_updated).total_seconds() }}"

last_updated contains a datetime object. If you subtract two datetime objects the result is a timedelta object. The computed value of the subtraction is reported, in seconds, by the timedelta object’s total_seconds() method.

That worked for me, but I had to use last_changed instead of last_updated for my Aqara Door Sensor on Zigbee2MQTT.

1 Like