Best way to make long time macro

Hello community!
I just started with Openhab 2 days ago and I would like to begin from my irrigation / watering system in the garden.
What I want to create:

  • I want to have a macros (and show them in habmin panel), something like “turn on each watering section for 15 minuts”

I know how to create macros based on Thread:sleep and Timers

Questions:

  1. Can I interrupt / cancel rule when it is being executed for case it has Thread::sleep function?
  2. How to perform a rule when habmin button is pressed? (it would start my macro)
  3. I would like to pass one parameter to rule. I need it to pass amount of minuts for each section of watering system that should be run (in case the macro button is pressed)

I would appreciate a help

IDK if it is the best way, but you can create an dummy-item in the config

// HABPanel Item
Number HABPanel_Test_One "HABPanel Test 1"

After that you can create several buttons in HABPanel and let them send different commands.

And then you can make a rule, that observes changes for this item and reacts on that.

rule "HABPanel act on Button Change"
when
    Item HABPanel_Test_One changed
then
    // Dummy-Test Log
    logInfo("testarea.rules", HABPanel_Test_One.state.toString())
end

This results in:

Then you should have everything to make different actions based on that command value.

One extra reply for question 1:

I don’t know if Thread::Sleep() can be interrupted.
But if you would work with Timers, you can easily cancel them, while they are running.

timer.cancel()

Does the work for you.

So if you have already a timer running and would like to stop the watering system by HABPanel Button,
you could send command value 0 and cancel all timers, when this button has been pressed.

Maybe someone else knows a bit more about Thread::Sleep(), since i didnt work with it yet.

Thank You jerome. I made some macro function and I cant make it working. Below the function:

val org.eclipse.xtext.xbase.lib.Functions$Function3<List<SwitchItem>,List<Integer>,Integer,void> startTmpSpringler = [
 List<SwitchItem> pItems, List<Integer> pTimes, Integer pIndex |
        for(var i = 0; i < pItems.size; i++)
        {
                if(i != pIndex)
                {
                        pItems.get(i).sendCommand(OFF)
                }
        }
        sprMacroTimer = createTimer(now.plusSeconds(pTimes.get(pIndex)), [|
        pItems.get(pIndex).sendCommand(OFF)

        if((pIndex + 1) < pItems.size)
        {
               startTmpSpringler.apply(pItems, pTimes, pIndex+1)
        }else
        {
               sprMacroTimer = null
        }
    ])
]

there is exception on this line:

startTmpSpringler.apply(pItems, pTimes, pIndex+1)

The exception:

017-07-17 16:53:05.235 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.2017-07-17T16:53:05.208+02:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {
  <XMemberFeatureCallImplCustom>.sendCommand(<XFeatureCallImplCustom>)
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@123d301
} ] threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: java.lang.NullPointerException: cannot invoke method public abstract java.lang.Object org.eclipse.xtext.xbase.lib.Functions$Function3.apply(java.lan$
        at org.quartz.core.JobRunShell.run(JobRunShell.java:213)[104:org.eclipse.smarthome.core.scheduler:0.9.0.b5]
        at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573)[104:org.eclipse.smarthome.core.scheduler:0.9.0.b5]
Caused by: java.lang.NullPointerException: cannot invoke method public abstract java.lang.Object org.eclipse.xtext.xbase.lib.Functions$Function3.apply(java.lang.Object,java.lang.Object,java.lang.Object) on null
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1070)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeOperation(XbaseInterpreter.java:1060)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._invokeFeature(XbaseInterpreter.java:1046)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.invokeFeature(XbaseInterpreter.java:991)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.smarthome.model.script.interpreter.ScriptInterpreter.invokeFeature(ScriptInterpreter.java:114)[129:org.eclipse.smarthome.model.script:0.9.0.b5]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:763)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:219)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:446)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:227)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:459)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:243)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter._doEvaluate(XbaseInterpreter.java:446)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.doEvaluate(XbaseInterpreter.java:227)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.internalEvaluate(XbaseInterpreter.java:203)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.XbaseInterpreter.evaluate(XbaseInterpreter.java:189)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.ClosureInvocationHandler.doInvoke(ClosureInvocationHandler.java:46)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at org.eclipse.xtext.xbase.interpreter.impl.AbstractClosureInvocationHandler.invoke(AbstractClosureInvocationHandler.java:29)[145:org.eclipse.xtext.xbase:2.9.2.v20160428-1452]
        at com.sun.proxy.$Proxy132.apply(Unknown Source)[:]
        at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:44)[129:org.eclipse.smarthome.model.script:0.9.0.b5]
        at org.quartz.core.JobRunShell.run(JobRunShell.java:202)[104:org.eclipse.smarthome.core.scheduler:0.9.0.b5]
        ... 1 more

As I am new with this language, I have few questions:

  1. Is it forbidden to call function from timer?
  2. Does createTimer makes another thread or it is recursion in this case?
  3. Anyone knows how to make it working? :>

When i get you code correct (i am still earning timer things myself^^) you set the timer “null” in the else condition before firing the “cancel()” method.

This will make the timer run forward in background and it tries to fire its handle after time’s up.
I think i had an similar issue, while i wanted to set the timer null.

My solution was:

tWashingMachine.cancel()
tWashingMachine = null

Try that.
but maybe i am getting something wrong since i didnt use timers like you do inside another List element?!

Ok everything clear…

It seems like global lambda function / another global VARs are reachable ONLY directly from “rule” body (the exceptions are items).

That is why I couldnt call (startTmpSpringler function is null within lambda function):
startTmpSpringler.apply(pItems, pTimes, pIndex+1)

and couldnt assign global timer inside lambda function:
sprMacroTimer = createTimer(now…) [global sprMacroTimer is always null]

I would really like to see any workaround for this or explanation how items are initialized that they can be globally reached

No. Instead of using a long sleep, try something like:

Create an unbound Switch and a Rule that gets triggered when that switch is pressed.

See:

Store the value in an Item. You cannot pass parameters to Rules directly

You might find the following informative:

Note the above is a 1.x version set of rules and will need slight modifications to work in OH 2.x.

Here is an approach I wrote up a long time ago that I just rewrote as a Design Pattern which might be helpful:

And a few other comments:

  • “macro” is not a term of use in the Rules DSL. We have Rules, Scripts, and lambdas. From your later posts, you seem to be referring to lambdas. Using the same terms will help us understand your questions better.

No, but unfortunately error reporting from within lambdas is not very good. Almost any error would cause that log entry. The problem could be one of syntax (I’m not sure the Rules Language supports for loops with that syntax for example). The best thing to do is load the rule up in ESH Desginer 0.8 and see if it finds any syntax errors. Then add a try catch around the entire body of your lambda and log out what exception is actually being thrown.

It does neither. It adds a lambda to the Timer thread to be executed at a later date.

That is correct. Furthermore, lambdas are only available to the rules in that .rules file.

You could pass the lambda to itself as a function argument if that is what you really wanted to do. I highly recommend applying some of what is in my links above though for a much easier and cleaner solution.

Also correct. A lambda can only access what is available from the context in which it was created. A global lambda has no context so cannot access anything except Items and what is passed to it as an argument.

Items are globally reachable. But global vals and vars are only available to Rules from within the same .rules file they are defined. And lambdas only have access to what gets passed to them.

Finally, coding rules where you need these sorts of accesses and passing of things around will only give you hours of frustration, excessively lengthy, complicated, and brittle code that is very difficult to maintain and update. I highly recommend looking through the links above and the Design Pattern examples to find alternaitve, approaches that use the features of OH and the Rules language.

Thank You Rich for extended answer.
I implemented my irrigation system directly in “Rule” body, based on state changes -> sendCommand(ON) (similar to what You show above in Design Pattern: Cascading Timers) but I would like to make those functions reusable to agregate commonly used behaviours (I have some idea and I will add it here if it works)

I have two more questions:

  1. I wonder if it is possible to pass reference type to lambda function, something like from C: foo(&pVariable) to make something like:
    foo(global_variable) and inside the foo makes something like
    pVariable = 5

and then 5 is assigned to the global variable

  1. Could You provide the information how to implement classes (like Timer or so?)

I appreciate any help

No. The Rules Domain Specific Language (DSL) is a much much higher level language then C and as other similar higher level languages you do not have access to pointers and passing by reference and all that.

One trick work around would be to put your values (e.g. the reference to the Timer) into an ArrayList or HashMap and pass that ArrayList or HashMap.

But ultimately, like I said, if you try to code in the Rules DSL like it was C you will end up unhappy.

You cannot in the Rules DSL. In addition to being a high-level language, it is also a highly constrained language. It is most similar to Xtend but purposely lacks many features of even that language the ability to create your own classes, structures, or even arrays.

If you want to add a new feature to the Rules language it is called an Action and the instructions for coding new add-ons to OH can be found here:

http://docs.openhab.org/developers/