Timer in DSL UI rule

  • Platform information:
    • Hardware: Raspberry Pi 4 Model B Rev 1.4 8GB
    • OS: Debian GNU/Linux 10 (buster) (openhabian)
    • Java Runtime Environment: _openjdk version “11.0.10” 2021-01-19 LTS
    • openHAB version: 3.1.0.M1 - Milestone Build
      Hey!

I was running openhab 2.5 for some time, and decided to migrate to version 3 and follow the best practices where possible. I am DIY coder so my code is not always pretty. In OH3 I decided to start setup from scratch with DSL rules created via UI.
Where possible I copied in some old rules and just cleaned them up. I got a pretty decent minimal setup, however in my old rules I was using Thread::sleep which I know is not the best practice as it makes system wait the time before executing the rest. I wanted to give timers a go but after scrolling through a lot of topics I am still a bit confused on how to create it in UI rule.
Below is one of the scripts I would like to convert to use timer:

Thread::sleep(2000)
if (FrontDoorSensor_ContactFrontdoor.state==CLOSED){
Frontdoor_DoorLock.sendCommand(ON)
}

Now what it does is: If sensor on the door fires CLOSED system should wait 2 seconds and if the door is still closed, turn the lock. The delay is there to prevent the system from closing when the door is not yet fully closed or if you close it, then go like, crap, forgot keys and open them again to grab them, then lock. Could you guide me a bit how to change it to create a timer and achieve similar result?
I am also using timer with my light, so if I hold two buttons on my Aqara double switch it should lock the back door, kill all the lights and if it is after dawn (I have a dummy switch switching on at night with Astro binding) switch light in the hallway for a short time and then switch it off so that I don’t have to leave the house in darkness. This is sort of I am leaving action.
The below is a section of CASE statement for this:

case “both_long”:{
Lights_all.sendCommand(OFF)
Backdoor_DoorLock.sendCommand(ON)
if (NightTime.state == ON){
OnByMovement_Staircase.sendCommand(OFF)
StaircaseMovementOn.sendCommand(OFF)
HallwayLight_Brightness.sendCommand(30)
Thread::sleep(10000)
HallwayLight_Power.sendCommand(OFF)
StaircaseMovementOn.sendCommand(ON)
}
}

Now the above is doing all as expected but doesn’t really wait the 10s but just blinking the light and switching it off. Can’t see anything wrong (except for using sleep that I also want to get rid of).
I would really appreciate your help in that matter.
Apologies if any of the info is incomplete.

A limitation of this approach is that conditions are checked only before and after. Anything could happen in between. Depending what you’re doing, you might want an intermediate event to abort or restart this timing cycle. This is possible with the more flexible timers.

“Its the same as the old examples” in general.
You create a timer scheduled for some fixed future datetime, most often given as now+time.

createTimer(now.plusSeconds(2),  [  |
   Frontdoor_DoorLock.sendCommand(ON)
] )

The block of code in is set up for future execution, completely independent of the rule that spawned it.
Important to understand that having created this timer, the rule carries on without delay to the next line. Usually the timer block executes long after the creating rule has finished and exited.

Good enough for simple purposes, but there’s a snag. We can’t even tell if that timer is running or finished from a rule, let alone stop it.
Secret is to save the handle or pointer that createTimer() provides for us.

var fred = createTimer(now.plusSeconds(2),  [  |
   Frontdoor_DoorLock.sendCommand(ON)
] )

Now we can use variable fred to communicate with that independent timer. Example -
fred.cancel()
will stop it.

Snag - when a rule exits, temporary variables created inside the rule are lost. fred will be lost, even though the timer is still ticking.

Solution - store the handle somewhere that survives the rule exit, and is still valid the next time this or some other rule runs.
In traditional file-based DSL rules, you’d use a “global variable” which is pre-defined outside of any rules, so as to survive between rules. You’ll see this used in most existing createTimer() examples.

But there’s nowhere “outside of rules” to define global variables when using UI DSL rules entry.
Bum, that’s most of the flexibility of timers wiped away.
In the other rules languages, there are workarounds for this, but not for DSL I think.

Are you wedded to UI rules? Using DSL in UI rules is a sort of worst of both worlds thing. I’d stick to DSL in files,or use another language in UI.

With a ‘global’ timer handle of some kind, you can do more sophisticated timing like - (psuedo code)

when door opens -
   cancel any existing timer
   turn on light
when door closes -
   start timer for auto-off

No matter how much you faff with the door, the last effective timer turns the light off after time X, and never while the door is open.

2 Likes

Thank you @rossko57 !
This is a very clear explanation!
I will try to implement the timers as they are for now, but noted the suggestion about global variables. I am not precious about using DSL to be honest, it is just something I used since the beginning as there was the biggest number of examples available. I would like to stick to UI rules however as I love I can make very quick changes via web interface and it has been recommended. It feels clear that OH is steering away from config files and towards web based interface so I think switching now will make it easier in future.
In terms of other languages in UI created rules, which would you recommend?
Is any of them offering global variables?