Class: OpenHAB::DSL::Debouncer

Inherits:
Object
  • Object
show all
Defined in:
lib/openhab/dsl/debouncer.rb

Overview

Provides the feature for debouncing calls to a given block.

The debouncer can filter events and only allow the events on the leading or trailing edge of the given interval. Its behavior can be customized through settings passed to its constructor.

The following timing diagram illustrates the incoming triggers and the actual executions using various options.

                             1    1    2    2    3    3    4    4
                   0    5    0    5    0    5    0    5    0    5
Triggers        : 'X.X...X...X..XX.X.X......XXXXXXXXXXX....X.....'
leading: false
for:5           : '|....X|....X |....X      |....X|....X   |....X'
leading: true
for:5           : 'X.....X......X....X......X....X....X....X.....'

more options, leading: false
Triggers        : 'X.X...X...X..XX.X.X......XXXXXXXXXXX....X.....'
for:5 idle:3    : '|....X|......X|......X...|............X.|....X'
for:5 idle:5    : '|......................X.|..............X.....'
for:5..5 idle:X : '|....X|....X.|....X......|....X|....X...|....X'
for:5..6 idle:5 : '|.....X...|.....X.|....X.|.....X|.....X.|....X'
for:5..7 idle:5 : '|......X..|......X|....X.|......X|......X.....'
for:5..8 idle:5 : '|.......X.|.......X......|.......X|.....X.....'
for:5..8 idle:3 : '|....X|......X|......X...|.......X|....X|....X'
for:5..8 idle:2 : '|....X|.....X|......X....|.......X|....X|....X'

Notes:

  • | indicates the start of the debounce period
  • With for: 5..5 (a range with begin=end), the idle_time argument is irrelevant and be unset/set to any value as it will not alter the debouncer's behavior.
  • Without an idle_time, the range end in for: X..Y is irrelevant. It is equivalent to for: X without the end of the range.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(for:, leading: false, idle_time: nil) ⇒ void

Constructor to create a debouncer object.

The constructor sets the options and behaviour of the debouncer when the #call method is called.

Terminology:

  • calls are invocations of the #call method, i.e. the events that need to be throttled / debounced.
  • executions are the actual code executions of the given block. Executions usually occur less frequently than the call to the debounce method.

Parameters:

  • for (Duration, Range, nil)

    The minimum and optional maximum execution interval.

    • Duration: The minimum interval between executions. The debouncer will not execute the given block more often than this.
    • Range: A range of Durations for the minimum to maximum interval between executions. The range end defines the maximum duration from the initial trigger, at which the debouncer will execute the block, even when an idle_time argument was given and calls continue to occur at an interval less than idle_time.
    • nil: When nil, no debouncing is performed, all the other parameters are ignored, and every call will result in immediate execution of the given block.
  • leading (true, false) (defaults to: false)
    • true: Perform leading edge "debouncing". Execute the first call then ignore subsequent calls that occur within the debounce period.
    • false: Perform trailing edge debouncing. Execute the last call at the end of the debounce period and ignore all the calls leading up to it.
  • idle_time (Duration, nil) (defaults to: nil)

    The minimum idle time between calls to stop debouncing. The debouncer will continue to hold until the interval between two calls is longer than the idle time or until the maximum interval between executions, when specified, is reached.



84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/openhab/dsl/debouncer.rb', line 84

def initialize(for:, leading: false, idle_time: nil)
  @interval = binding.local_variable_get(:for)
  return unless @interval

  @interval = (@interval..) unless @interval.is_a?(Range)

  @leading = leading
  @idle_time = idle_time
  @mutex = Mutex.new
  @block = nil
  @timer = nil
  reset
end

Instance Attribute Details

#idle_timeDuration? (readonly)

Returns The minimum idle time to stop debouncing.

Returns:

  • (Duration, nil)

    The minimum idle time to stop debouncing.



48
49
50
# File 'lib/openhab/dsl/debouncer.rb', line 48

def idle_time
  @idle_time
end

#intervalRange? (readonly)

Returns The range of accepted debounce period, or nil if debouncing is disabled.

Returns:

  • (Range, nil)

    The range of accepted debounce period, or nil if debouncing is disabled.



45
46
47
# File 'lib/openhab/dsl/debouncer.rb', line 45

def interval
  @interval
end

Instance Method Details

#call { ... } ⇒ void

This method returns an undefined value.

Debounces calls to the given block.

This method is meant to be called repeatedly with the same given block. However, if no block is given, it will call and debounce the previously given block

Examples:

Basic trailing edge debouncing

debouncer = Debouncer.new(for: 1.minute)
(1..100).each do
  debouncer.call { logger.info "I won't log more often than once a minute" }
  sleep 20 # call the debouncer every 20 seconds
end

Call the previous debounced block

debouncer = Debouncer.new(for: 1.minute)
debouncer.call { logger.info "Hello. It is #{Time.now}" } # First call to debounce

after(20.seconds) do |timer|
  debouncer.call # Call the original block above
  timer.reschedule unless timer.cancelled?
end

Yields:

  • Block to be debounced

Raises:

  • (ArgumentError)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/openhab/dsl/debouncer.rb', line 124

def call(&block)
  @block = block if block
  raise ArgumentError, "No block has been provided" unless @block

  return call! unless @interval # passthrough mode, no debouncing when @interval is nil

  now = ZonedDateTime.now
  if leading?
    leading_edge_debounce(now)
  else
    trailing_edge_debounce(now)
  end
  @mutex.synchronize { @last_timestamp = now }
end

#call!Object

Executes the latest block passed to the OpenHAB::DSL#debounce call regardless of any debounce settings.

Returns:

  • (Object)

    The return value of the block



144
145
146
# File 'lib/openhab/dsl/debouncer.rb', line 144

def call!
  @block.call
end

#flushBoolean

Immediately executes any outstanding event of a trailing edge debounce. The next call will start a new period.

It has no effect on a leading edge debouncer - use #reset instead.

Returns:

  • (Boolean)

    True if an existing debounce timer was rescheduled to run immediately. False if there were no outstanding executions.



173
174
175
176
177
178
179
180
# File 'lib/openhab/dsl/debouncer.rb', line 173

def flush
  @mutex.synchronize do
    if @timer&.cancel
      call!
      true
    end
  end
end

#leading?true, false

Returns true to indicate that this is a leading edge debouncer.

Returns:

  • (true, false)

    True if this object was created to be a leading edge debouncer. False otherwise.



187
188
189
# File 'lib/openhab/dsl/debouncer.rb', line 187

def leading?
  @leading
end

#resetBoolean

Resets the debounce period and cancels any outstanding block executions of a trailing edge debouncer.

  • A leading edge debouncer will execute its block on the next call and start a new debounce period.
  • A trailing edge debouncer will reset its debounce timer and the next call will become the start of a new debounce period.

Returns:

  • (Boolean)

    True if a pending execution was cancelled.



157
158
159
160
161
162
# File 'lib/openhab/dsl/debouncer.rb', line 157

def reset
  @mutex.synchronize do
    @last_timestamp = @leading_timestamp = @interval.begin.ago - 1.second if leading?
    @timer&.cancel
  end
end