Best way to reschedule a timer

When I need to reset a timer to start again due to a repeat trigger I do with something similar to the rule below. Is there a better way than cancelling the timer each time and creating it again?

rule "timed light"
when Item MySensor changed then {
//turn something On
if (MyTimer !==null) {
  MyTimer.cancel()
  MyTimer = null }
  MyTimer = createTimer(now.plusSeconds(30)[|
    //turn something Off 
    ]
 }
end

I do it like this:

rule "timed light"
when 
    Item MySensor changed
then
    if(MyTimer === null) 
    {
        MyTimer = createTimer(now.plusSeconds(30)[|
            //Do Stuff
            MyTimer = null
        ]
    }
    else
    {
        MyTimer.reschedule(now.plusSeconds(30)
    }
end

This method works well.

1 Like

Both of you forgot closing brackets for createTimer (and reschedule)

MyTimer = createTimer(now.plusSeconds(30)) [| ... ] 

MyTimer.reschedule(now.plusSeconds(30))

Just in case, someone stumbled onto this posting :wink:

7 Likes

Thanks for the replies - I typed the example out manually so it’s very possible that the syntax was wrong.
I’ll try the reschedule function :slight_smile:

Sorry for bumping in here.

What are this ‘===’ for?
if(MyTimer === null)

Is it the same as ‘==’?
if(MyTimer == null)

Or are there a differnet meaning? I read it in the xtent docu but do not understand fully.

It changes when you move to the latest version, where there is a possibility of the result being “null” != becomes !== and == becomes ===
I’m sure those that know about programming will be along shortly to explain why.
There is an error in the logs about it

This was explained by @rlkoshak here: Trrying to turn a device off after a certain amount of time

In short: If comparing to null (and only to null, not NULL !) === or !== is the better (and in openHAB2.2.0-1 requested) option.

I’ve switched almost entirely to using the EXPIRE binding to deal with what were previously timers issues.

Works well, gets you separation between your core automation logic and the mechanics of maintaining timers “in-line”.

Mechanics —

  1. Define a couple of timers as a virtual items like this, like this----
Switch SW_u_01_LIGHT_Kitchen_TIMER_5m_OFF { expire="5m,command=OFF" }
Switch SW_u_01_LIGHT_Kitchen_TIMER_10s_OFF { expire="10s,command=OFF" }
  1. In your rules file, on occurrence of some event, use sendCommand to turn this virtual switch ON, like this…
rule "Kitchen motion sensor fires"
    when
        Item MO_u_01_MOTION_Kitchen changed to OPEN or
        Item MO_u_01_MOTION_Kitchen changed to ON 
    then 
        SW_u_01_LIGHT_Kitchen_PXY_MOTION.sendCommand(ON)
        SW_u_01_LIGHT_Kitchen_TIMER_5m_OFF.sendCommand(ON)
end

This rule’s THEN clause first does a sendCommand to the entry point of the proxy-chain I have defined (long story, but gist is tracing back the source of a command for a switch to a particular type of trigger), and then turns the virtual switch for the 5minute timer (ON) in the second part of the THEN clause.

If the virtual switch for the 5m_OFF is triggered by the EXPIRE binding (ie 5 minutes pass without a countermanding instruction, this rule cascade gets hit----

rule "SW_u_01_LIGHT_Kitchen_TIMER_5m_OFF changed to  OFF"
    when
        Item SW_u_01_LIGHT_Kitchen_TIMER_5m_OFF changed to OFF
    then
		logDebug("APARTMENT","META_TIMER_3: Timer 5m  OFF received command OFF")
		logDebug("APARTMENT","META_TIMER_3: MO_u_01_MOTION_Kitchen.state is: " + MO_u_01_MOTION_Kitchen.state)
        logInfo("APARTMENT","META_TIMER_3: SW_u_01_LIGHT_Kitchen_TIMER_5m_OFF is OFF: Motion detector OFF,  sending ON to TIMER_10s_OFF")
	    SW_u_01_LIGHT_Kitchen_TIMER_10s_OFF.sendCommand(ON)
end

//.....

rule "SW_u_01_LIGHT_Kitchen_TIMER_10s_OFF changed to  OFF"
    when
        Item SW_u_01_LIGHT_Kitchen_TIMER_10s_OFF changed to OFF
    then
		if (MO_u_01_MOTION_Kitchen.state == OPEN) {
			SW_u_01_LIGHT_Kitchen_TIMER_10s_OFF.sendCommand(ON)
		} else {
	           SW_u_01_LIGHT_Kitchen_PXY_MOTION.sendCommand(OFF)
		}
end

So, in essence, if the 5m_OFF timer expires, the rules go into a tight loop checking to see if the motion detector is OPEN or not — IF OPEN it resets the the 10s_OFF “tight loop”.

This illustrates both one of the drawbacks of EXPIRE binding and how to deal with it. You cannot change the time length of an item with an EXPIRE binding. You can start it (with defined time length), stop it (with a sendCommand(OFF), or reset it to its defined length by a sendCommand(ON). You cannot have variable-length timers using EXPIRE binding, but I have found the overhead of the multiple virtual timer definitions to be well-worth the savings in mechanics of timer create, reset, cancel, cleanup operations inline in rules.

Yes, expire binding is a good thing, when using static timers. It’s a shame there is no way to change the expiration duration during runtime.

My experiences with the Expire binding are mixed. While the code gets cleaner I experience that the reschedule function does NOT work reliable. Sometimes it works but often you stand in the dark although the motion sensor retriggered.

1 Like

I’ve gone with a slightly modified version of this solution. I have 3 sensors, 3 lights and 3 rules, 2 of which interact, so using the expire binding wasn’t the best solution for me.
The simplest rule is below for reference.

rule "Garage Stairs Light"
when Item BottomStairsPIR changed then	{
GarageStairLights.sendCommand(ON)

if	(GarageStairLightsTimer === null)	{
	GarageStairLightsTimer = createTimer(now.plusSeconds(20)) [|
		sendCommand (GarageStairLights, OFF)
		GarageStairLightsTimer = null
		]
	}	else	{ GarageStairLightsTimer.reschedule(now.plusSeconds(20))	}
}
end

The PIR changes state a few times as I walk through its zone, so the light has a short timer. Both the light switch and the PIR are operated via MQTT, so I allow multiple triggers as occasionally one of the MQTT messages gets lost.

Thanks everyone for your help!

I just ran into your post. One question:

Do I have to define GarageStairLightsTimer as a variable somewhere? I ask because I receive the error:

[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Garage Stairs Light': The name 'GarageStairLightsTimer' cannot be resolved to an item or type; line 6, column 5, length 22

Best regards, Max

You need this at the top of the file before the rules start

 var Timer GarageStairLightsTimer = null

Thank you, @kevin. It is working now.

For me it only works this way:
MyTimer = createTimer(now.plusSeconds(30) [| … ])
otherwise I get a script error.

It’s either

myTimer = createTimer(now.plusSeconds(30), [| ... ])

or

myTimer = createTimer(now.plusSeconds(30)) [| ... ]

both ways are correct.