How to calculate time between the two most recent state changes for binary sensor

I’m trying to calculate the time between the last two pumpouts of my sump pit. I want this to calculate when the pumpout occurs, so it updates every pumpout.

I have a binary sensor that is on when the pump is pumping and off when it isn’t. I wrote the following template sensor (which took me forever to figure out!), but unfortunately this gives a time of almost zero as the last_changed is updated as the sensor is triggered.

- trigger:
    - platform: state
      entity_id:
        - binary_sensor.sump_pump_1_pumpout_event
      from: "off"
      to: "on"
  sensor:
    - name: "sump_pump_1_cycle_interval_now"
      state: >
        {{ ((as_timestamp(now()) - as_timestamp(states.binary_sensor.sump_pump_1_pumpout_event.last_changed)) | float(1) ) |int|timestamp_custom('%-M min %-S sec') }}
      attributes:
        friendly_name: Sump Pump 1 Cycle Interval

I need some way to store the last changed time and then find the difference from the new last changed time. In Node Red it has old.state and new.state, but I can’t figure out the javascript in the function node. This solution is to set up an SQL database, and I tried to do that but couldn’t figure out the basic parameters from the docs.

In plain English, or psuedo-code as @finity put it here, I want the trigger to cause HA to store the last_changed time as time_2 and store now() as time_1. Then subtract the two and return the result. Not sure how to do this with a template sensor and how to avoid having the last_changed simply always be now() since the thing is triggered by the change in the first place!

I’ve searched around and found similar questions, but the setups seem to be different enough that I can’t make solutions work for my setup. So I’m officially stuck and would appreciate any help.

The following Trigger-based Template Sensor records the last 5 time intervals (where an interval represents the time when the pump wasn’t running) in its cycles attribute as a list in reverse chronological order (most recent is first).

The sensor’s state is simply the most recent time when the pump turned on. The information you want is in the cycles attribute (which is unaffected by a restart).

template:
  - trigger:
      - platform: state
        entity_id: binary_sensor.sump_pump_1_pumpout_event
        from: 'off'
        to: 'on'
    sensor:
      - name: Sump Pump Operating Cycles
        state: "{{ now().timestamp() | timestamp_custom() }}"
        attributes:
          cycles: >
              {% set current = this.attributes.get('openings', []) %}
              {% set new = [{
                "stopped": (trigger.from_state.last_changed | as_local).isoformat(),
                "started": now().isoformat(),
                "duration": (now() - trigger.from_state.last_changed).total_seconds() | int(0) }] %}
              {{ ((new + current) | sort(reverse=true, attribute='duration'))[:5] }}

Initially the sensor’s value will be unknown and cycles will be empty. It will display data only after being triggered.

If you are interested, this example is a simplified version of another one I created here:


EDIT

Correction. Added missing line continuation character to cycles.

1 Like

Thanks for sharing, this looks perfect!

There was a missing > after cycles but after I added that (took me forever because I initially tried < and it didn’t work!) there were no errors and allowed me to restart.

I’ve tested it using Developer Tools and it is giving me the correct time between on events. But the entity card shows me the literal times as well. The only Attributes are Friendly name and Cycles. How do I snag just the duration value? I want to show the duration time as (%-M min %-S sec) when it’s all said and done, like this: image

Here’s what I’m currently getting:

I’m thinking there’s a way to set up an attribute Duration? I messed around with it a little bit, but I am so unfamiliar with the syntax I couldn’t get it to work and guess-and-test would take forever.

Also, is this line still needed since I’m not dealing with ranking the durations or anything?
{{ ((new + current) | sort(reverse=true, attribute='duration'))[:5] }}

If you don’t need a history of the last 5 cycles, then it can be reduced to this:

template:
  - trigger:
      - platform: state
        entity_id: binary_sensor.sump_pump_1_pumpout_event
        from: 'off'
        to: 'on'
    sensor:
      - name: Sump Pump 1 Period 
        state: >
          {{ (now() - trigger.from_state.last_changed).total_seconds()
            | timestamp_custom('%-H hr %-M min %-S sec', false) }}
4 Likes

That worked perfectly. Thank you so much!

1 Like

You’re welcome!

Please consider marking my post above with the Solution tag. It will automatically place a check-mark next to the topic’s title which signals to other users that this topic has been resolved. This helps users find answers to similar questions.

For more information about the Solution tag, refer to guideline 21 in the FAQ.