I think the timers are automatically set to null when they expire. Also, are all of the timer variables globals declared at the top of the rules file?
Try nesting the createTimer() calls within the previous timer’s lambda so the next timer in the chain is started at the end of the previous timer’s lambda:
var Timer LV_timer5_tuinv = null
var Timer LV_timer6_tuinv = null
var Timer LV_timer7_tuinv = null
var Timer LV_timer8_tuinv = null
var Timer LV_timer11_tuinv = null
var Timer LV_timer12_tuinv = null
var Timer LV_timer13_tuinv = null
rule "poort loop licht"
when
Item PO_open_lamp_uit received command ON
then
if (LV_timer5_tuinv == null &&
LV_timer6_tuinv == null &&
LV_timer7_tuinv == null &&
LV_timer8_tuinv == null &&
LV_timer11_tuinv == null &&
LV_timer12_tuinv == null &&
LV_timer13_tuinv == null) {
LV_timer5_tuinv = createTimer(now.plusSeconds(5)) [|
sendCommand(Output11_3, ON) //Spot 1 to
LV_timer6_tuinv = createTimer(now.plusSeconds(2)) [|
sendCommand(Output11_4, ON) //Spot 2
LV_timer7_tuinv = createTimer(now.plusSeconds(2)) [|
sendCommand(Output11_2, ON) // Lamp ornament
...
]
]
]
]
]
]
]
}
end
thank you for this great example but I dont get it all the way through.
I want to cascade my stair light and tried work with your rule, but ist doesn´t work as ist should.
What I want to achive is: when presence is detected downstairs turn on the light of the first stair, wait x seconds (should be variable with a number item) turn on the next and so on
The same when precence is detectet upstairs
Turn on light of stair 8 then 7 and so on then turn them off one after the other.
Thank you for your fast reply.
I must admit, that I have no clue what I am reading when I read through your code.
At which place, do the different stairs get their on command, and when do they turn off?
I don´t just want to copy and past, I want to understand, at least a little bit, what I am doing.
The chunk of code between the createTimer’s square brackets [ ] runs at some point in the future.
When it does run, it can reschedule itself to run again.
By counting each time it runs, it can know how many times it has run and do something different each on each pass.
var Timer tLight = null
var Integer iStep = 0
var Boolean bDir = true // true == up, false == down
rule “steps ON”
when
Item precence_upstairs received command ON or
Item precence_downstairs received command ON
then
tLight?.cancel
bDir = if(triggeringItem.name.contains(“up”)) true else false
iStep = if(triggeringItem.name.contains(“up”)) 0 else 5
tLight = createTimer(now.plusMillis(10), [
var Integer nDur = 1
if(duration.state instanceof Number)
nDur = duration.state as Number
if(bDir)
iStep ++ // or iStep = iStep + 1
else
iStep – // or iStep = iStep - 1
glight_stair.members.filter[i |
i.name.contains(iStep.toString)].head.sendCommand(ON)
if(iStep > 1 && iStep < 5)
tLight.reschedule(now.plusSeconds(nDur))
])
end
this is what I get when triggering upstairs:
2020-03-16 22:28:07.869 [vent.ItemStateChangedEvent] - precence_upstairs changed from OFF to ON
==> /var/log/openhab2/openhab.log <==
2020-03-16 22:28:07.889 [ERROR] [org.quartz.core.JobRunShell ] - Job DEFAULT.Timer 105 2020-03-16T22:28:07.888+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ {
var nDur
org.eclipse.xtext.xbase.impl.XIfExpressionImpl@17138be (conditionalExpression: false)
org.eclipse.xtext.xbase.impl.XIfExpressionImpl@1e2affb (conditionalExpression: false)
<XMemberFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)
org.eclipse.xtext.xbase.impl.XIfExpressionImpl@948387 (conditionalExpression: false)
} ] threw an unhandled Exception:
java.lang.reflect.UndeclaredThrowableException: null
at com.sun.proxy.$Proxy730.apply(Unknown Source) ~[?:?]
at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [bundleFile:?]
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]
Caused by: org.eclipse.smarthome.model.script.engine.ScriptExecutionException: 'state' is not a member of 'int'; line 52, column 12, length 14
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:133) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:861) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:231) ~[?:?]
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:907) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:257) ~[?:?]
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:469) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:255) ~[?:?]
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:458) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:239) ~[?:?]
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:201) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29) ~[?:?]
... 4 more
2020-03-16 22:28:07.919 [ERROR] [org.quartz.core.ErrorLogger ] - Job (DEFAULT.Timer 105 2020-03-16T22:28:07.888+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ {
var nDur
org.eclipse.xtext.xbase.impl.XIfExpressionImpl@17138be (conditionalExpression: false)
org.eclipse.xtext.xbase.impl.XIfExpressionImpl@1e2affb (conditionalExpression: false)
<XMemberFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)
org.eclipse.xtext.xbase.impl.XIfExpressionImpl@948387 (conditionalExpression: false)
} ] threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [bundleFile:?]
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]
Caused by: java.lang.reflect.UndeclaredThrowableException
at com.sun.proxy.$Proxy730.apply(Unknown Source) ~[?:?]
at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
... 1 more
Caused by: org.eclipse.smarthome.model.script.engine.ScriptExecutionException: 'state' is not a member of 'int'; line 52, column 12, length 14
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:133) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:861) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:231) ~[?:?]
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:907) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:257) ~[?:?]
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:469) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:255) ~[?:?]
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:458) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:239) ~[?:?]
at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:215) ~[?:?]
at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:201) ~[?:?]
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.$Proxy730.apply(Unknown Source) ~[?:?]
at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
... 1 more
Hmm… I think you did not create an Item duration (this is the item which will allow you to choose the x seconds.
Another thing… I just forgot to complete the code to switch off…
So the question here is: When shall the lights turn off?
Especially, should all lights turned on before the first light turns off?
There was another bug in the code (missing - ). I’ve commented the lines:
// global vars have to be defined at top of file
var Timer tLight = null
var Integer iStep = 0
var Boolean bDir = true // true == up, false == down
rule "steps ON"
when
Item precence_upstairs received command ON or // detected movement upstairs
Item precence_downstairs received command ON // detected movement downstairs
then
tLight?.cancel // cancel existing timer
bDir = if(triggeringItem.name.contains("up")) true else false // which direction?
iStep = if(triggeringItem.name.contains("up")) 0 else 5 // count up or down?
tLight = createTimer(now.plusMillis(10), [ // create the timer
var Integer nDur = 1 // setup default duration
if(duration.state instanceof Number) // if duration is set
nDur = duration.state as Number // get duration from item
if(bDir) // if direction up
iStep ++ // or iStep = iStep + 1 // count up
else // if direction down
iStep –- // or iStep = iStep - 1 // count down
glight_stair.members.filter[i | i.name.contains(iStep.toString)].head.sendCommand(ON) // get the actual step and send command ON
if(iStep > 1 && iStep < 4) // if not last step
tLight.reschedule(now.plusSeconds(nDur)) // schedule the next step
])
end
And after thinking hard I changed the code a little bit plus created another timer for switching off the lights.
// create global vars and vals always on top of file
var Timer tLightOn = null // object for ON timer
var Timer tLightOff = null // object for OFF timer
var Integer iStepOn = 0 // counter for ON
var Integer iStepOff = 0 // counter for OFF
var Boolean bDir = true // true == up, false == down
var Integer nStep = 1 // setup default stepTime
var Integer nDuration = 30 // setup default time between ON and OFF
var Integer nMaxStep = 0 // number of steps (automatic)
rule "steps ON and OFF"
when
Item precence_upstairs received command ON or // detected movement upstairs
Item precence_downstairs received command ON // detected movement downstairs
then
if(tLightOn !== null || tLightOff !== null )
return;
bDir = if(triggeringItem.name.contains("up")) true else false // which direction?
iStepOn = if(triggeringItem.name.contains("up")) 0 else nMaxStep + 1 // count up or down?
iStepOff = if(triggeringItem.name.contains("up")) 0 else nMaxStep + 1 // count up or down?
if(stepTime.state instanceof Number) // if stepTime is set (item must exist!)
nStep = stepTime.state as Number // get stepTime from item
if(stepOnDuration.state instanceof Number) // if stepOnDuration is set (item must exist!)
nDuration = stepOnDuration.state as Number // get stepOnDuration from item
nMaxStep = glight_stair.members.size
tLightOn = createTimer(now.plusMillis(10), [ // create the timer for ON
iStepOn = iStepOn + if(bDir) 1 else -1 // count up or down depending on direction
glight_stair.members.filter[i |
i.name.contains(iStepOn.toString)].head.sendCommand(ON) // get the actual step and send command ON
if(iStepOn > 1 && iStepOn < nMaxStep) // if not last step
tLightOn.reschedule(now.plusSeconds(nStep)) // schedule the next step
else
tLightOn = null
])
tLightOff = createTimer(now.plusSeconds(nDuration), [ // create the timer for OFF
iStepOff = iStepOff + if(bDir) 1 else -1 // count up or down depending on direction
glight_stair.members.filter[i |
i.name.contains(iStepOff.toString)].head.sendCommand(OFF) // get the actual step and send command OFF
if(iStepOff > 1 && iStepOff < nMaxStep) // if not last step
tLightOff.reschedule(now.plusSeconds(nStep)) // schedule the next step
else
tLightOff = null
])
end
I created a second timer for switching OFF. Of course we need another counter for that timer.
I changed the name nDur to nStep and changed the var to be global, as this var has to be the same for both timers and the time should not be changed while running timers.
I changed the behavior of the rule, since most likely when using the steps, you will trigger both motion detectors. So the second trigger (from the other motion detector) has to be ignored, as long as the timers do their job.
I created a var for maximum steps to make more clear the function of some values. This could be a val, as it’s very unlikely that the amount of steps will ever change but as it’s a nice feature, the rule determines the number of items in the group. This becomes handy when testing the rule with less steps (just don’t put them into the group - but be aware that the item names must contain a counter which starts with 1 up to the number of steps. But I have an idea to get rid of this problem, too )
Furthermore I created another var nDuration for the switch off time. This is: 30 seconds after a step is switched ON, it is switched OFF again. This value is configurable via an Item stepOnDuration, which must exist.
I changed count up and down, as the ternary operator is much more efficient here.