Design Pattern: Expire Binding Based Timers

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

21 Likes

This is great - thank you. Looks much more elegant than timers.

2 Likes

N.B. The actual syntax for an item using this binding is:

Switch MyTimer { expire="5m,command=OFF" }   //the 'expire=' is OUTSIDE the quotes
1 Like

Thanks. It’s so ready to miss stops like that when typing coffee on the phone.

1 Like

is there a reason why this one won’t work?

Switch MyTimer { expire="5m,command=ON" }

…and by the way. if it is a Proxy Item (like here), what’s the difference between expire="5m,command=ON" and expire="5m,state=ON"

Very little in this case, but it might or might not trigger other rules depending upon what they trigger on.

If you also had autoupdate=false, it would behave differently.

If you had a ‘real’ binding linkage(s), command would trigger that/them but state would not.

This does allow you to set up subtle controls

1 Like

I don’t see any reason why that won’t work but would need a lot more context to say so definitively. I’ve nothing to add to rossko57’s reply. He hit the nail on the head. command=ON is the equivalent to MyItem.sendCommand(ON) and state=ON is the equivalent to MyItem.postUpdate(ON) which implies all the same behaviors and caveats one has with updates and commands from any other binding.

One thing that is hard to get across in a DP like this is that when it is appropriate to use state versus command versus nothing because it is very context dependent.

In the context of the examples above, we use an OFF command to indicate that the Timer has expired. So to set the Timer we sendCommand(ON) which starts the expire binding running. A postUpdate(ON) would work just as well. To cancel the timer we purposefully use postUpdate(OFF) because we don’t want to trigger the “MyTimer expired” Rule. When the expire binding triggers it sendCommand(OFF) which does trigger the “MyTimer expired” Rule and the code that needs to run when the timer expires runs.

If you change the command to ON from the expire binding like you propose, you will need to also swap everything in the rule. So where you see an ON you would need to change it to OFF and the same for OFF or else the code won’t work like a Timer.

1 Like

Thanks, I thought so. I had to restart and now it works…

Thanks for this informative explanation, I have few questions please:

  • To run code when timer expires, we can use (MyTimer received command OFF) or (MyTimer changed), so what is the difference ?

  • Also when using (MyItem changed from OFF to ON), initially it doesn’t work because the item changed from null to on, so how can resolve this ?

  • Which is better: to trigger code using (when clause of rule), or (if statement within the rule) ?

Depends on the context and what the Expire timer is configured to do.

In the above, the Expire Binding is configure to send an OFF command to the Item when it expires. So to trigger a Rule only when the timer expires you would use Item MyTimer received command OFF.

Item MyTimer changed will trigger anytime MyTimer changes for any reason. So the Rule will trigger when the timer is started and MyTimer changes to ON and it will trigger when MyTimer changes to OFF. Notice this will capture the change caused for any reason. So if you use call MyTimer.postUpdate(OFF) and MyTimer isn’t already OFF then the Rule will trigger if you use changed but it will not trigger if you use received command OFF.

This was a deliberate design choice. In this case we don’t want to trigger the Rule when the Item changes from NULL to ON.

If you do want to trigger the rule then you can use Item MyItem changed to ON or Item MyItem received command ON.

All of the different rule triggers exist for a reason and they all have their place. You need to make sure you understand exactly what events that your system generates and choose the appropriate rule trigger for that case.

The question doesn’t make sense. Your two options have two completely different purposes.

Rules are triggered by events. If any one of the events in the Trigger occurs, the Rule will be run. This is why there is no such thing as an and in rule triggers. No two events will ever take place at exactly the same time so it makes no sense to have an and in a Rule trigger.

Once you are in the rule you know one of the events took place where you might need to take some action. So here is where you have your if statements to check the states of one or more Items or variables to determine if you need to take an action and what action you need to take.

It’s not an either or. You need both.

Is it possible to pass a variable to the expire function?

Number ExpireMinutesOffice
Switch Light_FF_Office_Ceiling      "Office"           <light>    (FF_Office, Lights, G_PresenceSimulation)      {mqtt=">[mqttbroker:/bo19/sw005:command:ON:1],>[mqttbroker:/bo19/sw005:command:OFF:0],<[mqttbroker:/bo19/sw005_status:state:ON:1],<[mqttbroker:/bo19/sw005_status:state:OFF:0]", expire= ExpireMinutesOffice.state.toString() + "m,command=OFF"}
Setpoint item=ExpireMinutesOffice label="Light switch off [%.1f]" icon="time" minValue=0 maxValue=100 step=1

I am getting the following error message:
mismatched input 'ExpireMinutesOffice' expecting RULE_STRING

Any idea?

No. If you need variable times you must use a Timer in a Rule.

1 Like

Great Stuff!

Easily applied and understood thanks to your design pattern!

can I add this binding in the JSON file?
Whats the syntex, if yes?

“Stue_lys”: {
“class”: “org.eclipse.smarthome.core.items.ManagedItemProvider$PersistedItem”,
“value”: {
“groupNames”: [],
“itemType”: “Switch”,
“tags”: [],
“label”: “Lys i stuen”
}

No, you cannot. It’s a 1.x version binding so .items files must be used. In OH 3 some other mechanism will be created that will implement the Expire Binding feature.

Hello Rich,

Your post is very helpful. Thank you very much.

I am very new to Openhab, and was wondering if you could clarify what you meant by

So to trigger a Rule only when the timer expires you would use Item MyTimer received command OFF .

I have used your code snippet and would like to use that to create an alert when a door is left open. The first part of my code creates a ding-dong song (to keep my 3 year old from walking out by himself).

I have seen your other post on using groups to reduce the lines of code, but to be honest it’s over my head. And honestly, I’m still trying to figure out this whole OpenHab thing.

so in items, I have created the following switch
Switch FrontDoorOpen_Timer { expire="5m,command=OFF" }

And in my Rules folder, I have:

rule “front_door_open”
when
Item zwave_device_08f074d1_node6_sensor_door changed from CLOSED to OPEN
then
playSound(“chromecast:chromecast:ead9e7276c37c0aaaf5a2c261342d6da”,“Apartment-ding-dong-sound.mp3”)
playSound(“chromecast:chromecast:ecd97d69f28d9f1c0173a27b8c48df78”,“Apartment-ding-dong-sound.mp3”)
Thread::sleep(250)
say(“front door”)

if(FrontDoor_Timer.state == ON) {
    // do stuff if Timer is actively running
}

// cancel Timer
FrontDoor_Timer.postUpdate(OFF)

// start Timer
FrontDoor_Timer.sendCommand(ON)

// do some stuff

end

rule “Door Open timer expired”
when
Item FrontDoor_Timer received command OFF
then
// Timer body
say(“the front door has been left open for 5 minutes”)
end

The ding-dong works, but for the life of my I can’t figure out how to change this to only give the alert when the door remains open. Any help would be super appreciated!

You’d want to “cancel” the expire timer by setting the timing Item to OFF without using a command.
Make another rule, triggered by the door changing to closed, that issues
FrontDoor_Timer.postUpdate(OFF)
Don’t be afraid to have many small rules for each task.

Setting the timer Item to the same state as the “target action” cancels the expire effect; using postUpdate instead of sending a command avoid triggering your alerting rule.

I am on latest openhab snapshot and I get following error every few seconds in the logs:
[ERROR] [core.karaf.internal.FeatureInstaller] - Failed installing ‘openhab-binding-expire’

Within paperui the expire binding shows as installed but is not functioning (switch never expires)

If I remember correctly the expire binding was within the action addon section of the paperui and is now in the binding section. Could this error be related to this move?

NoTechi

I’m pretty positive that is not the case. Expire has always been a binding. It doesn’t work like an Action (i.e. you call it from a Rule) so it doesn’t belong as an Action.

First thing to try, assuming you already tried restarting OH is to Clear the Cache and try again.

OK then I have bad memories … old man you know :wink:

I just restarted OH and cleared the cache but I get the same error in the logfiles after installing Expire via paperui after the restart.