Inform me once a Threshold is Reached
It sounds so simple: “When the temperature reaches 0°C or less, play a sound”. But how to avoid that this rule is triggered again and again? In this article, we discuss multiple approaches and how to solve this problem using OpenHAB 3.
Event-based Triggers vs. Threshold-based Triggers
A classical rule is defined by an event-based trigger or a time-based trigger: When a button is pressed, when the door is being unlocked, when the smoke detector detects smoke, when it’s 8 o’clock, every hour, and so on. These triggers are fired once when the event happens. That’s it. The button is pressed? The trigger is fired! And when we press the button a second time, the trigger is fired again. This is how it should be.
But let’s take a look at the following rule in OpenHAB 3:
In OpenHAB, it’s not possible to define that a rule should fire as soon as a numeric item has a value less or equal to a specific number. So, our rule triggers on each temperature change, but we add a condition, in which we check whether it’s less or equal than 0°C. In the action part of the rule, we defined that push notifications should be sent to the users’ smartphones.
Now let’s say, it’s 2°C. Everything is fine. But now the temperature changes to 0°C, and the notification is sent. So far, so good. But then it further decreases to -1°C, and again, a notification is sent. 0°C — again! 1°C — nothing, ok. 0°C — notification! -1°C — notification! -2°C — notification! Is this really what we want? No.
Before we discuss solutions, here are some more examples of other threshold-based triggers:
- Once the moisture sensor of a plant reaches a level below x, …
- Once the humidity in a room exceeds 60%, …
- Once the temperature of the sauna reaches 70°C, …
- Once the solar plant produces more than 1 kW of power, …
- Once the water level of a cistern is less than 20%, …
- Once the washing machine consumes less than 10 W, … (then it’s finished)
When creating all these rules, we face the same problem: How to avoid that the event is fired again and again, and not just only once.
Approach 0: Just trigger the rule for a small value range (bad solution)
Let’s say, you receive a notification not when the outside temperature is below 0°C but when it is exactly 0°C. Okay, if you have a numeric item with several fractional digits, it’s never exactly 0°C! So, you could fix it by firing the rule when the value is between 0°C and -0.5°C. This time, you will not get notified again when the temperature decreases further. But: (1) You may get notified multiple times when the value range is too large and there are multiple measurements within this range. (2) You may not get notified at all because the value range is too small and between two measurements, the whole range is skipped; see the “Hello?” part in the chart from above. (3) You get notified when the threshold is exceeded from the wrong direction; the “No!” in the chart from above. You want to hear “It’s cold outside” when it’s 0°C and it had a higher temperature before and not a lower.
Approach 1: Store the information that the threshold was already reached
We could create an item in OpenHAB 3 of the type Switch, let’s call it temperature_alarm, and we initialize it with the value OFF. In the rule from above, we add a second condition: temperature_alarm == OFF, and we set a second action: change temperature_alarm to ON. This way, we only send the notification once, and then never again. But “never again” is of course not a perfect solution. We can now manually set the temperature_alarm item state back to OFF to activate our notification again. Or we create a second rule which sets the item state to OFF, for example, when the temperature is more than 5°C, or once per day at 0:00 o’clock. Okay, in the latter approach, it can happen, that you receive a notfication every night when it’s colder than 0°C.
I do not like the approach of creating these helper items. It’s not really an item (like a temperature-sensor value or a lightbulb state), it’s just there to store a meta-information for a rule. Sometimes helper items are the best, or the only way to do it. But in our case, there are alternative ways how to do it.
In JavaScript-based rules in OpenHAB 3, it’s possible to store the information whether the rule is already fired in a rule-related member variable this.something
:
var logger = Java.type('org.slf4j.LoggerFactory')
.getLogger('org.openhab.rule.' + ctx.ruleUID);var temp = itemRegistry.getItem("outside_temperature").getState();if(temp <= 0 && !this.already_notified) {
events.sendCommand("EchoKitchen_Say",
"The outside temperature is "+temp+" degrees");
this.already_notified = true;
}if(temp >= 5 && this.already_notified) {
logger.info("Re-activate outside-temperature notification.");
this.already_notified = false;
}
This way, we do not need to introduce a new item for storing the state. As a little drawback, it’s not possible to inspect the current state of this.already_notified and we cannot manually change it. Furthermore, after a restart of OpenHAB, this variable will have the value undefined.
Mind that we need to remove the condition “outside_temperature <= 0” from the rule. This condition is now checked within the JavaScript code in the rule.
Approach 2: Compare an item’s current state with its previous state
Within OpenHAB rules, event.oldItemState
gives us access to the previous state of the item that triggered the rule.
var temp = itemRegistry.getItem("outside_temperature").getState();if(temp <= 0 && event.oldItemState > 0) {
events.sendCommand("EchoKitchen_Say",
"The outside temperature is "+temp+" degrees");
}
This rule is much simpler and we do not introduce further variables or items. We simply monitor whether the temperature changes from a value greater than 0°C to a value less or equal to 0°C. And this happens only once. Okay, unless it’s like this: 0°C, 1°C, 0°C, 1°C, 0°C, 1°C,… In the first approach, we could avoid notifications in these cases by setting the reset threshold to 5°C or something bigger.
This approach is perfect for monotonously increasing or decreasing item states.
Approach 3: Access a historical state of an item
Approach 2 is very simple and should solve a lot of problems with threshold-based triggers. When you want to customize the range of not-retriggering, approach 1 might be a better choise. But let’s come to one of the examples from above: the washing-machine notification.
A smart power plug with a built-in energy meter measures the current power consumption of a washing machine. We want to create a rule that sends a notification as soon as the washing machine is finished. But how to detect when it’s finished, how to avoid false notifications, and how to notify only once?
The following (fake) chart shows the power consumption of the washing machine:
Problems to consider:
- Standby consumption; the machine consumes 2W even if it is powered off
- Don’t notify when the machine is not running
- Only notify once, when the machine is not running “anymore”
- Don’t notify when the machine makes a short washing pause
The solution here is to use OpenHAB’s persistence service. When an item state is persisted using a service like RRD4J, a historical state of an item can be accessed within rules. The following example gives us the state of an item that it had 10 minutes ago:
var logger = Java.type('org.slf4j.LoggerFactory')
.getLogger(‘org.openhab.rule.’ + ctx.ruleUID);
var ZonedDateTime = Java.type('java.time.ZonedDateTime');
var PersistenceExtensions = Java.type(
'org.openhab.core.persistence.extensions.PersistenceExtensions');var old_value = PersistenceExtensions.historicState(
itemRegistry.getItem("washing_machine_power"),
ZonedDateTime.now().minusMinutes(10)
).state;logger.info("10 Minutes ago: "+old_state);
The class PersistenceExtensions offers many other useful methods like computing the minimum, maximum, or average since a specific point in time:
var max_value = PersistenceExtensions.maximumSince(
itemRegistry.getItem("washing_machine_power"),
ZonedDateTime.now().minusMinutes(10)
).state;
Using this, we could say that our washing machine is finished when the maximum power consumption over the last 10 minutes is less than 10 Watts. Again, we must avoid that the notification is sent more than once. Either with changing an item state “already_notified” to ON, or a variable this.already_notified=true
.
if(max_value < 10 && !this.already_notified) {
events.sendCommand("EchoKitchen_Say",
"The washing machine is finished!");
this.already_notified = true;
}if(itemRegistry.getItem(“washing_machine_power”).getState() > 10) {
this.already_notified = false;
}
This script is executed whenever the state of the washing machine’s power meter changes. The latter part of the script resets the notification when the washing machine is started the next time.
Conclusion
Sending a notification when a threshold is reached sounds easy. But to guarantee an exactly-once behavior, rules should be developed with care. In this article, we presented multiple approaches: (1) Memorizing that we were already notified, (2) only notify in the one moment the threshold was exceeded by comparing the current item state with its previous state, (3) accessing a historic state value or an average or maximum over a specific time period.