[SOLVED] 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
0 Likes

(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).

0 Likes

(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!

0 Likes

(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.

0 Likes

(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.

0 Likes

(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.

0 Likes

(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

1 Like

(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).

0 Likes

(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
0 Likes

(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!

0 Likes

(Vincent Regaud) #11

That’s fine

1 Like

(Vincent Bevan) #12

Hi I’d be grateful for some advice on the logic of createTimer. I have a rule which takes a speech to text input and then based on the txt containing content in a switch case construct actions are performed. I am trying to use using multiple timers for delays and seem to have the following issue which I don’t understand.

I am running the following environment:

Release = Raspbian GNU/Linux 9 (stretch)
Kernel = Linux 4.14.98-v7+
Platform = Raspberry Pi 3 Model B Rev 1.2
openHAB 2.5.0~M1-1 (Milestone Build)

The following is an extract of a block in the rule which doesn’t work, but in my opinion looks more logical then the one further below which works fine (I ma trying to release timers to avoid increasing memory footprint):

		case txt.contains("bedtime"): {
			lou_onkrec_01_har.sendCommand("PowerOff")
			if (timer === null) {
				timer = createTimer (now.plusSeconds(4)) [|
					Sonoff_3_Switch.sendCommand(OFF)
					lounge_floor_light.sendCommand(OFF)
					hall_floor_light.sendCommand(ON)
				]
			}	
			timer.cancel
			timer = null
			if (timer === null) {
				timer = createTimer (now.plusSeconds(8)) [|
					lounge_ceiling_light.sendCommand(OFF)
					bedroom_ceiling_light.sendCommand(30)
					all_media.sendCommand(OFF)
				]
			}
			timer.cancel
			timer = null
		}

The above rule performs the first command (lou_onkrec_01_har.sendCommand(“PowerOff”)) and then exits the case block.

The next attempt works fine but looks messy and I’m worried that the timers are not released…

case txt.contains("bedtime"): {
			lou_onkrec_01_har.sendCommand("PowerOff")
			timer = createTimer (now.plusSeconds(4)) [|
				Sonoff_3_Switch.sendCommand(OFF)
				lounge_floor_light.sendCommand(OFF)
				hall_floor_light.sendCommand(ON)
				timer = createTimer (now.plusSeconds(8)) [|
					lounge_ceiling_light.sendCommand(OFF)
					bedroom_ceiling_light.sendCommand(30)
					all_media.sendCommand(OFF)
				]
			]
			timer.cancel
		}

Also I receive the following errors in my log file:

019-02-25 23:14:36.958 [ome.event.ItemCommandEvent] - Item 'VoiceCommand' received command bedtime

2019-02-25 23:14:36.974 [vent.ItemStateChangedEvent] - VoiceCommand changed from  to bedtime

==> /var/log/openhab2/openhab.log <==

2019-02-25 23:14:36.990 [INFO ] [se.smarthome.model.script.Voice.Rec:] - VoiceCommand received: voicecommand (type=stringitem, state=bedtime, label=command, category=null, groups=[all])

==> /var/log/openhab2/events.log <==

2019-02-25 23:14:37.008 [ome.event.ItemCommandEvent] - Item 'lou_onkrec_01_har' received command PowerOff

2019-02-25 23:14:37.103 [nt.ItemStatePredictedEvent] - lou_onkrec_01_har predicted to become PowerOff

2019-02-25 23:14:37.109 [vent.ItemStateChangedEvent] - lou_onkrec_01_har changed from UNDEF to PowerOff

==> /var/log/openhab2/openhab.log <==

2019-02-25 23:14:37.114 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'VoiceControl': An error occurred during the script execution: Couldn't invoke 'assignValueTo' for feature JvmVoid:  (eProxyURI: voice.rules#|::0.2.0.2.0.2.1.6.1.0.1::0::/1)

==> /var/log/openhab2/events.log <==

2019-02-25 23:14:37.130 [vent.ItemStateChangedEvent] - lou_onkrec_01_har changed from PowerOff to UNDEF

2019-02-25 23:14:41.014 [ome.event.ItemCommandEvent] - Item 'Sonoff_3_Switch' received command OFF

2019-02-25 23:14:41.026 [ome.event.ItemCommandEvent] - Item 'lounge_floor_light' received command OFF

2019-02-25 23:14:41.043 [ome.event.ItemCommandEvent] - Item 'hall_floor_light' received command ON

==> /var/log/openhab2/openhab.log <==

2019-02-25 23:14:41.040 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.2019-02-25T23:14:41.007+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <null>.timer = <XFeatureCallImplCustom>

} ] threw an unhandled Exception: 

java.lang.IllegalArgumentException: Couldn't invoke 'assignValueTo' for feature JvmVoid:  (eProxyURI: voice.rules#|::0.2.0.2.0.2.1.6.1.0.1.7.6.1.1.0.3::0::/1)

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.assignValueTo(XbaseInterpreter.java:1225) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1213) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:216) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]

	at com.sun.proxy.$Proxy181.apply(Unknown Source) ~[?:?]

	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]

	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [107:org.eclipse.smarthome.core.scheduler:0.11.0.oh250M1]

	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.11.0.oh250M1]

==> /var/log/openhab2/events.log <==

2019-02-25 23:14:42.845 [vent.ItemStateChangedEvent] - lounge_floor_light changed from ON to OFF

2019-02-25 23:14:42.849 [vent.ItemStateChangedEvent] - hall_floor_light changed from OFF to ON

==> /var/log/openhab2/openhab.log <==

2019-02-25 23:14:42.848 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.2019-02-25T23:14:41.007+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <XFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)

  <null>.timer = <XFeatureCallImplCustom>

} ] threw an exception.

org.quartz.SchedulerException: Job threw an unhandled exception.

	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [107:org.eclipse.smarthome.core.scheduler:0.11.0.oh250M1]

	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [107:org.eclipse.smarthome.core.scheduler:0.11.0.oh250M1]

Caused by: java.lang.IllegalArgumentException: Couldn't invoke 'assignValueTo' for feature JvmVoid:  (eProxyURI: voice.rules#|::0.2.0.2.0.2.1.6.1.0.1.7.6.1.1.0.3::0::/1)

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.assignValueTo(XbaseInterpreter.java:1225) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1213) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:216) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:447) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:228) ~[?:?]

	at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:204) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:190) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]

	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]

	at com.sun.proxy.$Proxy181.apply(Unknown Source) ~[?:?]

	at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:49) ~[?:?]

	at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]

	... 1 more

2019-02-25 23:14:49.034 [ome.event.ItemCommandEvent] - Item 'lounge_ceiling_light' received command OFF

2019-02-25 23:14:49.040 [ome.event.ItemCommandEvent] - Item 'bedroom_ceiling_light' received command 30

2019-02-25 23:14:49.045 [ome.event.ItemCommandEvent] - Item 'all_media' received command OFF

2019-02-25 23:14:49.069 [ome.event.ItemCommandEvent] - Item 'bathroom_washmachine_switch' received command OFF

2019-02-25 23:14:49.073 [ome.event.ItemCommandEvent] - Item 'Sonoff_3_Switch' received command OFF

2019-02-25 23:14:49.079 [ome.event.ItemCommandEvent] - Item 'lounge_tv' received command OFF

2019-02-25 23:14:49.085 [ome.event.ItemCommandEvent] - Item 'kitchen_music' received command OFF

2019-02-25 23:14:49.094 [ome.event.ItemCommandEvent] - Item 'lounge_music' received command OFF

2019-02-25 23:14:49.101 [nt.ItemStatePredictedEvent] - lounge_ceiling_light predicted to become OFF

2019-02-25 23:14:49.118 [nt.ItemStatePredictedEvent] - bedroom_ceiling_light predicted to become 30

2019-02-25 23:14:49.163 [vent.ItemStateChangedEvent] - lounge_ceiling_light changed from 40 to 0

2019-02-25 23:14:49.167 [vent.ItemStateChangedEvent] - bedroom_ceiling_light changed from 0 to 30

2019-02-25 23:14:49.175 [nt.ItemStatePredictedEvent] - kitchen_music predicted to become OFF

2019-02-25 23:14:49.183 [nt.ItemStatePredictedEvent] - lounge_music predicted to become OFF

I’d be grateful for any insight into this.

Cheers

Vincent

0 Likes

(Rossko57) #13
			if (timer === null) {
				timer = createTimer (now.plusSeconds(4)) [|
					...
				]
			}	
			timer.cancel
			timer = null
			if (timer === null) {
				timer = createTimer (now.plusSeconds(8)) [|
					...
				]
			}
			timer.cancel
			timer = null

This code creates a timer block scheduled for future execution, then immediately cancels it and destroys it.
It then reuses the same handle to create and schedule a timer block, and immediately cancels and destroys it again.

For clarity; you’re using a variable named timer as a container to hold a timer. That’s often useful, if you want to check or change that timer later. It works like a handle so you get hold of that scheduled code.

The action of createTimer is to make a block of code and schedule it to be run later. That’s the bit between [ ] above.
Having scheduled that, we then immediately carry on to the next line in the rest of the rule, and execute that.
In this case that’s timer.cancel, which has the obvious effect and stops that schedule.

0 Likes

(Rich Koshak) #14

Everything rossko57 said is dead on right.

createTimer schedules a bit of code (stuff between the [ ]) to run sometime in the future without blocking.

To block, you would use Thread::sleep. However, there are lots of reasons why that’s a bad idea so avoid using it if at all possible.

That is why your second attempt works. You schedule the code to run in 4 seconds. After 4 seconds it sends the OFF commands then you schedule another timer to run in 8 seconds ti send the last three commands.

NOTE: You should remove the timer.cancel. Hmmmm, are you sure this works? With the cancel it should prevent it from running.

So, the question is do you ever want to cancel these timers based on some other event? Or when the txt contains bedtime it will always run these Timers no matter what. If it’s the former I recommend creating two global variables to hold the timers and then schedule the two separately.

firstTimer = createTimer(now.plusSeconds(4), [ |
    Sonoff_3_Switch...
   firstTimer = null
])
secondTimer = createTimer(now.plusSeconds(12), [ | // 8+4
    lounge_ceiling_light...
    secondTimer = null
])

I doubt the errors you are seeing are coming from the block of code you posted.

Finally, I recommend reviewing A simple scene management: finally a good use of scripts for a potential way to better organize your scenes. I suspect this one Rule is HUGE. This might give you a way to make it more manageable and maintainable.

0 Likes

(Vincent Bevan) #15

Many thx to you and and to rlkoshak for helping me understand this better and for your quick responses, I now see where my fault lies. Thanks also for the pointer to scene management as I think you are right it will help me manage this better :sunglasses::+1:

0 Likes