Debouncing binary sensory

Here’s the solution yaml.

I have a catflap and a letter box with binary sensors that generate more than one MQTT message when operated due to the mechanical limitations.

I’d like to debounce them so that I only get one trigger per cat or mail delivery!

I’ve been using the code below for something similar (preventing two automations from running when another has triggered within a time period) but I’m not sure how to adapt it to achieve what I need as the condition would always be false.

Is there a way to replace ‘now’ with a time period?

Can anyone please give me a pointer as to where to start please?

trigger:
    - entity_id: binary_sensor.back_door_motion
      from: 'off'
      platform: state
      to: 'on'
  condition:
    - condition: state
      entity_id: input_boolean.housekeeper_mode
      state: 'off'
    - condition: template
      value_template: '{{ (as_timestamp(now()) - as_timestamp(state_attr("automation.announce_back_door_has_closed", "last_triggered") | default(0)) | int > 5)}}'
    - condition: template
      value_template: '{{ (as_timestamp(now()) - as_timestamp(state_attr("automation.announce_back_door_has_opened", "last_triggered") | default(0)) | int > 5)}}'
  action:

I’m not sure what you are asking here.

Don’t you want to compare the last time the automation triggered to now so that you won’t get multiple triggers of the automation driven by the sensor just like you are doing with the example automation you posted?

Why would the condition always be false?

Or are you just wanting to prevent multiple messages being sent to the broker?

If I do something like this:

alias: announce_cat_has_gone_trough_the_catflap
    trigger:
        - entity_id: binary_sensor.catflap
          from: 'off'
          platform: state
          to: 'on'
      condition:
        - condition: template
          value_template: '{{ (as_timestamp(now()) - as_timestamp(state_attr("automation.announce_cat_has_gone_through_the_catflap", "last_triggered") | default(0)) | int > 5)}}'
      action:
        service: notify.ios

The action won’t get called because the condition will be false.

not if it’s been more than 5 seconds from the last time the automation was last triggered. in that case it will be true.

let’s say that the last triggered was 17:14:25. and now is 17:14:55.

17:14:55 - 17:14:25 = 30 seconds which is greater than 5 seconds so the condition will be true.

unless i’m missing something else…:thinking:



After seeing the suggestion made by @Bartem (see next post below), go with that! Significantly simpler than what I proposed!

{{ as_timestamp(now()) - as_timestamp(states.binary_sensor.catflap.last_changed) > 5}}

I’ll leave my original post as-is for anyone wishing to … use a more complicated approach. :slightly_smiling_face:


If you want to ‘debounce’ a motion sensor (or any other binary_sensor) you basically want a ‘latching’ effect.

The first time the sensor changes to on, it is considered to be ‘latched’ for a fixed period of time (say 10 seconds). During this latching period, all subsequent state-changes are effectively ignored.

It’s easy to implement this latching effect. You’ll need a timer, input_boolean, and a simple automation.

Timer

Create a timer and set its duration to the amount of time you wish to ‘latch’ the binary_sensor (i.e. after the first state-change to on, your automation will ignore all subsequent state-changes for X seconds).

timer:
  back_door_motion_latch:
    duration: '00:00:10'

Input_Boolean

Create an input_boolean. It’s purpose is to simply indicate when latching is in effect. This will be used by your automation (in a condition).

input_boolean:
  back_door_motion_latch:
    initial: off

Your Automation

The following is your automation, slightly modified. The main differences are:

  • a condition to check the state of input_boolean.back_door_motion_latch
  • a service to start the timer

The first time the sensor changes to on, it turns on input_boolean.back_door_motion_latch and start timer.back_door_motion_latch. Add your desired services below them.

The next time the sensor changes to on (within 10 seconds of the first time), the automation will be triggered but it won’t get past the second condition, so the action won’t be executed.

- alias: 'Your automation'
  trigger:
      - entity_id: binary_sensor.back_door_motion
        from: 'off'
        platform: state
        to: 'on'
    condition:
      - condition: state
        entity_id: input_boolean.housekeeper_mode
        state: 'off'
      - condition: state
        entity_id: input_boolean.back_door_motion_latch
        state: 'off'
    action:
      - service: input_boolean.turn_on
        entity_id: input_boolean.back_door_motion_latch
      - service: timer.start
        entity_id: timer.back_door_motion_latch
        # add the rest of your services

Timer Automation

Create this simple automation. After the timer expires, it turns off input_boolean.back_door_motion_latch thereby cancelling the latching effect.

- alias: 'Motion Latch timer finished'
  trigger:
  - platform: event
    event_type: timer.finished
    event_data:
      entity_id: timer.back_door_motion_latch
  action:
    - service: input_boolean.turn_off
      entity_id: input_boolean.back_door_motion_latch
2 Likes

You could also try doing what you have but instead of using the state attr of the automation last being triggered try using the binary sensor itself as the condition and use last_changed and make it something like 2 or 3 seconds … still might be too fast of a bounce though and you may have to go to input Boolean route suggested above

EDIT: sorry should have read more closely it seems you said it wasn’t;t working as you had it because it was always false… this is what I have…

value_template: "{{(as_timestamp(now()) - (as_timestamp(states.group.key_group.last_changed))>60)}}"

so your’s would be:

value_template: "{{(as_timestamp(now()) - (as_timestamp(states.binary_sensor.back_door_motion.last_changed))>5)}}"
4 Likes

i’m still waiting for an explanation of why the condition as written won’t work…

1 Like

Thank you so much for taking the time with such a well written and comprehensive answer. I appreciate it.

1 Like

I’ll give that a try too.

Thank you for your time.

At the point the condition is tested the automation has just been triggered so the condition is false.

It will work if the notification firing was used in the template, instead of the automation trigger, but I’m not sure it can beacuse notify.mobile_app is not unique to this activity.

I don’t think that’s correct at all.

I don’t think the “last_triggered” gets set until the action part actually runs.

Have you actually tested that?

Yes, I tested it today without success. Still working on a solution though.

You can leverage the fact that an automation can turn itself off and on. As it turns out, turning off an automation while it is running doesn’t halt the execution of the script running within the automation. When the automation is turned off it can’t be triggered again.

- alias: test
  trigger:
    - platform: state
      entity_id: binary_sensor.foobar
      from: 'off'
      to: 'on'
  action:
    - service: homeassistant.turn_off
      entity_id: automation.test 
    ### add your actions here
    - delay: 00:01
    - service: homeassistant.turn_on
      entity_id: automation.test

well, that’s strange.

I just built a test automation:

- alias: test
  initial_state: 'on'
  trigger:
    platform: state
    entity_id: input_boolean.bool_15
    to: 'on'
  condition:
    - condition: template
      value_template: '{{ (as_timestamp(now()) - as_timestamp(state_attr("automation.test", "last_triggered")) | int > 10)}}'
  action:
    - delay: '00:00:05'
    - service: homeassistant.turn_off
      entity_id: input_boolean.bool_15

And when I turn bool_15 on in 5 seconds the boolean gets turned back off (the automation works). If I wait more than 10 seconds from the first time I switch it on then it goes off again in 5 seconds (the automation works).

If I switch it on again withing 10 seconds after the automation turns it off then it stays on forever until I turn it off (the automation works therefore keeping the switch on by not running the action).

I think I’ve proven to my satisfaction that what I’m saying is correct.

2 Likes

Thank you, finity, you are quite correct. It does indeed work. I re coded it this morning and voilà!

For those who may also need this, here’s my yaml:

- alias: Announce Mail has been delivered
  hide_entity: false
  initial_state: 'on'
  trigger:
    - platform: state
      entity_id: binary_sensor.letterbox
      from: 'off'
      to: 'on'
  condition:
    - condition: template
      value_template: '{{ (as_timestamp(now()) - as_timestamp(state_attr("automation.announce_mail_has_been_delivered", "last_triggered") | default(0)) | int > 5)}}'
  action:
    - service: tts.google_say
      data:
       entity_id: media_player.kitchen_speaker
       message: "Mail has been delivered"
1 Like

You forcing me to prove you were wrong :wink: actually helped me learn something.

I now know that the “last_triggered” gets updated as soon as the actions begin to be executed and not when they are complete.

So the sequence of operation is:

check if the trigger is true
check for condition true
if true then update “last_triggered”
perform actions

You can see the effects of that sequence by swapping the times between the delay and the template in my automation. If you set the delay for 10 seconds and set the template compare value to 5 seconds then the condition will always be true and the automation will run every time since no matter how fast you are you can’t make 10 seconds to be less than 5 seconds. :slightly_smiling_face:

I realized that because I actually did have those values swapped and could never get the automation to not run.

And…

I think it gets even stranger than that because the sequence isn’t exactly as i have it above. There was a thread a while back in which I’m fairly sure it was proven that the conditions actually get evaluated before the trigger gets evaluated.

so it seems that the real sequence is:

check if the conditions are true
check if the trigger is true
set “last_triggered”
perform actions.

I’m not seeing that behavior (tested with 0.89 and 0.91).

I recall posting the results of an experiment using for and a condition with a time-based threshold. Unfortunately, I no longer recall the thread. However, it’s very easy to repeat the experiment.

The following automation turns off light.mantle after 3 minutes but only if the current time is before 11:30:00.

- alias: 'test timed-light off'
  trigger:
    entity_id: light.mantle
    platform: state
    from: 'off'
    to: 'on'
    for: '00:03:00'
  condition:
    condition: time
    before: '11:30:00'
  action:
    - service: light.turn_off
      data:
        entity_id: light.mantle

Here’s what I did:

  • I turned the light on at 11:26 and the automation set the light off, 3 minutes later, at 11:29.
  • Then I immediately turned the light on again. Now the 3-minute period crosses the threshold.
  • At 11:32, the light was not turned off and stayed on.

The automation checked condition after the 3-minute period expired, discovered the current time (11:32) exceeds the threshold time (11:30) and did not execute the action.

Try it and let me know if you get the same result or if the light gets turned off even if the period crosses the threshold.

This is getting more and more interesting by the minute.

I never expected my original post would stimulate such discussion.

Yeah, I didn’t test that specific case myself so maybe something has changed in the later version of HA to “fix” it.

Isn’t the internet great?!! :grinning: