Closed jimtng closed 1 year ago
This seems like a more generalized form of for: <duration>
on a single trigger, no? Can you flesh out some concrete examples some more to get a feel for how useful it would actually be?
This is applicable for example when you have triggers on multiple items and you want to update a calculated item.
myitems = Item1, Item2, Item2, Item4
rule do
changed *myitems
run { SummaryItem.update "Total: #{myitems.map(&:state).sum}" }
end
You don't want the rule to run 4 times when all Item1 .. Item4 were updated usually at the same time (e.g. detailed properties of something coming from the same binding). debounce for: 1.second, trigger_on: :end
is good for this. Most debouncing needs the :end
trigger so it should be the default.
A slightly different scenario: If the changes occur continuously without any rest, the SummaryItem would only be "refreshed" / calculated once per second. This would apply even when triggering on just one item, and also the change duration trigger would not work here - as the value never stops changing. In this scenario either trigger_on: :start or :end works equally. In this scenario, one could use a cron/every :second trigger, but what if the changes only occur in bursts.
As to how useful this would be, that's open for debate, hence this post.
I like it. It also reminds me I want to do a calculated_item(SummaryItem) { myitems.map(&:state).sum }
terse rule (and automatically detect what items need changed
triggers; I've figured out a strategy to do that). I could definitely forward any debounce args for that.
My only reservation is naming of parameters so that they're clear to someone unfamiliar with the code. These are by no means recommendations, I'm just brainstorming:
# ideas for the continuously updating items use case
debounce for: 1.second, max_wait: 1.second
debounce for: 1.second, continuous: true
debounce for: 1.second, wait_for_settle: false
debounce for: 1.second, wait_for_quiesce: false
# trigger_on: :start
debounce for: 5.minutes, on_start: true
debounce for: 5.minutes, leading: true
debounce for: 5.minutes, trigger_on: :leading_edge
# maybe different term for the duration?
debounce over: 5.minutes
debounce period: 5.minutes
If/when you implement this, you should redo the current changed Item, for: duration
to use the same debounce back end.
I've just thought of one application for a leading edge debouncing: Prevent door bell from being pressed repeatedly. We want to trigger on the first press and ignore subsequent presses within X seconds.
Omg yes please. I currently already have that exact rule, but implemented by just storing a timestamp in a map of the last time it was triggered, iirc.
I have a "doorbell" on the keypad at each fence gate, and my youngest love to just pound on that button sometimes.
See also https://github.com/boc-tothefuture/openhab-jruby/issues/356, which seems to be a subset of the full "debounce" functionality, but is also some ideas for how to name things (though possibly already shot down?).
Just recording an idea that came to mind. Also implement a DSL.debounce so it can be used within a UI rule:
debounce for 1.minute do
...
end
An idea: instead of using one debounce
guard and making it complex, we introduce three guards:
debounce_for 3.seconds[, max_wait: 10.seconds]
throttle_for 3.seconds # this is a trailing edge debouncer
only_every 3.seconds # this is a leading edge debouncer
debounce_for
ensures that there's a minimum duration between two triggers before it would let the rule runs. max_wait
is an optional argument to limit the overall amount of time that it waits, if triggers never stopped for longer than the interval.throttle_for
ensures that the rule runs no more often than the given duration. It does not require a minimum interval between triggers. When triggers happen frequently, throttle_for
simply doesn't run the rules until the given duration had elapsed, regardless of how long ago was the previous trigger.only_every
it's similar to throttle_for except it executes on the leading edge of the trigger.Whilst not as flexible as the original debounce, these three options seem a lot easier to understand, and it should cover most needs, I would think.
thoughts?
I need to understand max_wait and min_idle first. how does min_idle factor into these three options?
I've changed debounce_for
so it uses a range instead of max_wait. It's more intuitive that way I think.
# @param [Duration,Range] debounce_time The interval between triggers before the rules
# are allowed to run. When specified just as a Duration or an endless range, it
# will continue to wait until the given duration has elapsed since the last trigger
# has passed before executing the rule.
#
# The end of the range when specified, sets the maximum amount of time to wait
# before the rule will execute regardless of the interval between triggers.
The first part of that param describes min_idle. Basically it means your events need to be separated apart by at least min_idle
. It's the "quiesce" time that needs to occur before execution is allowed. So if triggers are too busy and keeps happening at a rate faster than min_idle, the debouncer will not execute the call / rules.
I will try to explain more about this in the timing diagram too
When you have events that fire too fast all the time, with just min_idle, you'll never get an execution because min_idle is never satisfied. max_wait overrides this to ensure that executions will happen at least every max_wait
interval even though min_idle isn't met.
Idea: add rule config Debounce trigger guard on leading or trailing edge
debounce for: duration, trigger_on: :start|:end
With leading edge option, trigger rule when no triggers have happened in the past X duration. Discard triggers otherwise.
With trailing edge option, delay triggers until no more triggers were received within the past X duration and run only the last trigger.