Debounce

Debouncing is a technical term for the following behavior. After an initial event, ignore subsequent event for a certain amount of time to give it time to settle. For example, in electronics, pressing a button may result in an initial press event but after releasing the button it may physically bounce a little sending several pressed and released events until it settles.

In openHAB there are a number of use cases where this overall approach can be applied in a larger context. For example, when detecting presence one may want to only count a person as away if they have been away for five minutes to prevent the home from going into away mode when the person simply walks to the mailbox and back. Sometimes a network can be a little flakey and a service may appear offline for a minute when it’s actually running so we want to wait for a couple minutes before alerting that it’s gone offline. There are many more use cases of course.

While there are several Debounce Profiles posted here and there on the forum and elsewhere and an issue with a PR to implement it as part of OH itself, a Profile only applies to a Link between a Channel and an Item. It cannot be used with Items alone and therefore its use is limited.

How it works:

Each Item to be debounced needs to have two Items, a “raw” Item and a “proxy” Item. The “raw” Item has the value that is to be debounced. The “proxy” Item is the Item that receives the debounced value and is the Item that represents the sensor in openHAB (e.g. put on the UI, used in a rule, etc.).

All of the “raw” Items must be added to a “Debounce” Group. This Group is what triggers the Debounce rule to run when the states of the “raw” Items change.

Each “raw” Item must also have debounce Item metadata. This tells the rule the name of the corresponding “proxy” Item and some configuration properties that tell the rule how long to debounce the state, an optional list of those states to debounce (e.g. one can only debounce the OFF state but immediately process the ON state), and whether the debounced state should be sent to the proxy Item as an update or a command.

In .items files an example would look something like:

Switch RawSensor (Debounce) { debounce="ProxySensor"[timeout="2m", states="OFF", command="False"] }
Switch ProxySensor

or in the UI

value: ProxySensor
config:
  states: OFF
  timeout: 2m
  command: "True"

In the UI, navigate to the “raw” Item, click on “Add Metadata”, click “Enter Custom Namespace…” (under “Custom namespaces” section) and enter “debounce” as the namespace.

Property Purpose Allowed values
value Name of the proxy Item Valid Item name, should not be the same as the Item with the metadata.
states List of states that are to be debounced. Leaving this option out or setting it to empty string will debounce all Item states. Comma separated list of Item states or empty string.
timeout How long to debounce the value for. A duration string using xHxMxS type format where D=days, H=hours, M=minutes, S=seconds, Z=msec. Case doesn’t matter and any one or more units can be used. For example “4h5s”.
command An optional boolean indicating whether the proxy should be commanded or updated. “true” or “false”, defaults to “false”.

See Generic Presence Detection for an example using this for debouncing presence detection.

Language: Nashorn JavaScript or JSScripting

Dependencies:

  • A Debounce Group created
  • All the “raw” Items added to the Debounce Group
  • All the “raw” Items have valid “debounce” Item metadata

Changelog

Version 0.3

  • adjusted for breaking change, will no longer work for versions of OH prior to December 15th

Version 0.2

  • Made compatible with both Nashorn and JSScripting
    NOTE: Sometime after OH 3.2 I will remove support for Nashorn so I can use the helper library.

Version 0.1

  • initial release

Resources

https://raw.githubusercontent.com/rkoshak/openhab-rules-tools/main/rule-templates/debounce/debounce.yaml

8 Likes

Regarding the example

Should this be ProxySensor instead?

Yes indeed. Thanks for noticing and letting me know. That’s what happens when you copy and paste and try to change names in the process.

Hi all

A short question, does this work with String items or do one need to use timer for debouncing string items?

Usecase is that follows, on my ev charger I would like to let loafbalancibg settle betweeen changes and not just loop trough stepping up or down availible curren.

It should work with String Items but I confess I never tested it with them. If it’s not working for you please enable debug on the logger and post the Item definition, debounce metadata, and events.log entries along with the debounce debug logging so I can see what’s going on.

I mostly use it with Switches and Contacts. I did test it with Numbers but not QuantityTypes which might cause some problems. But in the rule itself it normalizes everything to Strings anyway so a String Item should work just fine.

Hi

Thanks for quick reply as allways. Hence I would be needing a number item in my debounce setup I started with that and my aka proxy item is just null. Maybe that’s what you already predicted.

Usecase for my number item.
Home energymeter will report gridlload changes to an number Item and I will just like to let the ev box API and box have a chance to react and report back before it loops again.

I will just keep digging in my timers i suppose.

OT is there a way to verify varibales in OH i use to set a variable timestamp and have condition isBefore too do debouncing.

I don’t think that use case would work because as long as the raw item keeps changing the Denbounce won’t happen. It’s only when the raw item remains the same state fire the time will it forward the state to the proxy item.

It depends what you mean by verify. In general a variable isn’t very useful if you can’t check what it is. Verifying usually just means an if statement to see if the variable is what you expect it to be.

@rlkoshak thanks for your useful templates. I’m just about to finally migrate from OH 2.5 with jython rules to OH 3.2 with jsscripting. Since I used your jython implementation of debounce before and now wanted to use this rule template as a replacement, I stumbled over some differences:
It seems that the metadata configuration was on the “Debounce”-group for the jython implementation and now has to be set for every individual sensor of the group, correct? Is there any specific reason why this was changed? (I don’t really mind, which way is used, I was just wondering why my previous item configurations did not work with the “new template” and was figuring what has to be changed)

That’s not how it worked. In the Python version, there was no Debounce Group. You’ve always had to define debounce metadata on each Item. For this rule template, you need to create a Group and add all your Debounce Items to that Group. The Item metadata has not changed so your old Item metadata will still work.

In the text based files it made sense to have a rule that creates another rule. That’s how the old Jython rule was able to work using just the metadata on the Items. In this case it doesn’t make sense to have a rule template that creates a rule that creates yet another rule which needs to be recreated every time you change the debounce metadata on an Item. So I just use a Group.

Ah, sorry, I misinterpreted the old configuration then. Used your Generic Presence Detection by then and was a bit confused by the nested groups in my configuration (which I haven’t touched for quite some time now). I also somehow refused to accept that the Debounce Group is just a container for the debounce items and nothing more (despite being the rule trigger) :sweat_smile:. I should have re-read the presence detection topic …
(I remember now, that the jython rule built its own trigger using all items with corresponding metadata instead of a group)
Everything clear now, thanks for your efforts.

I’m a little bit lost on this.

Using OpenHab 3.2.0-1 on RaspberryOS completely on MainUI (i.e. no manual item definitions)

First of all I got nuts, because the debounce trigger didn’t work at all, until I figured out, that I had to define 1 rule from the debounce-template for each debounce group. Perhaps you should add this to the description for newbies like me.

Now I don’t get the expected results.

I defined

  • a Switch “PresenceProxyDebounced”
  • a Switch “PresenceSimulation” which I can switch manually form UI
    • “PresenceSimulation” is in the defined debounce group
    • has metadata
value: PresenceProxyDebounced
config:
  timeout: 30s
  command: "false"
  states: OFF

Here’s what I’m doing:

  • When I switch PresenceSimulation from OFF to ON, ON is propagated to PresenceProxyDebounced. Fine!
  • When I switch PresenceSimulation from ON to OFF and wait >30s, nothing happens at once and after 30s OFF is propagated to PresenceProxyDebounced. Fine!
  • When I switch PresenceSimulation from ON to OFF and back to ON after 10s, nothing happens at once and after 30s OFF is propagated to PresenceProxyDebounced. Which isn’t intended, is it?
    • Expected result to me was: PresenceProxyDebounced remains completely unchanged (immediately and after 30s)
    • it seems to me, that the ON event after 10s doesn’t cancel the running timer

Here is the corresponding event.log:

2022-03-26 15:44:00.812 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'PresenceSimulation' received command OFF
2022-03-26 15:44:00.819 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'PresenceSimulation' changed from ON to OFF
2022-03-26 15:44:10.775 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'PresenceSimulation' received command ON
2022-03-26 15:44:10.781 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'PresenceSimulation' changed from OFF to ON
2022-03-26 15:44:30.858 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'PresenceProxyDebounced' changed from ON to OFF

Here is the corresponding openhab.log (debug enabled):

2022-03-26 15:44:00.827 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Match = 30s
2022-03-26 15:44:00.830 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Days = 0 hours = 0 minutes = 0 seconds = 30 msec = 0
2022-03-26 15:44:00.833 [DEBUG] [el.script.Rules.rules_tools.Debounce] - Debouncing PresenceSimulation with proxy = PresenceProxyDebounced timeout = 30s and states = OFF
2022-03-26 15:44:00.835 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Timer manager check called
2022-03-26 15:44:00.838 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Match = 30s
2022-03-26 15:44:00.840 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Days = 0 hours = 0 minutes = 0 seconds = 30 msec = 0
2022-03-26 15:44:00.842 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Timer to be set for 2022-03-26T15:44:30.841984+01:00[Europe/Berlin]
2022-03-26 15:44:00.844 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Creating timer for PresenceSimulation
2022-03-26 15:44:00.846 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Timer created for PresenceSimulation
2022-03-26 15:44:10.792 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Match = 30s
2022-03-26 15:44:10.796 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Days = 0 hours = 0 minutes = 0 seconds = 30 msec = 0
2022-03-26 15:44:10.800 [DEBUG] [el.script.Rules.rules_tools.Debounce] - PresenceSimulation changed to ON which is not debouncing
2022-03-26 15:44:10.802 [DEBUG] [el.script.Rules.rules_tools.Debounce] - End debounce for PresenceProxyDebounced, new state = ON, curr state = ON, command = false
2022-03-26 15:44:30.842 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Timer expired for PresenceSimulation
2022-03-26 15:44:30.844 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Calling expired function function() {
        logger.debug("End debounce for " + proxy + ", new state = " + state + ", curr state = " + items[proxy] + ", command = " + isCommand);
        if(isCommand && items[proxy] != state) {
          logger.debug("Sending command " + state + " to " + proxy);
          events.sendCommand(proxy, state.toString());
        }
        else if (items[proxy] != state) {
          logger.debug("Posting update " + state + " to " + proxy);
          events.postUpdate(proxy, state.toString());
        }
      }
2022-03-26 15:44:30.847 [DEBUG] [el.script.Rules.rules_tools.Debounce] - End debounce for PresenceProxyDebounced, new state = OFF, curr state = ON, command = false
2022-03-26 15:44:30.851 [DEBUG] [el.script.Rules.rules_tools.Debounce] - Posting update OFF to PresenceProxyDebounced
2022-03-26 15:44:30.854 [DEBUG] [el.script.Rules.rules_tools.TimerMgr] - Deleting the expired timer

Is my expected result wrong?
Did I miss some necessary configuration?

…you are not the only one seeing “unexpected” behaviour… i tried do use debounce also but it seems to not propagate the on states correctly…

see one of my graphes for example (yellow is ON, blue is OFF):


(debounce time is set to 5 minutes)

It’s not feasible for all rule templates to document the core process for installing and using a template. It is covered in the draft for the rules section of Getting Started and likely will be covered in other sections of the docs too.

Correct, when it changes to ON any debounce should be canceled since the only state debounced is OFF.

I’ll have to look into this. The log is showing that the timer should have been cancelled. “End debounce for PresenceProxyDebounced …”. But I’m not seeing the debug logs indicating that the timer actually was cancelled.

That’s the root of the problem.

2 Likes

@DARqz, @krikk I think I have a fix which seems to be working for me but I’d like you to test it out too just in case I’m missing something.

Open the rule that was instantiated from the rule template and open the Action:

Insert the following on line 209: this.timers.cancel(event.itemName); // Cancel the timer if it exists. It should look like this:

else {
  logger.debug(event.itemName + " changed to " + event.itemState + " which is not debouncing");
  this.timers.cancel(event.itemName); // Cancel the timer if it exists
  end_debounce_generator(event.itemState, cfg["proxy"], cfg["command"])();
}

That will explicitly cancel the timer when the Item returns to a non-debounced state. This is one of those cases where I can’t imagine how it’s been working before now.

1 Like

Thanks for sharing this Rule Template. I have quite a few anti-flapping timers that I will (over time as I make other changes) replace with this. I converted a couple of them to begin with, and it works great.

i implemented you code change, but i still get strange behaviour… i have a test setup like described in your post " Generic Presence Detection and i have this logs:

2022-04-03 22:08:20.833 [DEBUG] [el.script.Rules.rules_tools.Debounce] - Sending command OFF to Person1Presence
2022-04-03 22:08:20.857 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Person1Presence' received command OFF
2022-04-03 22:08:20.862 [INFO ] [hab.event.GroupItemStateChangedEvent] - Item 'Presence' changed from ON to OFF through Person1Presence
2022-04-03 22:08:20.865 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Person1Presence' changed from ON to OFF
2022-04-03 22:17:52.339 [INFO ] [openhab.event.ItemCommandEvent      ] - Item 'Person1Sensor1' received command ON
2022-04-03 22:17:52.349 [INFO ] [openhab.event.ItemStateChangedEvent ] - Item 'Person1Sensor1' changed from OFF to ON
2022-04-03 22:17:52.352 [INFO ] [hab.event.GroupItemStateChangedEvent] - Item 'Person1Presence_Raw' changed from OFF to ON through Person1Sensor1
2022-04-03 22:17:52.360 [DEBUG] [el.script.Rules.rules_tools.Debounce] - Debouncing Person1Presence_Raw with proxy = Person1Presence timeout = 5m and states =
2022-04-03 22:22:52.374 [DEBUG] [el.script.Rules.rules_tools.Debounce] - End debounce for Person1Presence, new state = ON, curr state = OFF, command = true
2022-04-03 22:22:52.391 [DEBUG] [el.script.Rules.rules_tools.Debounce] - Sending command ON to Person1Presence

in this log i do not understand why the line

Item 'Person1Presence_Raw' changed from OFF to ON

does not trigger a change of Person1Presence… if the item Person1Presence_Raw goes to ON, shouldn’t this immediatly trigger Person1Presence to ON??

…i just have seen that Person1Presence goes to ON, exactly 5 minutes later (my debounce time!)… is this a bug? …or do i have a wrong understandig what debounce should do…? or do a miss something??

If that’s how the metadata is configured. Assuming you’ve not changed the metadata from above where “states” only lists OFF, then it should immediately go to ON and the timer become cancelled.

That’s how it’s working for me with the change from above.

And the logs do indicate that the rule did send the ON command to Person1Presence. Those last two lines show that happening immediately in response to Person1Presence_Raw changing from OFF to ON.

You do not show any logs after that. You should see in events.log Person1Presence receiving that ON command and it changing to ON (assuming it wasn’t already ON). You shouldn’t see anything else in openhab.log after the “Sending command ON…” statement because the timer was in fact cancelled. The Debounce rule has nothing left to do for that Item change.

You’ve not posted enough logs to see what’s going on. From what’s posted above it’s working as expected.

i am not sure you noticed the 5 minutes delay, so i reformated the logs:

22:08:20.833 Sending command OFF to Person1Presence
22:08:20.857 Item ‘Person1Presence’ received command OFF
22:08:20.862 Item ‘Presence’ changed from ON to OFF through Person1Presence
22:08:20.865 Item ‘Person1Presence’ changed from ON to OFF
22:17:52.339 Item ‘Person1Sensor1’ received command ON
22:17:52.349 Item ‘Person1Sensor1’ changed from OFF to ON
22:17:52.352 Item ‘Person1Presence_Raw’ changed from OFF to ON through Person1Sensor1
22:17:52.360 Debouncing Person1Presence_Raw with proxy = Person1Presence timeout = 5m and states =
22:22:52.374 End debounce for Person1Presence, new state = ON, curr state = OFF, command = true
22:22:52.391 Sending command ON to Person1Presence

…but if i understand you correctly, the event should pass immediatly and not with 5 minutes (the debounce time) delay… but thx for the clarification, will try to to another test to reproduce it again…

PS: the debounce config of Person1Presence_Raw:

value: Person1Presence
config:
  command: "True"
  state: OFF
  timeout: 5m

I didn’t see the five minute delay. It’s hard to line thing up on a small screen.

The relevant code is as follows:

if(cfg["states"].length == 0 || 
  (cfg["states"].length > 0 && cfg["states"].indexOf(event.itemState.toString()) >= 0)) {
  logger.debug("Debouncing " + event.itemName + " with proxy = " + cfg["proxy"] 
               + " timeout = " + cfg["timeout"] + " and states = " + cfg["states"]);
  this.timers.check(event.itemName, cfg["timeout"], 
                    end_debounce_generator(event.itemState, cfg["proxy"], cfg["command"]));    
}
else {
  logger.debug(event.itemName + " changed to " + event.itemState + " which is not debouncing");
  this.timers.cancel(event.itemName); // Cancel the timer if it exists
  end_debounce_generator(event.itemState, cfg["proxy"], cfg["command"])();
}

In prose: If there is no “states” entry in the Item’s metadata or the item’s new state is among the comma separated list of states in the “states” entry in the Item’s metadata, create a Timer to call the function created by end_debounce_generator at the configured time.

If there is a “states” entry in the Item’s metadata but the Item’s new state is not among the comma separated list of states, immediately cancel the timer (if there was one) and immediately call the function created by end_debounce_generator.

That first part of the if ran, which means either there was no “states” entry or ON was among the list. And indeed, looking back at the metadata you don’t have a “states” entry. You have “state”.

yes, you found the error… big thanks, i did not see this… plz can you update your post because i think i copied the error from there :slight_smile: