Rule with multiple timer

Hello,
I need to create a rule that when a specific item receives a specific state, executes the rule by turning off various lights at 5 seconds one after the other.
I tried turning off the first one, then creating a 5 second timer and turning off the second and it works.
If I try to create another 5 second timer and turn off the third light it doesn’t work.
How can I use multiple consecutive timers in a rule?
Thanks.

Can’t see your rule. Guessing, you want the second timer to be ten seconds. (createTimer does not stop and wait, it schedules a future action)

You can use as many timers as you want :slight_smile: but you don’t need to for this job.
Instead just use one Timer and reschedule the timer. in the timer, count up a global var, then decide dependend on the value which light to turn off.

I write below an example that I would like to try but I don’t know if it will work, but I have 6 lights to turn off 5 seconds between one and the next.

rule “Switch Off Light 5s”

when
Item SwitchAllL changed to OFF
then
Light1.sendCommand(OFF)
t1 = createTimer(now.plusSeconds(5), [|
Light2.sendCommand(OFF)
])
t2 = createTimer(now.plusSeconds(5), [|
Light3.sendCommand(OFF)
])
end

How can I create the correctly rule?

Thanks.

As before

As @rossko57 wrote before, t1 and t2 are created at the same time (well, maybe some milliseconds different). you need to define the timer as global.

For your issue, I see 3 different ways:

// define global vars on top of file
var Timer t1 = null
var Timer t2 = null
var Timer t3 = null
var Timer t4 = null
var Timer t5 = null

rule "switch off 5s"                          // will work, but bumb
when
    Item SwitchAllL received command OFF
then
    Light1.sendCommand(OFF)
    t1 = createTimer(now.plusSeconds(5), [|
        Light2.sendCommand(OFF)
    ])
    t2 = createTimer(now.plusSeconds(10), [|
        Light3.sendCommand(OFF)
    ])
    t3 = createTimer(now.plusSeconds(15), [|
        Light4.sendCommand(OFF)
    ])
    t4 = createTimer(now.plusSeconds(20), [|
        Light5.sendCommand(OFF)
    ])
    t5 = createTimer(now.plusSeconds(25), [|
        Light6.sendCommand(OFF)
    ])
end

A more elegant solution is to use only one timer and one counter:

// define global vars on top of file
var Timer tAutoOff = null
var int iAutoOff = 0

rule "switch off 5s"                          // smarter
when
    Item SwitchAllL received command OFF
then
    if(tAutoOff !== null)                     // timer already scheduled, so leave rule
        return;
    iAutoOff = 0                              // initialize counter
    tAutoOff = createTimer(now, [|
        iAutoOff ++
        switch(iAutoOff) {
            case  1 : Light1.sendCommand(OFF)
            case  2 : Light2.sendCommand(OFF)
            case  3 : Light3.sendCommand(OFF)
            case  4 : Light4.sendCommand(OFF)
            case  5 : Light5.sendCommand(OFF)
            case  6 : Light6.sendCommand(OFF)
            default : {
                tAutoOff = null
                return;
            }
        }
        tAutoOff.reschedule(now.plusSeconds(5))
    ])
end

But it’s more efficient to group all Items which should be turned OFF and use the Group Item. You don’t need to know how much Items are in the group:

// define global vars on top of file
var Timer tAutoOff = null
var int iAutoOff = 0

// for this rule all Light Items have to have a _ before the Number, e.g. Light_1 or Light_15_autoOff
// and all lights have to be members of Group Item gLights

rule "switch off 5s"                          // smartest
when
    Item SwitchAllL received command OFF
then
    if(tAutoOff !== null)                           // timer already scheduled, so leave rule
        return;
    iAutoOff = 0                                    // initialize counter
    tAutoOff = createTimer(now, [|                  // start timer immediately
        iAutoOff ++                                 // count up
        var currItem = gLights.members.filter[i|Integer::parseInt(i.name.split("_").get(1)) == iAutoOff] // get a group of Items
        if(currItem.size == 0) {                    // if no Item in Group
            tAutoOff = null                         // delete Timer
        } else {                                    // otherwise
            currItem.head.sendCommand(OFF)          // send a command to the first (and only) item
            tAutoOff.reschedule(now.plusSeconds(5)) // and restart the timer
        }
   ])
end

Please be aware that the numbering must be a row from 1 to n with step size 1 and no gap.

But you even don’t need the Number in the name, though it helps for the order, but you could use alphabetical order instead (or numbering but with fixed length, so if using up to 100 lights you have to use 00,01, 02, 03 … 97,98,99):

// define global vars on top of file
var Timer tAutoOff = null
var int iAutoOff = 0

// for this rule all Light Items have to be members of Group Item gLights

rule "switch off 5s"                          // even smarter
when
    Item SwitchAllL received command OFF
then
    if(tAutoOff !== null)                                          // timer already scheduled, so leave rule
        return;
    iAutoOff = 0                                                   // initialize counter
    tAutoOff = createTimer(now, [|
        var currItem = gLights.members.sortBy[name].get(iAutoOff)  // get the item (not a group of Items!)
        currItem.sendCommand(OFF)                                  // and send OFF
        iAutoOff ++                                                // count up
        if(iAutoOff >= gLights.members.size){                      // if last item
            tAutoOff = null                                        // reset timer var
            return;                                                // abd keave timer
		}
        tAutoOff.reschedule(now.plusSeconds(5))
   ])
end

So, there are many different ways to solve the problem, and some are smarter than others :wink:

Hello, thank you very much.
I test this solution:

But in events log I have this error:

missing EOF at ‘var’

How can I fix this error?
Thanks.

I define the global vars in the top of the file and everything seems to be working.
It is correct as a solution?

Yes. A DSL rule file must be structured like…

// Imports

// Global variables and constants

// Rule 1

// Rule 2
...

@Udo_Hartmann Thank you for your code and examples.

I am struggling to integrate this with two conditions that have to be met at the same time for a home cinema rule. What am I missing here? I tested it before without the conditions but I thought && is needed for combining both conditions in an IF clause?

 var Timer tAutoOff = null
var int iAutoOff = 0

rule "Heimkino ON"
when
    Item Heimkino received command ON
then
logInfo("myRule", "value is {}", LivingRoomTV.state)
    if(LivingRoomTV.getStateAs(OnOffType) == ON) && (tAutoOff !== null) {                      // timer already scheduled, so leave rule
        return;
    iAutoOff = 0                              // initialize counter
    tAutoOff = createTimer(now, [|
        iAutoOff ++
        switch(iAutoOff) {
            case  1 : AlexaTTS.sendCommand('Heimkino wird gestartet')
            case  2 : AlexaTTS.sendCommand('Schalte Receiver ein') 
            case  3 : DenonInput.sendCommand('SAT/CBL')
            case  4 : AlexaTTS.sendCommand('Schalte zweiten Verstärker ein') 
            case  5 : broadlink_rm3_78_0f_77_5a_c0_78_command.sendCommand("2NDAMP")
            case  6 : AlexaTTS.sendCommand('Schalte Beamer ein') 
            case  7 : broadlink_rm3_78_0f_77_5a_c0_78_command.sendCommand("BEAMON")
            case  8 : AlexaTTS.sendCommand('Herunterfahren der Leinwand') 
            case  8 : Leinwand.sendCommand(ON)
            case  9 : AlexaTTS.sendCommand('Viel Spass beim Film') 
            default : {
                tAutoOff = null
                return;
            }
        }
        tAutoOff.reschedule(now.plusSeconds(1))
    ])
    }
         else
    if(tAutoOff !== null)                     // timer already scheduled, so leave rule
        return;
    iAutoOff = 0                              // initialize counter
    tAutoOff = createTimer(now, [|
        iAutoOff ++
        switch(iAutoOff) {
            case  1 : AlexaTTS.sendCommand('Heimkino wird gestartet')
            case  2 : AlexaTTS.sendCommand('Schalte Receiver ein') 
            case  3 : DenonInput.sendCommand('SAT/CBL')
            case  4 : AlexaTTS.sendCommand('Schalte zweiten Verstärker ein') 
            case  5 : broadlink_rm3_78_0f_77_5a_c0_78_command.sendCommand("2NDAMP")
            case  6 : AlexaTTS.sendCommand('Schalte Beamer ein') 
            case  7 : broadlink_rm3_78_0f_77_5a_c0_78_command.sendCommand("BEAMON")
            case  8 : AlexaTTS.sendCommand('Herunterfahren der Leinwand') 
            case  8 : Leinwand.sendCommand(ON)
            case  9 : AlexaTTS.sendCommand('Viel Spass beim Film') 
            default : {
                tAutoOff = null
                return;
            }
        }
        tAutoOff.reschedule(now.plusSeconds(1))
    ])

So as you can see my rules should check if the LivingRoomTV is on or not and depending if it’s on or not first turn it off if still on or after checking it is off run the same routine.

Also what would be the best timer option if I indeed would need different waits for each action instead of 5secs for all in this case? In some actions I would 3 secs but in other 5 for example.

Help is appreciated :slight_smile:

Cheers,
Henning

I expect your openhab.log complains about this, though it may not be obvious what the problem is exactly.

if() evaluates one condition, in brackets.
if ( X )
A condition can itself be made up of two or more parts.
if ( Z == blah && Y == bleh )
You can of course use brackets to clarify, and often should, but do nest them.
if ( (Z == blah) && (Y == bleh) )

There’s another problem here though -

The part following if() in curly braces { } is the code to be executed if “true”

if ( condition) {
   do this code
}
this code outside { } gets done afterwards 

If your condition above is ‘true’, the code-to-do inside { } starts with return, and you leave your rule immediately.
No matter what happens, you never get to run iAutoOff=0 or any of the rest of the code until the matching closing }
I expect you’ve put a } in the wrong place.

Maybe it should be

if( (LivingRoomTV.getStateAs(OnOffType) == ON) && (tAutoOff !== null) ) {
        return;
    }
iAutoOff = 0
...

I should clarify the earlier examples you’re working from

if(tAutoOff !== null)                                          // timer already scheduled, so leave rule
        return;
    iAutoOff = 0

Here, the author has been "lazy’ and omitted the braces { } altogether.
That’s allowed, the rule will assume that only one line "belongs’ to the if-condition.
Here, if its “true”, we do the return.
If its “not true”, we carry on at iAutoOff=0

Personally I never leave out { }, it doesn’t make it work any better just makes it harder to read.

I’m pretty sure you’re thinking way to complex.

var Timer tAutoOff = null
var int iAutoOff = 0

rule "Heimkino ON"
when
    Item Heimkino received command ON
then
    if(tAutoOff !== null)                       // timer already scheduled, so leave rule
        return;
    logInfo("myRule", "value is {}", LivingRoomTV.state)
    if(LivingRoomTV.State == ON) 
        LivingRoomTV.sendCommand(OFF)
    iAutoOff = 0                              // initialize counter
    tAutoOff = createTimer(now, [|
        iAutoOff ++
        switch(iAutoOff) {
            case  1 : AlexaTTS.sendCommand('Heimkino wird gestartet')
            case  2 : AlexaTTS.sendCommand('Schalte Receiver ein') 
            case  3 : DenonInput.sendCommand('SAT/CBL')
            case  4 : AlexaTTS.sendCommand('Schalte zweiten Verstärker ein') 
            case  5 : broadlink_rm3_78_0f_77_5a_c0_78_command.sendCommand("2NDAMP")
            case  6 : AlexaTTS.sendCommand('Schalte Beamer ein') 
            case  7 : broadlink_rm3_78_0f_77_5a_c0_78_command.sendCommand("BEAMON")
            case  8 : AlexaTTS.sendCommand('Herunterfahren der Leinwand') 
            case  8 : Leinwand.sendCommand(ON)
            case  9 : AlexaTTS.sendCommand('Viel Spass beim Film') 
            default : {
                tAutoOff = null
                return;
            }
        }
        tAutoOff.reschedule(now.plusSeconds(1))
    ])
end

Simply switch tv OFF if ON…

1 Like

Yep, that is way better, thank your for that.

One last bit though which drives me insane.
Case 8 is not executed, the Leinwand.sendCommand(ON) and I have no idea why.

In the corresponding Leinwand.sendCommand(OFF) command everything works though.
The log shows that the command is not even fired, can this be due to the case timers?

What is odd that the same ON command can be performed in the Paper UI for the same Leinwand item. Any thoughts on this? Not really sure how to troubleshoot but since it is working standalone it must have something to do with the case timers right? But case 9 is successfully executed though…Leaves me totally puzzled.

Ideas are welcome :wink:

Cheers,
Henning

Oh, that’s simple, there are two case 8 lines in the code (to be honest, I did not see this issue, too).

Maybe a bit more straight forward:

var Timer tAutoOff = null
var int iAutoOff = 0

rule "Heimkino ON"
when
    Item Heimkino received command ON
then
    if(tAutoOff !== null)                       // timer already scheduled, so leave rule
        return;
    logInfo("myRule", "value is {}", LivingRoomTV.state)
    if(LivingRoomTV.State == ON) 
        LivingRoomTV.sendCommand(OFF)
    iAutoOff = 0                                // initialize counter
    tAutoOff = createTimer(now, [|
        iAutoOff ++
        switch(iAutoOff) {
            case  1 : AlexaTTS.sendCommand('Heimkino wird gestartet')
            case  2 : {
                DenonInput.sendCommand('SAT/CBL')
                AlexaTTS.sendCommand('Schalte Receiver ein') 
            }
            case  3 : {
                broadlink_rm3_78_0f_77_5a_c0_78_command.sendCommand("2NDAMP")
                AlexaTTS.sendCommand('Schalte zweiten Verstärker ein') 
            }
            case  4 : {
                broadlink_rm3_78_0f_77_5a_c0_78_command.sendCommand("BEAMON")
                AlexaTTS.sendCommand('Schalte Beamer ein') 
            }
            case  5 : {
                Leinwand.sendCommand(ON)
                AlexaTTS.sendCommand('Herunterfahren der Leinwand') 
            }
            case  6 : AlexaTTS.sendCommand('Viel Spass beim Film') 
            default : {
                tAutoOff = null
                return;
            }
        }
        tAutoOff.reschedule(now.plusSeconds(1))
    ])
end
1 Like

WOW, you need really to look twice!! My god I am getting old, again thank you @Udo_Hartmann :slight_smile: