Design Pattern: Expire Binding Based Timers

designpattern
Tags: #<Tag:0x00007fd30f92a1f0>

(Rich Koshak) #1

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" }
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

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.

Related Design Patterns


Trrying to turn a device off after a certain amount of time
Increasing light over a given time period
Rules stop firing after a short while, stack trace in logs
Timer's and OH2 Restarts
Cancel timer doesn`t work
Cannot get rule to Cancel Timer
Yet another WakeUp Light Rule
Action on DateTime item age
Rule, that check if last update is older than 5 Minutes
Group rule stopped triggering. (Snapshot #1220 and back a week or so)
How to wait in a rule?
Rule optimization: Window OPEN reminder
Simplifying Rule
Resetting timers
Timers and cancel 2 doorlocks
Making Decisions Based on Time of Day
Using Timer Class in Rule Making
ReentrantLock with Timer
Timed lights, Alerts to push notification?
Very simple rule executes slow
Send command with .map with MQTT
Taming a flapping sensor
Yet another Timer question
Eliminating lingering timers
Design Pattern: Proxy Item
Openhab 2 Contact Sensor Time Rule
How to create a complex light status check rule?
Declaring Lambdas as Parameters in a Lambda (newest syntax)
Error when resetting timer
Design Pattern: Sensor Aggregation
Design Pattern: Manual Trigger Detection
Design Pattern: Working with Groups in Rules
Cron expression in if statement
Design Pattern: Motion Sensor Timer
[SOLVED] Timer off delay for PowerView blinds
Lambda in rule fails with 'Error during the execution of rule '{RuleName}': null'
Help With Hue Rules
Help to create a rule "turn on a lamp when motion is detected but only after sunset until sunrise"
Stop timer and reset to null. Structure of rule okay?
Is it possible to force a LUX sensor to update?
(Dan) #2

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


(bob_dickenson) #3

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

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

(Rich Koshak) #4

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


(Thomas Binder) #5

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"


(Rossko57) #6

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


(Rich Koshak) #7

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.


(Thomas Binder) #8

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


(Ahmad Yazan Tibi) #9

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) ?


(Rich Koshak) #10

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.