Radiators rule for nested groups

OH4.0.4
Raspi 4+

I try to write a rule whis shall open fully all Radiators in my house when the switch is ON. I need to vent them at winter time.

  1. I created an item switch to triger a rule if it is ON.
  2. All my Radiatoras are in one gThermostats group. Inside the group I have radiators with different items (i.e Mode, Temperature, Setpoint itp.) → nestet Group
  3. I do not know how to set all mode channels to 1 (Heat) and all setpointchannels to 26

It does not make sense to do this for each radiator. Does someone have an example of such rule?
Since I am not an effecitve programmer I ahve difficulties to write this rule (textual version)

Put all the mode Items into a separate Group:Number. Then sendCommand(1) to that Group and all members of the Group will be commanded with 1.

Put all the setpoinchannels Items into another separate Group:Number. Then sendCommand(26) to that Group and all the setpointchannels Items will be commanded with 26.

Any other approach is going to be much more complicated. With this approach we need only one rule with two lines, one to command the mode Group and one to command the setpointchannels Group.

Understood. I was thinking there is possibility to trigger item from the N+2 Group.
Let me follow your intruction. I shall come back having rule created.

this is my rule

rule "Venting"

when
    Item HeatON changed
then

    if (HeatON.state == ON){
        gThermostatModes.sendCommand (1)
        gThermostatFull.sendCommand (26)
        gThermostatModes.members.forEach [i | logInfo("Venting", i.name + " = " + i.state.toString) ]

    }

    if (HeatON.state == OFF){
        gThermostatFull.sendCommand (21)
        gThermostatModes.sendCommand (0)
        gThermostatModes.members.forEach [i | logInfo("Venting", i.name + " = " + i.state.toString) ]
    }

end

It works but I would like to insert a small delay between sending the sommands. Just to give the time to to the radiator to update values.

and this is the rule with sleep. I am not sure this one is a state of the art solution for delays in OpenHab

rule "Venting"

when
    Item HeatON changed
then

    if (HeatON.state == ON){
        gThermostatModes.sendCommand (1)
        Thread::sleep(5000)
        gThermostatFull.sendCommand (26)
        gThermostatModes.members.forEach [i | logInfo("Venting", i.name + " = " + i.state.toString) ]

    }

    if (HeatON.state == OFF){
        gThermostatFull.sendCommand (21)
        Thread::sleep(5000)
        gThermostatModes.sendCommand (0)
        gThermostatModes.members.forEach [i | logInfo("Venting", i.name + " = " + i.state.toString) ]
    }

end

Assuming this rule doesn’t get triggered more than once every five seconds there should be no problems using a sleep. However, if ti does get triggered more often the subsequent triggers will get queued up and worked off in order which can often lead to weird behaviors and/or starving out the rule. To avoid that you’d want to use a Timer and when the rule triggers and the Timer is already created you can reschedule it. Only when the timer goes away will an ON/OFF command be processed by the rule. That creates a kind of timeout on the HeatOn switch so it doesn’t flip back and forth if someone spams the switch.

Another thing to watch out for is that HeatOn is not guaranteed to be in the same state as it was when the rule triggered. This may be desirable but usually it is not. You’ll want to use the implicit variable newState instead of HeatOn.state to see the state change that actually triggered the rule regardless of any changes that may have happened to HeatON in the meantime.

You could reduce some of this duplicated code using Design Pattern: How to Structure a Rule and it just might be worth while if you introduced a Timer. As written now it’s probably not worth it though.

rule "Venting"
when
    Item HeatON changed
then
    // 1. Decide if we need to run
    if(private.cache.get('timer') != null) {
        logWarn('HeatOn'', 'Already venting, wait for the operation to complete before running again.')
        return;
    }

    // 2. Calculate what to do
    val firstCommand = if(newState == ON) then [ | gThermostatModes.sendCommand(1) ] else [ | gThermostatFull.sendCommand(21) ]
    val secondCommand = if(newState == ON) then [ | gThermostatFull.sendCommand(26) ] else  [ | gThermostatModes.sendCommand(0) ]

    // 3. Do it
    firstCommand.apply();
    private.cache.put('timer', createTimer(now.plusSeconds(5), [ | 
        secondCommand.apply()
        // Note, it's likely that the command on the line above has not yet been processed by all the Items so
        // some of these log statements might still show the old state
        gThermostatModes.members.forEach [i | logInfo("Venting", i.name + " = " + i.state.toString) ]
        private.cache.put('timer', null)
    ])
end