Create while loops in rules

Hi,

I could use help creating a rule with a while loop. It’s just a test of how the whole thing works and not a real practical application yet. Nevertheless, I already have a few ideas in mind.

Let me explain what I’m trying to accomplish:

    while(condition == true) {
        // do work
        Thread::sleep(1)

        if (isRunningExampleRule.state == OFF) {
            condition = false
        }
        logInfo("example rule", "Greetings from example rule")
    }

I have a trigger (exampleRuleTrigger) that executes a rule. This is simplified also only a switch. Then I have another switch (isRunningExampleRule) that I want to use as a termination condition in this rule.

I tried following:

rule "example rule"
when
    Item exampleRuleTrigger changed to ON
then
    logInfo("example rule", "execution of example rule is started")

    var condition = true
    timer = createTimer(now.plusSeconds(1), [ |
        // do work
        if (isRunningExampleRule.state == OFF) {
            condition = false
        }
        logInfo("example rule", "Greetings from example rule")

        if (condition == false) {
            timer = null
        }
        timer.reschedule(now.plusSeconds(1))
    ])

    logInfo("example rule", "execution of example rule is finished")
end

But I will receive following error:

06:10:36.028 [INFO ] [openhab.event.ItemCommandEvent       ] - Item 'exampleRuleTrigger' received command ON
06:10:36.034 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'exampleRuleTrigger' changed from OFF to ON
06:10:36.085 [INFO ] [penhab.core.model.script.example rule] - execution of example rule is started
06:10:36.094 [ERROR] [.internal.handler.ScriptActionHandler] - Script execution of rule with UID 'rules-1' failed: An error occurred during the script execution: Couldn't invoke 'assignValueTo' for feature JvmVoid:  (eProxyURI: rules.rules#|::0.2.0.2.0.2::0::/1) in rules
06:10:37.087 [INFO ] [penhab.core.model.script.example rule] - Greetings from example rule
06:10:37.091 [WARN ] [core.internal.scheduler.SchedulerImpl] - Scheduled job '<unknown>' failed and stopped
java.lang.reflect.UndeclaredThrowableException: null
	at com.sun.proxy.$Proxy142.apply(Unknown Source) ~[?:?]
	at org.openhab.core.model.script.actions.ScriptExecution.lambda$0(ScriptExecution.java:97) ~[?:?]
	at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$12(SchedulerImpl.java:191) ~[?:?]
	at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$1(SchedulerImpl.java:88) ~[?:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
	at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
	at java.lang.Thread.run(Thread.java:829) [?:?]
Caused by: org.openhab.core.model.script.engine.ScriptExecutionException: The name 'timer' cannot be resolved to an item or type; line 78, column 9, length 5
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:141) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1008) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:971) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:247) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:874) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:243) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:475) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:251) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:213) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:47) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:30) ~[?:?]

Note: the state of isRunningExampleRule is still ON.

If I change timer = ... to var timer = ... I will receive following error:

06:12:35.518 [INFO ] [del.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'rules.rules', using it anyway:
The value of the local variable timer is not used
06:13:17.813 [INFO ] [openhab.event.ItemCommandEvent       ] - Item 'exampleRuleTrigger' received command ON
06:13:17.821 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'exampleRuleTrigger' changed from OFF to ON
06:13:17.925 [INFO ] [penhab.core.model.script.example rule] - execution of example rule is started
06:13:17.929 [INFO ] [penhab.core.model.script.example rule] - execution of example rule is finished
06:13:18.929 [INFO ] [penhab.core.model.script.example rule] - Greetings from example rule
06:13:18.931 [WARN ] [core.internal.scheduler.SchedulerImpl] - Scheduled job '<unknown>' failed and stopped
java.lang.reflect.UndeclaredThrowableException: null
	at com.sun.proxy.$Proxy142.apply(Unknown Source) ~[?:?]
	at org.openhab.core.model.script.actions.ScriptExecution.lambda$0(ScriptExecution.java:97) ~[?:?]
	at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$12(SchedulerImpl.java:191) ~[?:?]
	at org.openhab.core.internal.scheduler.SchedulerImpl.lambda$1(SchedulerImpl.java:88) ~[?:?]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
	at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
	at java.lang.Thread.run(Thread.java:829) [?:?]
Caused by: org.openhab.core.model.script.engine.ScriptExecutionException: The name 'timer' cannot be resolved to an item or type; line 78, column 9, length 5
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:141) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:1008) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:971) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:247) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:874) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:243) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:475) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:251) ~[?:?]
	at org.openhab.core.model.script.interpreter.ScriptInterpreter.doEvaluate(ScriptInterpreter.java:226) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:227) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:213) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:47) ~[?:?]
	at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:30) ~[?:?]
	... 10 more

Instead of true and false I tried it with 1 and 0.

Of course if it would work the loop can be simplified to

    timer = createTimer(now.plusSeconds(1), [ |
        // do work
        logInfo("example rule", "Greetings from example rule")

        if(isRunningExampleRule.state == ON) timer.reschedule(now.plusSeconds(1))
        else timer = null
    ])

Obviously I am too stupid to write a While loop.

Thanks in advance.

Hi,

If you want to access the timer again, you need to define the variable for it outside of the rule definition, i.e. in the beginning of the rule file.

BUT: the more important point in my opinion is that this approach looks to me conceptually wrong… a rule should not actively “wait” for a condition to become true, neither in a while loop nor by rerunning a timer task every second. Obviously this is difficult to discuss in an abstract example, but what hinders you to also trigger the rule on isRunningExampleRule.state changed to OFF? In the rule you can then check that the combination is as expected…

rfu

First of all, there is a while function (and it’s better style not to use it but instead do it with a timer).

Second: maybe there is a misundersstandung about the nature of a rule with a timer.
The rule will create the timer and then will terminate. That is: the timer is completely independent of the rule (well, mostly…)
To get control over a timer - even from the code which is executed by the timer itself - you’ll need a pointer to the scheduled task. This is the value stored in the var. The var must be of Type Timer, and you have to do it manually. As the timer itself is completely independent of the rule (mostly…) the timer var has to be a global var, not locally.
Although timer is not reserved, it’s best practice to use a less general name for the var.

The simple solution for your issue is this:

// keep global var definition on top of the file!
var Timer tMyTimer = null // a simple pointer to a scheduled task, set to nothing

rule "example rule"
when
    Item exampleRuleTrigger changed to ON
then
    logInfo("example rule", "execution of example rule is started")
    tMyTimer = createTimer(now.plusSeconds(1), [ |
        // do work
        logInfo("example rule", "Greetings from example rule")
        if (isRunningExampleRule.state == OFF) 
            tMyTimer = null
        else
            tMyTimer.reschedule(now.plusSeconds(1))
    ])
    logInfo("example rule", "execution of example rule is finished")
end

You won’t need an additional var named condition. You’ll have to check all conditions within the timer code itself, or at least the vor has to be globally defined, too.