Some time ago I published post how to configure auto off timer for lights. There was concept of switching off after time which I set up in Sitemap. After digging through community forum I found post about:
Design Pattern : Expire Binding based Countdown timer
It required expire binding and instead of setting separate local variable for timer it uses switch Item with expire extension to count down our time.
It is worth to start from the example on the website to have better understanding how it works. Later we may add additional elements such as global Settings option with time, motion detector, additional conditions for light sensor etc.
Basic setup
Basic pre-setup – add expire binding – either in PaperUI or in addons.cfg add expire1:
1 2 |
# A comma-separated st of bindings to install (e.g. "binding = sonos,knx,zwave") binding = http1,ntp,network,mqtt,exec,openweathermap,weather1,amazonechocontrol,expire1,mihome,bluetooth |
timer.items
1 2 3 4 5 6 7 8 9 10 11 |
// example counter Number myCounter "Minutes counter [%s]" {expire="1m,command=-1", autoupdate="false"} Switch testLamp "example light [%s]" // test buttons for UI Switch test6 "6 min run" {expire="2s,state=OFF"} // expire makes it like a pushbutton Switch test3 "3 min run" {expire="2s,state=OFF"} Switch test2 "force 2 mins" {expire="2s,state=OFF"} Switch testabort "counter cancel" {expire="2s,state=OFF"} |
timer.sitemap
1 2 3 4 5 6 7 8 |
Frame label="Countdown testing" { Text item=myCounter Text item=testLamp Switch item=test3 Switch item=test6 Switch item=test2 Switch item=testabort } |
and finally rule in timer.rule:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// 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 |
Links to other useful concepts with examples:
Design Pattern: Expire Binding Based Timers
Design Pattern: Motion Sensor Timer
Advanced setup
Here is my setup. I will try to simplify and remove unnecessarily elements. It is important to implement previous basic sample to get good understanding what is going on. My example uses elements from previous post. Could be also useful to get better understanding.
Settings
default.sitemap
1 2 3 4 5 6 7 |
Frame label="Settings" { Text label="Auto OFF timers" icon="time"{ Setpoint item=myTimer_kitchen step=5 minValue=0 maxValue=240 Setpoint item=myTimer_bathroom step=5 minValue=0 maxValue=240 Setpoint item=myTimer_bedroom step=5 minValue=0 maxValue=240 } Group item=Settings } |
My kitchen controls – in the middle timer how much time left, and one below group of all lights.
default.sitemap
1 2 3 4 5 6 |
//Kuchnia Text label="Kuchnia" icon="kitchen" { Text item=GF_Sonoff_RF_Door_1 label="Lodowka [%s]" Text item=myCounter_kitchen Group item=GF_Kitchen_Ceiling_Lights label="Kuchnia" } |
In further files I use only the one for kitchen as there are multiple elements like motion detector, switches, timers, light sensor. I do not put things file as any of you can have different type and technology for this sensors while items could be the same.
timer.items
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//Some Groups Group GF_Kitchen "Kitchen" (gDashboard) Group GF_Kitchen_Ceiling_Lights (GF_Kitchen,GF_Lights) //Light switches Switch sonoffwall5_p2 "Stolik" (GF_Kitchen_Ceiling_Lights) { channel="mqtt:topic:sonoffbridge:wall5-p2" } Switch sonoffwall5_p1 "Okap" (GF_Kitchen_Ceiling_Lights) { channel="mqtt:topic:sonoffbridge:wall5-p1" } //PIR Contact digoo1pir "PIR Kuchnia" { channel="mqtt:topic:sonoffbridge:digoopir1" } //auto off timers Number myTimer_kitchen "Kuchnia OFF [%d]" // no binding needed //timers Number myCounter_kitchen "Pozostało min do wyłączenia [%s]" {expire="1m,command=-1",autoupdate="false"} //light sensor Number pogoda_lux "Natężenie światła [%.0f lx]" (gDashboard,gGraph_Light) {channel="mqtt:topic:mything:pogodynka_lux" } |
timer.rules
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
// Rule to manage countdown rule "Countdown tick - kitchen both lamps" when Item myCounter_kitchen received command then var cmmd = (receivedCommand as Number).intValue // integers only var count = 0 if (myCounter_kitchen.state != NULL) { // avoid 1st time run error count = (myCounter_kitchen.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 sonoffwall5_p1.sendCommand(OFF) sonoffwall5_p2.sendCommand(OFF) } myCounter_kitchen.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_kitchen.postUpdate(cmmd) // nb we still update even if equal value - resets expire binding // do startup/continue actions //if (testLamp.state != ON) { // sonoffwall5_p1.sendCommand(ON) // sonoffwall5_p2.sendCommand(OFF) //} } else if (cmmd == 0) { // cancel countdown myCounter_kitchen.postUpdate(0) // do optional cancel actions sonoffwall5_p1.sendCommand(OFF) sonoffwall5_p2.sendCommand(OFF) } end rule "Motion detector reset timer only when any of the lamps is on" when Item digoo1pir received update then if ( sonoffwall5_p2.state == ON ) { myCounter_kitchen.sendCommand((myTimer_kitchen.state as DecimalType).intValue) } else if (sonoffwall5_p1.state == ON ) { myCounter_kitchen.sendCommand((myTimer_kitchen.state as DecimalType).intValue) } end rule "lamp1 is on" when Item sonoffwall5_p1 changed from OFF to ON then myCounter_kitchen.sendCommand((myTimer_kitchen.state as DecimalType).intValue) end rule "lamp2 is on" when Item sonoffwall5_p2 changed from OFF to ON then myCounter_kitchen.sendCommand((myTimer_kitchen.state as DecimalType).intValue) end //reset timer to 1 as motion sensor could reset again if somebody is inside rule "any kitchen lamp turn off rest timer to 1m" when Item sonoffwall5_p2 changed from ON to OFF or Item sonoffwall5_p1 changed from ON to OFF then myCounter_kitchen.postUpdate(1) end rule "Motion detector turn on light when is dark outside" when // Member of GF_Kitchen_Ceiling_Lights received command Item digoo1pir received update then if (pogoda_lux.state < 350 && sonoffwall5_p1.state == OFF){ sonoffwall5_p1.sendCommand(ON) } end |
What happens? If light is below 350 lux then once PIR detects move turn on one of the lights sonoffwall5_p1. Turning on any of the lights set countdown timer myCounter_kitchen to the values set in settings to myTimer_kitchen. If any of lights are ON and PIR detects move also reset myCounter_kitchen to myTimer_kitchen value. Due to expire1 binding and setting counter counts down every minute.
Advanced setup, have errors the log says ?
“no viable alternative at input ‘&'”
can you please change or just make it workj ? i love the ider ,but i can`t code myself !
Sorry for that. Ia have pain to find out good code snippet parser for wordpress. It replace & and < > with some other tags.
Please let me know which exact code does not work, I will send you over email.
// 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