Problem Statement
One often needs to schedule some code to execute at some time in the future. The “usual” way to achieve this is by using a Timer which lets you create and schedule a lambda to execute at a given date and time. However, Timers have their drawbacks including:
- the need to keep track of open Timers so they can be canceled at a later date, often requiring the use of HashMaps or some other data structure
- rescheduling or creating a cascade of Timers is challenging (i.e. when a timer expires it kicks off another copy of itself or another Timer to do something else
- a lot of boiler plate code is required to check for whether a timer is running, canceling it, rescheduling it, etc.; in some cases this boiler plate code will dominate the other “functional” code in a Rule
Concept
Create a special Switch Item bound to the Expire binding to drive your Timer. Configure the Expire binding with command=OFF
so when the timer expires it generates an OFF command.
To start the Timer sendCommand(ON) or postUpdate(ON) to the Timer Item.
To cancel the Timer postUpdate(OFF) to the Timer Item.
Place the body of the code that you want to execute when the Timer expires in a rule triggered by received command OFF.
To check to see if the Timer is still running check to see if the Timer’s state is ON.
Simple Example
Items
Switch MyTimer { expire="5m,command=OFF" }
JSR223 Python
from core.rules import rule
from core.triggers import when
@rule("Some Rule that starts MyTimer")
@when("<some tigger>")
def some_rule(event):
# do some work
# Timer running?
if items["MyTimer"] == ON:
# do stuff if Timer is actively running
# cancel Timer
events.postUpdate("MyTimer", OFF)
# start Timer
events.sendCommand("MyTimer", ON)
# do some stuff
@rule("MyTimer expired")
@when("Item MyTimer received command OFF")
def mytimer_expired(event):
# Timer body
Rules DSL
rule "Some Rule that Starts MyTimer"
when
// some trigger
then
// do some work
// Timer running?
if(MyTimer.state == ON) {
// do stuff if Timer is actively running
}
// cancel Timer
MyTimer.postUpdate(OFF)
// start Timer
MyTimer.sendCommand(ON)
// do some stuff
end
rule "MyTimer expired"
when
Item MyTimer received command OFF
then
// Timer body
end
Comprehensive Example
See the Cascading Timers Design Pattern
Bonus, reschedule at startup
Unlike normal Timers, it is very easy to restart these Timers on an OH restart or Rules reload which is significantly more difficult with traditional Timers.
Items
Create a Group and add all your Expire Binding Timer Items to this Group.
Group:Switch gResetExpire
JSR223 Python
from core.rules import rule
from core.triggers import when
@rule("Reset Expire Binding Timers", description="Sends an ON command to all members of gResetExpire if they were restoreOnStartup to ON", tags=["admin"])
@when("System started")
def reset_expire(event):
reset_expire.log.info("Restarting Expire binding Timers")
for timer in ir.getItem("gResetExpire").members:
events.sendCommand(timer, ON)
Rules DSL
rule "Reset Expire Binding Timers"
when
System started
then
gResetExpire.members.forEach[ timer | timer.sendCommand(timer.state) ]
end
Advantages and Limitations
Advantages:
- The code involved when using the Expire binding is much simpler and easier to follow than Timers.
- It is easy to determine whether a Timer was active when OH comes back through restoreOnStartup (I don’t think restoreOnStartup actually restarts the Expire binding though).
- If all you are using the Timer for is to reset the state of an Item, you can just use the Expire binding on that Item and avoid rules entirely.
Disadvantages:
- The amount of time applied to the Timer is hard coded. You cannot calculate the length of time in the future that the Timer needs to execute programmatically in a Rule
- It requires installation of the Expire binding.
- It requires the creation of more Items
Related Design Patterns
- Cascading Timers
- Motion Sensor Timer
- A Sate Machine Primer: Note this code uses traditional Timers but could be implemented using Expire Timers
- Generic Is Alive
- Recursive Timers
- Event Limit: Expire binding could be used as the check to limit when the next event can be sent
- Separation of Behaviors: Use this design pattern to name and group your Timers and you will be able to get the timer assocaited with another Item wihtout maintaining a global HashMap