Question about how createTimer works


(Cyn) #1

I’ve been researching timers in rules and have a question about how they work. I found this simple code on github

    rule "do something if item state is 0 for more than 10 seconds"
    when
    	Item MyItem changed
    then
    	if(MyItem.state==0) {
    		timer = createTimer(now.plusSeconds(10)) [|
    			// do something! 
    		]
    	} else {
    		if(timer!=null) {
    			timer.cancel
    			timer = null
    		}
    	}
    end

It seems to me that this code does the following: when the state of MyItem changes, if it is 0 then after 10 seconds whatever code is in the “do something” section is executed.

But the description of “do something if item state is 0 for more than 10 seconds” implies to me that when MyItem initially changes state, if it is 0 but changes to 1 before the 10 seconds is up, then the timer function will not execute the code in the “do something” section.

Which is it?

For example, if MyItem happens to be a door contact and I want to check if it stays open for more than 100 seconds do I need to check the state of MyItem again after the timer ends? Do I need the second if(MyItem.state==1) part of the code in the “do something” section?

    rule "do something if item state is 1 for more than 100 seconds"
    when
    	Item MyItem changed
    then
    	if(MyItem.state==1) {
    		timer = createTimer(now.plusSeconds(100)) [|
                        //do something
    			if (MyItem.state==1) {
                             logInfo("MyItem", "MyItem has been open for 100 seconds")
                        }
    		]
    	} else {
    		if(timer!=null) {
    			timer.cancel
    			timer = null
    		}
    	}
    end

(Al) #2

You would not need the second “MyItem.state==1” check because in the event that within your 100s your MyItem changes back to 1, the else statement would be executed.
In the else statement the first check

if(timer!=null) {

will check if the timer is actually running (i.e. it got started by the MyItem.state==1 trigger of the rule. If the timer is running (i.e. the 100s are not up yet), the

    			timer.cancel
    			timer = null

part will then stop the timer, in which case the code

                             logInfo("MyItem", "MyItem has been open for 100 seconds")

will never execute (even without the if statement above it).


(Vincent Regaud) #3

Use that:


var Timer timer = null // AT THE TOP OF THE RULE FILE

rule "do something if item state is 0 for more than 10 seconds"
when
    Item MyItem changed
then
    if (MyItem.state == 0) {
        if (timer === null) {
            timer = createTimer(now.plusSeconds(10), [ |
                if (MyItem.state == 0) { // Item state is still 0 after 10s
                    // do something!
                }
            ])
        } else {
            if(timer!=null) {
                timer.cancel
                timer = null
       }
    }
end

Please note that if the item has changed from 0 to something else and then reverted back to 0 in the 10s the rule will still // do something!


(Rich Koshak) #4

The previous answers are all correct but I want to explain it a little more thoroughly.

This rule triggered whenever there is a change to MyItem.

So let’s say MyItem changes to 0. Then we create a timer that will start executing in 10 seconds to // do something.

Now let’s say it is 3 seconds later and MyItem changes to 1. The Rule starts running and the if(MyItem.state == 0) is false so the else clause runs. This clause cancels the Timer if it exists which means that // do something will never run.

Now let’s assume that MyItem never changes from 0 for the full ten seconds after the Timer is created. In that case // do something happens because MyItem never changed from the 0 state.

So as you can see, the keys to the behavior are:

  • The Rule runs for all changes to MyItem
  • If a Timer exists and MyItem changes to any state other than 0, the Timer gets cancelled

Thus, // do something will only run if MyItem remains in the 0 state for 10 seconds.


(Cyn) #5

Thanks! Now I get it. I was getting stuck thinking the createTimer was working like the sleep function.

What I’m actually trying to accomplish is when a door closes if there is no motion for 5 minutes then turn a light on. I was trying to do it all in one rule but now I see I will use multiple rules and access the timer from different rules.


(Vincent Regaud) #6

Do not access a timer from different rules. You will run into trouble. There is a better way to do this. I am on my phone right now but look for the design pattern for timer in the forum.


(Rich Koshak) #7

I’m not sure I would say that. I can think of lots of use cases where, for example, you may want to set a timer to prevent another Rule from running for a certain amount of time after another Rule ran.

However, I do agree that in this case I don’t see why one would need separate Rules.

@cshop, see Design Pattern: Motion Sensor Timer


(Julian Divett) #8

Hi I am also having some issues with create timer rules = I have followed the Design pattern thread and I now have lots of rules using createTimer. Should I use a separate variable for each timer at the top of the rule file? At the moment I am using createTimer without a variable eg:

rule “Wake Timmy and Dennis Monday to Friday”
when
Time cron "0 40 06 ? * MON-FRI "
then
if (Wakeup.state == ON)
{
logInfo(“WAKEUP.RULES”,“wakeup state is on so Dennis/Timmy alarm”)
sendCommand(Sonos_Shuffle, ON)
sendCommand(Sonos_Favorite,“Alarm”)
sendCommand(Sonos_Volume,15)
sendCommand(timmy_music,ON)
createTimer(now.plusSeconds(5), [ |
sendCommand(dennis_music,ON)
sendCommand(Lt_Dennis_Mezz,ON)
sendCommand(Lt_Timmy_Walls,ON)
])
}
else {
logInfo(“WAKEUP.RULES”,“wakeup state is off”)
}
end

I am having issues with rules not always firing (for example this one did not run this morning) and I can’t figure out why (even with logging).


(Vincent Regaud) #9

Please use the code fences when posting code.

There is a stray space at the end of your cron string. That may be the reason.
This is your rule tidied up a bit.
I have used the sendCommand method instead of the action.
There is no issue with your use of the timer.

rule "Wake Timmy and Dennis Monday to Friday"
when
    Time cron "0 40 06 ? * MON-FRI"
then
    if (Wakeup.state == ON) {
        logInfo("WAKEUP.RULES", "wakeup state is on so Dennis/Timmy alarm")
        Sonos_Shuffle.sendCommand(ON)
        Sonos_Favorite.sendCommand("Alarm")
        Sonos_Volume.sendCommand(15)
        timmy_music.sendCommand(ON)
        createTimer(now.plusSeconds(5), [ |
            dennis_music.sendCommand(ON)
            Lt_Dennis_Mezz.sendCommand(ON)
            Lt_Timmy_Walls.sendCommand(ON)
        ])
    } else {
        logInfo("WAKEUP.RULES", "wakeup state is off")
    }
end

(Julian Divett) #10

Thanks - sorry pressed the wrong markup button, getting old need some glasses!

Well done for spotting the space.

So I can have many createTimers running concurrently in different rules without problem and without using variables? What about having more createTimers within a rule so that I don’t send too many commands at once -
eg:

    Sonos_Shuffle.sendCommand(ON)
    createTimer(now.plusSeconds(5), [ |
    Sonos_Favorite.sendCommand("Alarm")
    Sonos_Volume.sendCommand(5)
    ])
    timmy_music.sendCommand(ON)
    createTimer(now.plusSeconds(15), [ |
    dennis_music.sendCommand(ON)
    Lt_Dennis_Mezz.sendCommand(ON)
    Lt_Timmy_Walls.sendCommand(ON)
    ])

I’ve been using sendcommand(item,action) all the time and I now understand the difference! Will change them all now!


(Vincent Regaud) #11

That’s fine