Problem Statement
Sometimes you need to run a timer, but want to able to inspect the remaining time. This is not directly possible with either createTimer() or the Expire binding.
Example - I have lighting-on timers that run for different duration depending on the trigger source or time of day. It is useful to have the timer behave “intelligently” - do not apply a new 5-minute request where there is still seven minutes of a previous trigger to run. But do apply it if there is only two minutes left.
Concept
Create a number Item to use as a minutes countdown. Set autoupdate off for this, so its state only gets set by rules, not by Commands.
Link it to the Expire binding (you may need to install that) so that a Command of minus-1 is sent after a minute. -1 is arbritrary really, but easy to understand!
The countdown is managed by a single rule.
- To use the countdown, simply send it a command with the required number of minutes. It acts smart, and only ‘reschedules’ the countdown when the new target runs longer than the current count.
- You can force the countdown to end early by commanding zero.
- You can force the countdown to some new value by commanding it as a negative value, e.g. -10 This overrides the ‘smart’ behaviour, e.g sets it to 10 even if there is still 20 from a previous trigger.
You write into the rule what actions you want it to take upon starting or rescheduling, upon expiry, and upon cancellation (which might not be the same as normal expiry)
Demonstration Items
// example counter
Number myCounter "Minutes counter [%s]" <clock> {expire="1m,command=-1", autoupdate="false"}
Switch testLamp "example light [%s]" <light>
// test buttons for UI
Switch test6 "6 min run" <button> {expire="2s,state=OFF"} // expire makes it like a pushbutton
Switch test3 "3 min run" <button> {expire="2s,state=OFF"}
Switch test2 "force 2 mins" <button> {expire="2s,state=OFF"}
Switch testabort "counter cancel" <button> {expire="2s,state=OFF"}
Demonstration rules
// Rule to manage countdown
rule "Countdown tick"
when
Item myCounter received command
then
var cmmd = (receivedCommand as Number).intValue // integers only
var count = 0
if (myCounter.state != NULL) { // avoid 1st time run error
count = (myCounter.state as Number).intValue
}
if (cmmd == -1 && count > 0) { // decrement counter, do not go below zero
if (count == 1) {
// do actions for counter expiry, as we about to zero it now
testLamp.sendCommand(OFF)
}
myCounter.postUpdate(count - 1)
} else if (cmmd >= count || cmmd < -1) { // new or refreshed target
if (cmmd < -1) { // force override
cmmd = 0 - cmmd // make it positive
}
myCounter.postUpdate(cmmd) // nb we still update even if equal value - resets expire binding
// do startup/continue actions
if (testLamp.state != ON) {
testLamp.sendCommand(ON)
}
} else if (cmmd == 0) { // cancel countdown
myCounter.postUpdate(0)
// do optional cancel actions
testLamp.sendCommand(OFF)
}
end
// rules just for test simulation buttons
rule "test 6 mins"
when
Item test6 received command
then
myCounter.sendCommand(6)
end
rule "test 3 mins"
when
Item test3 received command
then
myCounter.sendCommand(3)
end
rule "test force"
when
Item test2 received command
then
myCounter.sendCommand(-2)
end
rule "test stoppit"
when
Item testabort received command
then
myCounter.sendCommand(0)
end
Demo sitemap
Frame label="Countdown testing" {
Text item=myCounter
Text item=testLamp
Switch item=test3
Switch item=test6
Switch item=test2
Switch item=testabort
}
Inspired by @rikoshak post on one-shot timers