Wait for executing a rule

Hi,

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?

Cheers,
Oliver

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.

This requires you using a “Virtual item”. The UI changes the virtual item, and your rule does the timer thingy like @rossko57 described.

Code in jruby is very straight forward as it has a feature to handle exactly this type of scenario. Just one line rule is needed.

require 'openhab'

changed(Virtual_SetPoint, for: 3.seconds) { Real_SetPoint << Virtual_SetPoint }

Or it can be written in a longer more traditional format:

require 'openhab'

rule 'delay setpoint' do
  changed Virtual_SetPoint, for: 3.seconds
  run { Real_SetPoint << Virtual_SetPoint }
end

It won’t execute the “run” block until Virtual_SetPoint remains the same value for 3 seconds.

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
            ])

Does this make sense?

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 :smiley:

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.

1 Like

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.

require 'openhab'

rule "Abschalt-/Einschaltwert neu berechnen" dowhen
  changed gSollTemperaturen.members, for: 2.seconds
  not_if a_RegelVerzoegerung
  triggered do |item|
    v_Heizung_Soll = item
    hysterese = vIt_HZ_Hysterese.to_f
    abschaltwert = v_Heizung_Soll.to_f + hysterese
    einschaltwert = abschaltwert - 2 * hysterese

    room_name = v_Heizung_Soll.name.split('_')[3]
    v_Heizung_Abschalt = gAbschaltTemperature.find { |item| item.name.include? room_name }
    v_Heizung_Einschalt = gEinschaltTemperaturen.find { |item| item.name.include? room_name }

    v_Heizung_Abschalt&.command abschaltwert
    v_Heizung_Einschalt&.command einschaltwert 
    logger.info("Solltemperatur geändert für #{room_name}: #{v_Heizung_Soll} °C")
end

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.

1 Like

That looks really nice and minimal. I will definitely give it a try!