Is it possible to let a rule wait before it gets executed to wait for a condition to be true without breaking with the general concepts of openHAB?
In my case it is about setting the desired temperatures for a heating. I do this via a sitemap in 0.5 °C steps. So changing from 20 °C to 22 °C degrees triggers 4 times the rule to send the new desired temperature to my Shelly devices. The desired temperature is an item which triggers a “send new temperature value to the shelly” rule.
It is not bad, the Shellys can cope with it. Maybe just a little “ugly”. I am now in this phase where my project works but I am trying to find small things to optimize.
Is there a way how to make sure that within a certain time frime the final selected temperature value gets only sent once to the http end point or would you leave it that way?
For the button-clicky scenerio described, use a timer.
When the user setting changes, start a timer for a few seconds ahead.
Every time the user setting changes, reschedule any existing timer again.
When buttons have stopped being clicked, the timer expires and runs the code to send new setting.
OK, I think I slowly get it.
How would you do it in my particular case? This is how I have set up the rule like this:
rule "Abschalt-/Einschaltwert neu berechnen"
when
Member of gSollTemperaturen received update
then
if(a_RegelVerzoegerung.state == ON) {
return;
}
var v_Heizung_Soll = triggeringItem
var Double Hysterese = (vIt_HZ_Hysterese.state as Number).doubleValue()
var Double Abschaltwert = (v_Heizung_Soll.state as Number).doubleValue() + Hysterese
var Double Einschaltwert = Abschaltwert - 2 * Hysterese
var String roomName = v_Heizung_Soll.name.split("_").get(3)
val v_Heizung_Abschalt = gAbschaltTemperaturen.members.findFirst[ i | i.name.toString.contains(roomName) ] as NumberItem
val v_Heizung_Einschalt = gEinschaltTemperaturen.members.findFirst[ i | i.name.toString.contains(roomName) ] as NumberItem
v_Heizung_Abschalt.sendCommand(Abschaltwert)
v_Heizung_Einschalt.sendCommand(Einschaltwert)
logInfo("Abschalt-/Einschaltwert neu berechnen", "Solltemperatur geändert für " + roomName + ": " + v_Heizung_Soll.state + " °C")
end
It works generically for every thermostat. If not necessary I do not want to create one virtual alarm item for each room. Could it also work with:
createTimer(now.plusSeconds(5), [ |
//here comes the whole part of the rule above after the "then" statement
])
Ye-es, sort of, kind of. But you need to manage your Timer, not create a new timer every key press, that is not helpful.
If you want to use timers, look at examples like window open alarms to get timer usage basics.
More sophisticated use
You probably want something in-between.
Let’s re-phrase previous workflow
When the user setting changes -
(a) cancel any existing timer
(b) start a timer for a few seconds ahead. But you must “remember” the timer in a global variable so that you can do (a)
When the timer expires, do whatever it is you do to send to device.
You’ll need to make a judgement about whether you need this to deal with multiple different group members changing at the same time, that will make it more difficult.
Ok, I think I got it. I create a Switch item with an alarm that expires after, let’s say, 2 seconds. It is then not the temperature item but the alarm that triggers the above posted rule. An additional rule notices a change in one temperature item and starts the timer and if needed resets it if the item changed again in the meantime.
I would use one timer for all items. I won‘t change on the UI within those 2 seconds two room temperatures. I guess
Okay, you can use an extra Item with expire feature if you really want. There’s always more than one way to do things.
The trick then will be remembering which temperature device you wanted to update.
Maybe use a String type Item with expire=3s, state=“UPLOAD”
When UI makes a change to group member, update the shared timing Item with the real triggering Item name string.
When timing Item changes to “UPLOAD”, run the upload code based on previousState for the orginal Item identity.
In jruby, your rule could be translated like this. I tried to keep the same logic/convention as the original script to ease understanding. Note that the key to this, is simply adding for: 2.seconds in the trigger. The jruby library will automatically wait until there are no more changes to the member of gSollTemperaturen for 2 seconds before firing the rule code.
If the same member of gSollTemperaturen keeps changing quickly in less than 2 seconds, the rule won’t fire. It has the built in logic that handles the creation of timer, resetting it whenever changes are detected, so you don’t have to write the lengthy timer logic handling. A different timer is created for each member item, so they each will be tracked independently as one would expect.
It has this feature because this is a very very common scenario, not worth repeatedly writing the same boilerplate code over and over.
Note: you can have RulesDSL and jruby, and any other automation languages running side by side, and have 5 rules in rulesdsl, 2 rules in jruby, 3 rules in js, etc. After someone learns the jruby rules, I don’t see how it’s not tempting to convert everything into jruby. I started with rulesdsl → jython → jruby.