Design Pattern: Hysteresis

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

Problem Statement

Often one will encounter a situation where a device needs to be turned on/off based on a sensor reading but sensors are inherently noisy, sometimes jumping up a little bit and then jumping down a little bit. In the naive approach one sets a threshold with a simple:

    if items["MySensor"] > items["MySetpoint"]:
        events.sendCommand("MyDevice", "ON")
    else:
        events.sendCommand("MyDevice", "OFF")

However, when using this approach. if the sensor happens to be sitting around the setpoint value, it will bounce above and below the setpoint potentially causing the device to rapidly turn on and off.

Concept

Provide a “dead zone”, called hysteresis, in which changes in the sensor reading does not cause a change in the device.

For example, let’s say we have a heater with a setpoint of 70 degrees F. When the temperature is below 70 we want to turn on the heater and when it’s above 70 we want to turn it off.

if items["MyTemp"] < 70:
    events.sendCommand("MyHeater", "ON")
else:
    events.sendCommand("MyHeater", "OFF")

Of course, that will cause flapping. So instead we add a buffer. When the temp gets below 68 degrees F we turn on the heater. When the temp gets above 70 degrees F we turn it off. When the temp is between 68 and 70 degrees we do nothing.

if items["MyTemp"] < 68:
    events.sendCommand("MyHeater", "ON")
elif items["MyTemp"] >= 70:
    events.sendCommand("MyHeater", "OFF")

In Rules DSL

if(MyTemp.state < 68) MyHeater.sendCommand(ON)
else if(MyTemp.state >= 70) MyHeater.sendCommand(OFF)

I’ve written a Python library function (more languages to follow) at https://github.com/rkoshak/openhab-rules-helper. This library function allows you to pass a threshold, a value, and optionally the buffer above and below the threshold to define the buffer. The function returns -1 if the value is below buffer, 1 if it’s above, and 0 if it is within the buffer.

So the python code above might become (replacing the hard coded threshold with an Item’s state):

from community.hysteresis import hysteresis

...

    hyst = hysteresis(items["MySetpoint"], items["MyTemp"], low=2)
    if hyst < 0:
        events.sendCommand("MyHeater", "ON")
    elif hyst > 0:
        events.sendCommand("MyHeater", "OFF")

The biggest advantage of using the function is it works with all the openHAB and Python number types. So, for example, you can pass a Number:Temperature for the setpoint and a PercentType for the value and a Python primitive int for the low buffer and it will work without problem. In the vast majority of cases where this will be used, the Number Items are likely to have units of measurement.

Advantages and Disadvantages

There are all sorts of approaches to keep a device from flapping. This approach works best when the sensor is measuring something that tends to change relatively slowly like temperature or humidity. The wider the hysteresis buffer, the longer it should take for a device to turn on or off as the sensor reading will take more time to reach the lower or upper thresholds.

10 Likes

What else to say but, another great dp.

Eventually use the (new) hysteresis profile instead ?

That works in some cases but not all cases so I intend to mention the profile but keep the DP in place.

2 Likes

Hello,

I am wondering if the DSL rule shall not be modifed as follows:

if(MyTemp.state < 68 && MyHeater.state != ON) MyHeater.sendCommand(ON)
else if(MyTemp.state >= 70 && MyHeater.state != OFF) MyHeater.sendCommand(OFF)

in order to limit trigering commands every time the temperature sensor changes below 68 (ex 67, 67.5, 67,7 etc)?

All things are possible. The main point is that there is a range of values where nothing happens so that the device doesn’t bounce. The DPs by necessity for clarity, only shows the bare minimum to explain the concept usually.

Note, this case might be better handled using the hysteresis profile instead of a rule.

Interesting, is the hysteresis profile a specific binding restricted (ex openweathermap) or can it be used for any channel/binding (ex modbus)?

See Items | openHAB. Profiles are applied to the link between a Channel and an Item.