Design Pattern: Gate Keeper

Hello all,
I hope a comment on this pattern is a good place to put my question - if it is not, please let me know. I will open up a new thread then.

My goal

I want to send out notifications via telegram from rules

My approach

  • proxy string TNotificationViaTelegram for the message to be sent
  • any rule may set it, e.g. TNotificationViaTelegram.sendCommand(tMsg)
  • a central rule which reacts to changes in this item:
rule "Send Notification over Telegram"
  //notify via Telegram
  when
    Item TNotificationViaTelegram received update
  then
    if (SNotificationViaTelegram.state != ON) {
      return;
    }

    val String msg = TNotificationViaTelegram.state.toString
    
    sendTelegram("HomeInfo", msg)
  end

Problem

In the Telegram message stream, I sometimes miss a message and instead get the same message twice in quick succession.
I am pretty certain, that this is what is going wrong:

  1. rule A updates TNotificationViaTelegram
  2. rule B updates TNotificationViaTelegram (in quick succession - this e.g. happens by rules which react to presence)
  3. rule Send Notification over Telegram gets triggered by the rule A update, but the actual sending happens, rule B has already changed the content - ergo the rule B message gets sent
  4. rule Send Notification over Telegram gets triggered by the rule B update, the rule B message gets sent

Solution approaches

Note: these are the ones I can think of, I have not tried out any of them yet - still theorizing :wink:

  1. Add a Thread::sleep after every instance of TNotificationViaTelegram.sendCommand(tMsg)

    • Unsexy approach, as it is not centralized and I have to think of it whenever I sent a message
    • I don’t think it will work 100%, as when a certain event (presence) triggers two rules, these two rules might still update TNotificationViaTelegram at nearly the same point of time. This would be less likely to happen, if I use randomized sleep-times, which makes working with this approach even more complicated.
  2. Approach as suggested in the “Simple Example”

    • If I understand correctly, this would not solve my problem (or would it??).
      I believe the approach makes a rule a singleton. But my problem is not the rule running in parallel, but that TNotificationViaTelegram is changed while the rule is running.
  3. Approach as suggested in the “Complex Example using Queues”

    • In essence, a FIFO queue is what would solve my problem. The way the example is set up though, the queue does not span across mutiple rules-files - correct?

So what is your view on this? Can anyone give me some pointers how to get this done?
Maybe there is a simple mechanism which I am use but don’t know or fail to recognize?

By the way: @rlkoshak - I have been using OpenHab for ca. 2 years now and your posts in this forum have been tremendously helpful for my understanding. I am amazed how often you find the time to take part in a discussion and help out with your knowledge and views.
Thank you very much for that!

This is probably not a Gate Keeper problem, though it might be solvable with it, though I don’t think that’s necessary.

Use commands instead of updates to trigger your Rule. Command makes sense here as you are activating an external service, not just updating OH’s internal state. Then, when you use received command, you can use the receivedCommand implicit variable which will always be what was commanded regardless of timing.

Option won’t do anything because that Thread::sleep won’t affect the other Rules that call the sendCommand. It will just delay when that one instance of the Rule will exit.

Option two and three won’t really solve your problem because your actual problem, if I understand correctly, is that when two updates occur too close together one of them get’s overwritten before it can be processed by the Rule. Using a lock or a timer or the queue will only add delays between calls to telegram so you don’t bombard the service with too many calls all at the same time. But the data problem you are seeing would still be a problem.

You might still want to use a Gate Keeper to spread out the messages sent to Telegram, but you need to use commands in order to keep the states from overwriting each other.

You are most welcome! I learn by helping and contributing to the forum is my way of giving back to the community.

1 Like

@rlkoshak: so you offer real-time problem solving services - cool :wink: ! In my job, I have access to some paid “premium support services” which are nowhere as good (or quick) as you are :smiley:

Thanks - I fully understand what you say. I even remember reading about the receivedCommand variable at some point. But I never used it and forgot about it.
I’ll go that way - I am very confident it’ll work!

You just happened to catch me when I was online. Though when I’m tagged I get an email and can often reply from my phone, which was the case here.

Edit: @5iver solved my problem and I have corrected the code so that one can copy&paste items and code if one have a similar use case. @rlkoshak thank you for this great design pattern!

Hi @rlkoshak , this is the case with my sonos speakers, when there are multiple notifications to be played.
That’s why I try to use the complex example of your design pattern to manage playing notifications.
But there are errors in my openhab.log that I cannot solve and I couldn’t find a solution in the forum.

My items in file GateKeeperExamples.items

Group GrpMessages
Switch CreateMessage    "CreateMessage [%s]"    (GrpMessages)
String TextQueued       "TextQueued [%s]"       (GrpMessages)
String TextOutput       "TextOutput [%s]"       (GrpMessages)

My code in file GateKeeperExamples.rules

// IMPORTS **************************************************************************  
    import java.util.concurrent.ConcurrentLinkedQueue

// GLOBAL VARIABLES ************************************************************
    val MyQueue = new ConcurrentLinkedQueue()    // Edited this line. The code causing the errror was: val Queue<String> MyQueue = new ConcurrentLinkedQueue()
    var Timer timer = null
    var lastCommand = now.minusSeconds(1).millis

    var MyMessageCounter = 0

// RULES ****************************************************************************

rule "rGateKeeperExample_CreateMessage"
 when
    Item CreateMessage received command
 then
    MyMessageCounter = MyMessageCounter + 1     // MyMessageCounter see global variables section
    val MyMessage = "Message no. " + MyMessageCounter.toString + " , date = " + now.toString
    TextQueued.sendCommand( MyMessage )
    logInfo("openhab", "New Message = " + MyMessage )
end // EndOfRule rGateKeeperExample_CreateMessage

rule "rGateKeeperExample_MessageOutput"
 when
    Item TextQueued received command
 then
    val String          MyLogFile = "MySeparateLogFile"   // if 'MySeparateLogFile' does not exist, logging goes into openhab.log (to create a separate logfile see https://www.openhab.org/docs/administration/logging.html#logging-into-separate-file)
    val DateTime        MyLogDate = now  
    val StringBuilder   MyLogMessage = new StringBuilder
    MyLogMessage.append( "\n" + "\n" + " ----------   rGateKeeperExample_MessageOutput v001   ----------" + "\n" + "\n" )
    MyLogMessage.append( "    MyLogDate = " + MyLogDate.toString + "\n" + "\n" ) // ("yyyy-MM-dd'T'HH:mm:ss.SSSZ")

    MyQueue.add(receivedCommand.toString)  // https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html#add(E)
    MyLogMessage.append( "    TextQueued = " + receivedCommand.toString + "\n" ) 

    if(timer === null) {
        timer = createTimer(now, [ |
            if(MyQueue.peek !== null) {    // https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html#peek()
                val TmpText = MyQueue.poll     // https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html#poll()
                TextOutput.sendCommand( TmpText.toString )
                // say(TextOutput, "voicerss:deDE", "sonos:PLAY1:SonosLiving", new PercentType(25))
                MyLogMessage.append( "       TextOutput = " + TmpText.toString + "\n" ) 
                lastCommand = now.millis
            }
            timer.reschedule(now.plusMillis(60000))  // output of next message after 60s
        ] )
    } 
    logInfo( MyLogFile, MyLogMessage.toString )
end // EndOfRule rGateKeeperExample_MessageOutput

My openhab.log

2019-03-28 20:58:18.129 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'GateKeeperExample.rules'
2019-03-28 21:25:55.014 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'GateKeeperExample.rules', using it anyway:
The field Tmp_GateKeeperExampleRules.MyQueue refers to the missing type Object
The field Tmp_GateKeeperExampleRules.MyQueue refers to the missing type Object
The field Tmp_GateKeeperExampleRules.MyQueue refers to the missing type Object
2019-03-28 21:25:55.209 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model 'GateKeeperExample.rules'
2019-03-28 21:26:26.329 [INFO ] [lipse.smarthome.model.script.openhab] - New Message = Message no. 1 , date = 2019-03-28T21:26:26.320+01:00
2019-03-28 21:26:26.359 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'rGateKeeperExample_MessageOutput': 'add' is not a member of 'Object'; line 38, column 5, length 37
2019-03-28 21:26:28.888 [INFO ] [lipse.smarthome.model.script.openhab] - New Message = Message no. 2 , date = 2019-03-28T21:26:28.875+01:00
2019-03-28 21:26:28.915 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'rGateKeeperExample_MessageOutput': 'add' is not a member of 'Object'; line 38, column 5, length 37
2019-03-28 21:26:29.725 [INFO ] [lipse.smarthome.model.script.openhab] - New Message = Message no. 3 , date = 2019-03-28T21:26:29.719+01:00
2019-03-28 21:26:29.757 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'rGateKeeperExample_MessageOutput': 'add' is not a member of 'Object'; line 38, column 5, length 37

Problem
I think the info in openhab.log
The field Tmp_GateKeeperExampleRules.MyQueue refers to the missing type Object
indicates, that there is something wrong with the declaration of MyQueue in this line of my code
val Queue<String> MyQueue = new ConcurrentLinkedQueue()

Every time the rule is triggered I get this error
Rule 'rGateKeeperExample_MessageOutput': 'add' is not a member of 'Object'; line 38, column 5, length 37
which points to line 38 with this code MyQueue.add(receivedCommand.toString)

I couldn’t find a solution yet, so every help is appreciated.

My system is openhabian installed on raspberry pi:

  • Release = Raspbian GNU/Linux 9 (stretch)*
  • Kernel = Linux 4.14.79-v7+*
  • Platform = Raspberry Pi 3 Model B Rev 1.2*
  • openHAB 2.4.0-1 (Release Build)*
1 Like

In DSL rules, only be as specific as you need to be.You shouldn’t get the error if you use this…

val MyQueue = new ConcurrentLinkedQueue()
1 Like

Many thanks, that is the solution.
Small cause, big effect!

If I have a rule using a GateKeeper (the complex example using queues), then one timer runs forever:
If a timer runs forever, does it mean, one thread is used forever?

If I have five rules, each using one GateKeeper:
Does the Rules DLS run out of threads?

When a timer is timing, no thread is used.
When a timer has expired and is executing it’s little code block, it uses a thread until it has exited that code.

rossko57 is correct. That is why I use Timers in that example. Timers use no resources beyond a tiny bit of ram while they are awaiting their time to execute.

Before the code block within timer = createTimer(now, [ | ... ]) can be exit, the timer is rescheduled. That’s why I’m wondering if the thread is running forever. Or almost forever.

That’s fine. Awaiting a scheduled time uses no thread.
When the timer’s little code block has completed the reschedule task, it exits. So releasing the execution thread.

Okay thank you very much. That’s reassuring.

The Gatekeeper (python) version is not (yet) in the community openhab helper libraries. Would be nice to have it there.

Regards S

That’s the plan. I’ve still some unit tests to write and documentation to flesh out before I submit the PR. I’ll update the OP when it’s merged. Most of the DPs I hope to similarly submit most of the DPs and tutorials.

2 Likes

I used this pattern to help out with turning on/off wifi for my kids… I’m using the Unifi binding and I put all my kids devices into a separate group (gDevicesToBlock). The problem was that when I did a set on the group, all commands where sent to the binding without any delays and it didn’t catch all devices. Same problem that’s described in the design pattern above for 433 Mhz devices.

Anyway, I updated the pattern and I just want to share it, as it took some time to get right.


Items:
======
Switch UnifiBlockController "Block wireless" <switch>
Group gDevicesToBlock "Devices to block"

Switch NilsPhoneBlock "Nils phone block" <switch> (gDevicesToBlock)
  {channel="unifi:wirelessClient:home:nilsPhone:blocked"}
Switch IvarsPhoneBlock "Ivar phone block" <switch> (gDevicesToBlock)
  {channel="unifi:wirelessClient:home:ivarsPhone:blocked"}


Rule:
=====

import java.util.concurrent.ConcurrentLinkedQueue

val deviceQueue = new ConcurrentLinkedQueue()
var Timer timer = null
val delay = 1000  // Delay in milliseconds between commands


rule "Block unblock Unifi"
when
  Item UnifiBlockController received update
then

  gDevicesToBlock.members.forEach[ device |
    deviceQueue.add(device)
  ]

  if (timer === null) {
    timer = createTimer(now.plusMillis(delay), [ |
      if (deviceQueue.peek !== null) {
        val device = deviceQueue.poll as SwitchItem
        device.sendCommand(UnifiBlockController.state)
        timer.reschedule(now.plusMillis(delay))
      } else {
        timer = null
      }
    ])
  }
end
3 Likes

The problem that you will still pass (((
This pattern is not usable for group switches, that are most used.
If you try to turn on or off the group from example on the top. This one

Group WirelessDevices

It will fail because of basic fault of setting timers at one time.
It means that the group switch just forces all the switches in group to to turn on or off AT ONE TIME (millis are difference)!
Because of it the rull will try to create multiply timers (it will just have not time to process all of them at once and to check if timer exists) and will fail (((

It can works somethimes with 2 switches in group, but if you have more it will cause the fault.

Can this behavior be in someway changed?

Show your code.

You can’t send a command to a Group with this DP. You must send the command to each member of the Group individually. From what you describe I can’t tell if that is what you are trying to do.

In the DP above, there is only one timer. If you are calling Gatekeeper for each member individually, you may need you add a short Thread::sleep, 10 msec should be enough, between each call to Gatekeeper.

I’m assuming the Rules DSL version of the code. The Python version blocks on the call to addCommand so there is no chance of processing a new consensus before the looking timer is created.

1 Like

Here is my code.


import java.util.concurrent.ConcurrentLinkedQueue
val ConcurrentLinkedQueue<String> commands = new ConcurrentLinkedQueue()

var String commandToQuery 
var Timer timer = null
var lastCommand = now.minusSeconds(1).millis

rule "Arduino 433Mhz Send Command. Query."
when
  Member of gSocket3All received command
then 
    commandToQuery = triggeringItem.name + "-" + receivedCommand.toString
    commands.add(commandToQuery)
        logInfo("arduino", "Add to queue: " + commandToQuery + "// Timer State: " + timer)
        if (timer === null) {
            logInfo("arduino", "No timer. Creating.")
            timer = createTimer(now, [|
                if(commands.peek !== null) {
                    val cmd = commands.poll
                    logInfo("arduino", "Sending command to Arduino 433Mhz: " + cmd + " and transformed: " + transform("MAP", "arduino2.map", cmd))
                    Arduino.sendCommand(transform("MAP", "arduino2.map", cmd))
                    lastCommand = now.millis
                } 
                val deltaTime = now.millis - lastCommand
                timer.reschedule(now.plusMillis(if(deltaTime<100) 100-deltaTime else 0)) // 0 will reschedule the timer to run immediately
            ]) 
        } 
end 

And after the first use it crashes with

2019-12-15 19:38:14.858 [INFO ] [lipse.smarthome.model.script.arduino] - Add to queue: Socket3N2-ON// Timer State: null

2019-12-15 19:38:14.861 [INFO ] [lipse.smarthome.model.script.arduino] - No timer. Creating.

2019-12-15 19:38:14.871 [INFO ] [lipse.smarthome.model.script.arduino] - Sending command to Arduino 433Mhz: Socket3N2-ON and transformed: 10;NewKaku;02a0ec00;3;ON;

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

2019-12-15 19:38:14.877 [ome.event.ItemCommandEvent] - Item 'Arduino' received command 10;NewKaku;02a0ec00;3;ON;

2019-12-15 19:38:14.882 [vent.ItemStateChangedEvent] - Arduino changed from 20;82;GlobalTronics;ID=0010;TEMP=0000;HUM=91;BAT=OK;

 to 10;NewKaku;02a0ec00;3;ON;

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

2019-12-15 19:38:14.891 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Arduino 433Mhz Receiver': 6

2019-12-15 19:38:14.895 [ERROR] [org.quartz.core.JobRunShell         ] - Job DEFAULT.Timer 1197 2019-12-15T19:38:14.864+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {

  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@f84189

  val deltaTime

  <XFeatureCallImplCustom>.reschedule(<XMemberFeatureCallImplCustom>)

} ] threw an unhandled Exception: 

java.lang.IllegalStateException: Could not invoke method: org.joda.time.DateTime.plusMillis(int) on instance: 2019-12-15T19:38:14.877+01:00

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

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

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

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

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

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

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

	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.evaluateArgumentExpressions(XbaseInterpreter.java:1116) ~[?:?]

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

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

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

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

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

	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.$Proxy519.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) [182:org.openhab.core.scheduler:2.5.0.M4]

	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [182:org.openhab.core.scheduler:2.5.0.M4]

Caused by: java.lang.IllegalArgumentException

	at sun.reflect.GeneratedMethodAccessor197.invoke(Unknown Source) ~[?:?]

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]

	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:?]

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

	... 27 more

2019-12-15 19:38:15.018 [ERROR] [org.quartz.core.ErrorLogger         ] - Job (DEFAULT.Timer 1197 2019-12-15T19:38:14.864+01:00: Proxy for org.eclipse.xtext.xbase.lib.Procedures$Procedure0: [ | {

  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@f84189

  val deltaTime

  <XFeatureCallImplCustom>.reschedule(<XMemberFeatureCallImplCustom>)

} ] threw an exception.

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

	at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [182:org.openhab.core.scheduler:2.5.0.M4]

	at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [182:org.openhab.core.scheduler:2.5.0.M4]

Caused by: java.lang.IllegalStateException: Could not invoke method: org.joda.time.DateTime.plusMillis(int) on instance: 2019-12-15T19:38:14.877+01:00

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

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

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

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

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

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

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

	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.evaluateArgumentExpressions(XbaseInterpreter.java:1116) ~[?:?]

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

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

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

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

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

	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.$Proxy519.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: java.lang.IllegalArgumentException

	at sun.reflect.GeneratedMethodAccessor197.invoke(Unknown Source) ~[?:?]

	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]

	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:?]

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

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

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

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

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

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

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

	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.evaluateArgumentExpressions(XbaseInterpreter.java:1116) ~[?:?]

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

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

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

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

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

	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.$Proxy519.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

And timer do not run in loop after this.

Where do i add this delay Rich?
If i set it direct after

then

then the commands added witout any delay.