Radiators rule for nested groups

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