Design Pattern: Debounce

Tags: #<Tag:0x00007f616f5d1e70>

Please see Design Pattern: What is a Design Pattern and How Do I Use Them for a desciption of DPs.

Purpose

Debounce, sometimes called anti-flapping, is a process where either updates to an Item are ignored after a certain initial state is received, or more commonly, waiting for an Item to stay in a given state before accepting the new state. This is useful for situations like:

  • a sensor may be faulty and occasionally flap back and forth rapidly (obviously you should fix the sensor but this can be a temporary fix)
  • there is a period of rapid changes and one should wait until the changes stop before processing the most recent change
  • cases like presence detection to avoid sending the house to away mode when only away for a very short period of time such as picking up the mail.

Concept

There are two Items, one linked to the sensor that needs to be debounced and another proxy Item. Only when the sensor Item has remained in it’s given state for long enough is that state transferred to the proxy Item. Note, that the sensor Item can be a Group.

Examples

Simple Example

We will be debouncing the Switch Person1PresenceSensor so that Person1Presence is not considered away until they have been away for more than 2 minutes and not considered present until they’ve been present for more than 2 minutes. When transferring the state to Person1Presence, use an update.

Python

There is a debounce library script available at https://github.com/rkoshak/openhab-rules-tools that can be downloaded and installed. To debounce the above Item all that is required is adding metadata to the sensor Item.

Switch Person1PresenceSensor { debounce="Person1Presence"[timeout="2m"] } // Sensor for Person 1's presence
Switch Person1Presence           // Proxy for Person1

The library has optional parameters where you can only debounce certain states and send a command to the proxy instead of an update. A more generic example for something like a button:

Switch ButtonSensor { debounce="Button"[timeout="0.25s" command="True"] }
Switch Button

With the above Items, the ButtonSensor must remain in a given state for 250 msec before the state is commanded to Button.

Rules DSL

var Timer timer = null

rule "Debounce Person1"
when
    Item Person1PresenceSensor changed
then
    timer?.cancel

    timer = createTimer(now.plusMinutes(2), [ |
        if(Person1Presence.state != Person1PresenceSensor.state)
            Person1Presence.postUpdate(Person1PresenceSensor.state)
        timer = null
    ])
end

Theory of Operation

When Person1PresenceSensor changes state, cancel any existing Timer and then set a Timer for two minutes. When Person1PresenceSensor has remained in the same state for two minutes, update Person1Presence with the current state of Person1PresenceSensor.

Complicated Example

This will be an implementation Generic Presence Detection where we have multiple people and multiple sensors per person. If any one sensor detects a person as present that person is considered immediately present. If all sensors for a person are OFF for two minutes or more, that person is considered away. When all people are away, the main presence switch is turned OFF with a command.

Items

Group:Switch:OR(ON, OFF) Present

Switch Person1_Present (PresenceSensors)
Group:Switch:OR(ON, OFF) Person1_PresenceSensors
Switch Person1_PresenceSensor1
Switch Person1_PresenceSensor2

Switch Person2_Present (PresenceSensors)
Group:Switch:OR(ON, OFF) Person2_PresenceSensors
Switch Person2_PresenceSensor1
Switch Person2_PresenceSensor2

Python

In this use case, we are only debouncing the OFF command by two minutes with a command. With the above mentioned debounce library, the following changes are required to the Items.

Group:Switch:OR(ON, OFF) Person1_PresenceSensors { debounce="Person1_Present"[timeout="2m", states="OFF", command="True"] }

Group:Switch:OR(ON, OFF) Person2_PresenceSensors { debounce="Person2_Present"[timeout="2m", states="OFF", command="True"] }

Rules DSL

import java.util.Map
import org.eclipse.smarthome.model.script.ScriptServiceUtil

val Map<String, Timer> timers = newHashMap

rule "Presence detection debounce"
when
    Item Person1PresenceSensors changed from OFF to ON or
    Item Person1PresenceSensors changed from ON to OFF or
    Item Person2PresenceSensors changed from OFF to ON or
    Item Person2PresenceSensors changed from ON to OFF
then

    // Cancel the timer if it exists
    timers.get(triggeringItem.name)?.cancel
    
    val delay = if(triggeringItem.state == ON) 0 else 2 // if ON update proxy immediately
    timers.put(triggeringItem.name, createTimer(now.plusMinutes(delay), [ |
        // get the proxy Item
        val proxyName = triggeringItem.name.split("_").get(0) + "_Present"
        val proxyItem = ScriptServiceUtil.getItemRegistry.getItem(proxyName)

        // Command if different
        if(proxyItem.state != triggeringItem.state) proxyItem.sendCommand(triggeringItem.state)

        // clear the timer
        timers.put(triggeringItem.name, null)
    ])
end

Theory of Operation

When all of the sensors for a person go to OFF, the Person1PresenceSensors Group will go to OFF through the Group aggregation function. Any existing Timer is cancelled and a new one is created which will go off in two minutes. After two minutes without changing from OFF, the OFF command is commanded to the Proxy Item which is determined using Design Pattern: Associated Items if it’s different from the Proxy Item’s current state.

When an ON command is received, the Timer for this Item is cancelled, if it exists, and a Timer is created to execute immediately which will command the Proxy Item to ON if it isn’t ON already.

Advantages and Disadvantages

When using the Python library, there is no code to write at all. All that is required is adding some Item metadata to the sensor Item and adding a proxy Item. The approach over all provides a generic way to delay processing of changes until it’s know n to be a good state in a general way.

The major disadvantage with the Rules DSL example is that it requires inserting the code into your specific use case (e.g. see Generic Presence Detection).

Related Design Patterns

Design Pattern How it’s used
Design Pattern: Associated Items Used in the Rules DSL example to convert the sensor Item’s name to it’s proxy name
Design Pattern: Motion Sensor Timer A similar but subtly different approach that is more suitable to cases where updates drive the command to the proxy Item instead of changes.
4 Likes