Duration based binary sensor triggers

I have an ESPHome node that has a binary sensor connected to a GPIO pin. I want to trigger different actions if the input has been active for various times, or inactive for various times. For example, I want to trigger four different automation actions:

  • action A when the input has been ON for 2 seconds
  • action B when the input has been ON for 15 seconds
  • action C when the input has been OFF for 2 seconds
  • action D when the input has been OFF for 15 minutes (no, this isn’t a typo, I don’t mean 15 seconds.)

This would be very easy to do in a Home Assistant automation, but I would like this to happen inside the ESPHome node itself. I’m struggling to figure this out.

If all I was interested in were action A and C, I could set a two second filter on the binary sensor, and then use the on_press or on_release triggers of the sensor. But that’s only half of the requirement, how would I also implement the longer actions? Can multiple filters been set, or can each trigger have its own filter?

There is the on_click trigger, which allows setting minimum and maximum times, but this seems to be looking for the input to become active for a certain time, and then perform the action when the input becomes inactive, assuming that it’s been active for the correct time range. I don’t want this, I want the triggers to fire as soon as the input has been in the right state for the right time, and not when it hits the opposite state.

The for condition seems like it has promise, but I can’t figure out where I would put this. If I put it on the on_press or on_release actions for the binary sensor, wouldn’t it only check this condition when the input pin first changes, and always be false? Seems like the for condition would have to be in an automation for another trigger, but what could that be? Seems like it would have to be an interval trigger that’s frequently being triggered, but then you have to make sure that you don’t trigger on each interval after it’s been long enough (say the interval is 1 second, and the for condition is 15 seconds; seems like the for condition would be satisfied at the 15 second point, as well as 16, 17, 18 and all subsequent seconds?)

Am I overthinking all of this? There must be a reasonable way to do this.

Did you also take a look at on_multi_click:?

Use the copy binary sensor to mirror the actual sensor to multiple binary sensors, then use delayed_on and invert filters as required to make each one report one of your desired actions.

@Mahko_Mahko - I didn’t try on_click as I read it that it looks for active-delay-inactive where delay is within the min and max range. I want an action that triggers as soon as the input is active for a set time, without the input needing to go inactive.

@clydebarrow - That sounds perfect, I’ll give it a try. I was thinking that a second sensor on the same input pin would give me what I wanted, but I couldn’t reference the same pin on two sensors. I didn’t know about a copy sensor.

Edit: fixed some typos

My reading is that “on for at least 2s” would trigger once 2 sec has elapsed. I think it will accept just the on event without a need for further off events. But you’d need to check.

Then you could add another timing for 15s I think.

on_multi_click

My test code:

binary_sensor:
  - platform: gpio
    name: Raw Input
    id: raw
    pin:
      number: GPIO36
      inverted:  False
      mode: INPUT

  - platform: copy
    source_id: raw
    name: OnClick
    on_click:
      min_length: 2s
      max_length: 10s
      then:
        - logger.log: "OnClick event"

  - platform: copy
    source_id: raw
    name: ShortActive
    filters:
      - delayed_on: 2s
    on_press:
      then:
        - logger.log: "ShortActive event"

  - platform: copy
    source_id: raw
    name: ShortIdle
    filters:
      - invert:
      - delayed_on: 4s
    on_press:
      then:
        - logger.log: "ShortIdle event"

  - platform: copy
    source_id: raw
    name: LongActive
    filters:
      - delayed_on: 10s
    on_press:
      then:
        - logger.log: "LongActive event"

  - platform: copy
    source_id: raw
    name: LongActive
    filters:
      - invert:
      - delayed_on: 20s
    on_press:
      then:
        - logger.log: "LongIdle event"

@Mahko_Mahko - Thank you for your help, but the on_click event works as I suspected: it only fires upon an active-delay-inactive sequence, when the delay is within the specified range. Any shorter or longer, it doesn’t fire. And it doesn’t fire when either the minimum or maximum time expires, only when a pulse is the correct duration.

But the four other events work perfectly! This is exactly what I wanted, thank you @clydebarrow.

For others that follow me, the only potential issue is that for a long duration input, the ShortActive event fires after the 2 seconds, and then the LongActive event fires 8 seconds later, after the expected total 10 seconds. Similarly for the ShortIdle and LongIdle events: a long duration deactivation of the input causes both to fire, at the appropriate times. I expected this, and it is not an issue for me, but this may not be what someone else might want. If only one event is desired based on the activation time, then on_click is probably what is wanted, but you lose the immediate event when the delay has passed.

I forgot to comment on this in my last post. I did try leaving off the max time, and it looks like it simply used the default 350ms value, which led to the error: " Max length (350ms) must be larger than min length (2s)."

But did you look into on_multi_click:, which is what I’ve been referring and linking to? Not on_click:.

My apologies, I guess I read your initial reply too quickly, and missed that the mention of “on_click” was a quote of me. I thought you were recommending on_click as well as on_multi_click.

I did not try on_multi_click for two reasons:

  • primarily because it appeared to be a superset of on_click, and appears to have the same reliance on the signal turning off to complete the timing (every example ends with an off time.)
  • but mostly because I had got it to work perfectly with the copy sensor :wink:
1 Like

Trying to use all those Copy sensors will be a nightmare and will never work right. Although using them this way is possible, they really aren’t meant to be used like this. For one reason, you already have the capability to combine all of these copy sensors and integrate them into your GPIO binary sensor like I’ve made an example of below. Copy sensors do work really well if you need to do any equations to calculate multiple values from 1 input. It also looks like some of those will trigger multiple copy sensors from 1 button press. It’s all bad and I’m not sure why people didn’t encourage you to avoid making it like this.

The second part where you have 10s delay_on and 20s delay _on and one is inverted but not the other… I have no idea what you’re trying to do here, it doesn’t make sense to me but, if you want to explain what the goal is, then I’m sure a solution can be found. One that will actually work as intended that is…

Instead of having 8 different copy sensors, you now have 1 sensor with 7-8 possible actions from the button timing. Is there a reason you don’t just add a couple of extra buttons instead of going this route?

In my experience, buttons like this don’t stay cool veryt long and you’ll hate having 1 button that requires push/hold and timing sequences. You’ll constantly trigger the wrong thing or you will get irritated at it because your on/off timing isn’t matching so the thing your trying to turn On, it won’t freaking turn on!

I strongly suggest adding more buttons if you can and keeping them to only 2 or max 3 possible actions or it will drive you nuts.

@Fallingaway24, thank you for taking the time to write a very thoughtful post. It is very much appreciated. But I think you are making some assumptions about what I’m doing which aren’t correct.

you already have the capability to combine all of these copy sensors and integrate them into your GPIO binary sensor like I’ve made an example of below.

While I admit that I didn’t try on_multi_click, I did try on_click (which is why it is in my test code that I posted, and not in the final code) but it did not do what I wanted. In your example, “ShortActive event” will only be logged if the signal was active for at least 2 seconds and at most 3 seconds, at the time that the signal went inactive. In other words, as the name implies, when a button is clicked for 2 to 3 seconds. I don’t want this, I want the “ShortActive” even to trigger as soon as the signal has been active for 2 seconds, regardless of when it becomes inactive. In my application, the signal will typically be active for 12 hours, and with on_click I wouldn’t get the ShortActive or even LongActive at all. I tried to explain this above, but apparently wasn’t clear.

The second part where you have 10s delay_on and 20s delay _on and one is inverted but not the other… I have no idea what you’re trying to do here, it doesn’t make sense to me

One is inverted and one is not because I want one to fire as soon as the signal as been active for 10 seconds, and I want the other to fire as soon as the signal has been inactive for 20 seconds.

but, if you want to explain what the goal is, then I’m sure a solution can be found.

I tried explaining it at the beginning of the first post:

I have an ESPHome node that has a binary sensor connected to a GPIO pin. I want to trigger different actions if the input has been active for various times, or inactive for various times. For example, I want to trigger four different automation actions:

  • action A when the input has been ON for 2 seconds
  • action B when the input has been ON for 15 seconds
  • action C when the input has been OFF for 2 seconds
  • action D when the input has been OFF for 15 minutes (no, this isn’t a typo, I don’t mean 15 seconds.)

And a little later on in that first post: (with added emphasis)

There is the on_click trigger, which allows setting minimum and maximum times, but this seems to be looking for the input to become active for a certain time, and then perform the action when the input becomes inactive, assuming that it’s been active for the correct time range. I don’t want this, I want the triggers to fire as soon as the input has been in the right state for the right time, and not when it hits the opposite state.

This is not a button being clicked, it is an actual binary sensor, basically an infrared beam detector. I want to know as soon as it has been active for a short time, and in addition I want to know if it has been inactive for a longer time. I also want the inverse: trigger when inactive for a short time, and trigger when inactive for a longer time.

It’s basically two independent debouncings off of the same input. One that triggers after a short time, and one that triggers after a longer time.

Looking at it again, perhaps I could’ve gone with only two copies. Instead of using the inverted option on two of them, perhaps adding a delayed_off filter and on_release action would’ve accomplished the same thing. I may try it if I have a reason to go into the code again. (I’m slightly hesitant to muck around with working code, i have other things to do.)

Instead of having 8 different copy sensors, you now have 1 sensor with 7-8 possible actions from the button timing. Is there a reason you don’t just add a couple of extra buttons instead of going this route?

I don’t have 8 copies, only 4, and maybe only 2. You say 7-8 actions from a button, but the issue is that this is NOT a button. It is a sensor and I can’t make it perform arbitrary timing. And I can’t add extra buttons because I only have the single output bit from the sensor.

In my experience, buttons like this don’t stay cool veryt long and you’ll hate having 1 button that requires push/hold and timing sequences. You’ll constantly trigger the wrong thing or you will get irritated at it because your on/off timing isn’t matching so the thing your trying to turn On, it won’t freaking turn on!

I completely agree with you! And I wouldn’t be going through this exercise if this was a button I was trying to manually trigger. But it’s not a button, and it’s not being clicked, I need to react when the steady state changes for different intervals.

I understand what your saying but, what I think you may not fully understand is all of the different button press sequences/timings are just methods for controlling multiple triggers. The fact that the state may go Off after 2s and before 3s doesnt mean that the action you need to happen is also bound to that brief 1 second.

For example, if you think about how you would use a PIR sensor to turn on some lights, right? The PIR(binary_sensor) changes to On and then quickly changes back to Off, right? That doesnt mean my light also turns On and Off to match the PIR sensor states. Another example, I use a binary sensor for a light switch. It wouldnt be very useful if i had to hold the button inorder to have light so, i made the light turn On and stay On when the button is pressed and it doesnt turn Off untill I press the button again and the 1s On/Off of the binary sensor has absolutely nothing to do with how the light works or how long it works. The binary_sensor is only the trigger. Its all in how you create your automations for each button trigger but, you are in no way limited by how long the binary sensor state stays On or how fast it goes Off. The only important thing is the state change event that is your trigger for something else. So, each of those different buton sequences are nothing but a unique trigger for an endless possibility of actions.

The way you’ve done it, it may be sufficient for whatever your doing for this project but, with more advanced automations your going to find out that doing it that way, it seriously limits your possible uses. There are many cases where you will discover that there are many possible ways to do something but, in most cases there are not so good ways and the best ways or the right ways to do it.

The example I posted was just that. Its an example and wasnt mesnt to be a copy/paste replacement for your existing projec.

I understand what your saying but, what I think you may not fully understand is all of the different button press sequences/timings are just methods for controlling multiple triggers. The fact that the state may go Off after 2s and before 3s doesnt mean that the action you need to happen is also bound to that brief 1 second.

No, I don’t think you understand what I am saying. IT’S NOT A BUTTON!

And I am not using the current state of the input to directly control something. I understand what you are trying to say in the rest of the post, and in your examples, and I’ve used such patterns many times in the past, but it does not apply to this use case.

I’ve tried to explain it a few times now, let me try another way. It’s similar to a photosensor beam. It can change state for a short time sometimes, and I want those transitions to trigger one pair of actions. Or it can change state for a long time sometimes, and I want those transitions to trigger another pair of actions.

For example:

  • The input is inactive, and has been for a while
  • The input goes active, and stays active.
  • 2 seconds later, the ShortActive series of events happen.
    • Note that the input has not yet gone inactive!
  • 15 minutes later, the LongActive series of events happen.
    • Note that the input still has not yet gone inactive!
  • Hours later, the input goes inactive, and stays inactive
  • 2 seconds later, the ShortInactive series of events happen
  • 15 seconds later, the LongInactive series of events happen

Note that in this example, the input signal has made one “click” that lasted for hours, yet all four actions were triggered in a timely manner. I have used on_click many times in other projects, where it makes sense. But in this case, the length of the signal being active is open ended, and I need the action to trigger long before the “click” is finished. (And also note that this can sometimes be a noisy signal, so I want to ignore short activations/deactivations, hence the 2 second delays.)

Also note that in my prior test code example, I’m simply logging a short string as a placeholder. I didn’t want to clog the example with the clutter of actual automation code. After all, the focus of the example is the trigger, not the action.

I appreciate your concern, and I thank you for your participation. I agree that this is not a common way to handle this, but this is not a common situation. I would not do it this way if it were a normal situation, and I can see where doing this routinely could become a maintenance headache.

While I admit that I am relatively new to ESPHome, and I have a lot to learn (I only have about 32 ESPHome projects currently running) I do have 39 years of professional embedded programming experience with commercial, military, and medical devices, and I do understand the concepts that you are trying to teach. I know what I want the project to do, and I know how I would be programming it if I was writing the actual code myself. But I spend enough time writing code for my job, that when I’m doing a personal project, if there’s a faster way to do it (like ESPHome) then I’m all for it!