To control a bunch of our lights and sockets to switch ON/OFF at a defined time I have found a simple solution which can control as much as lights/sockets I need with one simple rule.
My requirements are simple:
- control lights by time and time events like sunset, sunrise
- support different devices (I have Homematic and Sonoff devices), can be basically all which Switch items
- support multiple schedules for a device per day, e.g. ON 4am, OFF sunrise & ON 7pm, OFF 11pm
- schedule can be changed without coding aka. without change items or .rules file
Particularly important the last one because I have different modes and schedules for the outdoor devices in summer and winter.
A quick search in the OpenHab community already revealed a few interesting approaches. But these were all too complex and integrated addition triggers like contact or motion sensors.
My old approach was based on number items holding the on/off times and a ton of setpoint controls in the sitemap. But this got cumbersome with every new device and every new schedule.
So here is my approach: For each switchable item I want to control I have one additional String item which stores the schedule.
Devices:
Switch Switch_Outdoor_Pond_1 "Teich Luft" <flow> (gHouse,gPond,gSwitches) [ "Switchable" ] {channel="homematic:HG-HM-LC-Sw1-DR:ccu:xxxxxx:1#STATE"}
Switch Switch_Outdoor_Pond_2 "Teich Wasserfall" <flow> (gHouse,gPond,gSwitches) [ "Switchable" ] {channel="homematic:HG-HM-LC-Sw1-DR:ccu:xxxxxx:1#STATE"}
Switch Switch_Outdoor_Terrace_Light "Terrassenlicht" <light> (gOutdoor,gHouse,gSwitches) [ "Lighting" ] {mqtt=">[mosquitto:cmnd/sonoff-terrassenlicht/power:command:*:default],<[mosquitto:stat/sonoff-terrassenlicht/POWER1:state:default]"}
Switch Switch_Outdoor_Terrace_Lounge "Terrassenlounge" <light> (gOutdoor,gHouse,gSwitches) [ "Lighting" ] {mqtt=">[mosquitto:cmnd/sonoff-terrassenlounge/power:command:*:default],<[mosquitto:stat/sonoff-terrassenlounge/POWER1:state:default]"}
Automation rules:
String Auto_Switch_Outdoor_Pond_1 "Teichprogram Luft [%s]" <flow> (gAutomatic) {mqtt="<[mosquitto:openhab/config/SwitchOutdoorPond1:state:default]"}
String Auto_Switch_Outdoor_Pond_2 "Teichprogram Wasserfall [%s]" <flow> (gAutomatic) {mqtt="<[mosquitto:openhab/config/SwitchOutdoorPond2:state:default]"}
String Auto_Switch_Outdoor_Terrace_Light "Teressaenlichtprogramm [%s]" <light> (gAutomatic) {mqtt="<[mosquitto:openhab/config/SwitchOutdoorTerraceLight:state:default]"}
String Auto_Switch_Outdoor_Terrace_Lounge "Lounge Lichtprogramm [%s]" <light> (gAutomatic) {mqtt="<[mosquitto:openhab/config/SwitchOutdoorTerraceLounge:state:default]"}
The pattern for the schedules is as follows:
- multiple schedules are separated by |
- a schedule matches the <on>-<off> pattern
- either <on> or <off> is required, the second is optional
- SUN_RISE & SUN_SET are supported as well
I cannot change the schedule via Basic UI directly, since there is no input option for that. For some schedules I change often I have configured a “Selection” in the sitemap with all used options. For example home.sitemap snippet:
Selection item=Auto_Switch_Outdoor_Tree_Light label="Pflaumenbaum" mappings=[""="aus", "SUN_SET-20"="bis 20Uhr", "SUN_SET-21"="bis 21Uhr", "SUN_SET-22"="bis 22Uhr", "SUN_SET-23"="bis 23Uhr", "SUN_SET-0"="bis 24Uhr"]
Selection item=Auto_Switch_Outdoor_Terrace_Light label="Terassenlicht" mappings=[""="aus", "-20"="bis 20Uhr", "-21"="bis 21Uhr", "-22"="bis 22Uhr", "-23"="bis 23Uhr", "-0"="bis 24Uhr"]
Examples:
- 8-10 = ON at 8am, OFF at 10am
- -23 = no automatic on, but if on the OFF at 11pm
- 4-SUN_RISE = ON at 4am, OFF at sunrise
All schedules also can simply be updated via MQTT message, which is totally enough since I not change them that often.
The heart of the control login is in one generic rule:
rule "Automatic program timer"
when
Time cron "0 0 0/1 * * ?"
then
// iterate all automatic programs we have in gAutomatic group
gAutomatic.members.forEach(program| {
// get switch by program name from gSwitches
val switchName = program.name.substring(5, program.name.length)
val switchItem = gSwitches.members.findFirst[SwitchItem ctrl | ctrl.name == switchName ]
if (null === switchItem) {
logInfo(filename, "No switch found for " + program.name)
return
}
// get all schedules
logInfo(filename, "Schdeule for " + switchItem.name + ": " + program.state.toString)
val programsForSwitch = program.state.toString.split("\\|")
programsForSwitch.forEach(item | {
var schedule = item
if (item.startsWith("-")) schedule = "x" + item // x is placeholder for do nothing
if (item.endsWith("-")) schedule = item + "x"
// get ON & OFF time for schedule
val schedulesForSwitch = schedule.split("-")
if (schedulesForSwitch.length != 2) {
logInfo(filename, "Schedule " + schedule + " for " + switchItem.name + " is not valid, must be <on>-<off>")
return
}
// switch ON if not already
if (schedulesForSwitch.get(0) == now.getHourOfDay.toString && switchItem.state == OFF) {
logInfo(filename, "Change switch " + switchItem.name + " to ON (time)")
switchItem.sendCommand(ON)
}
// switch OFF if not already
if (schedulesForSwitch.get(1) == now.getHourOfDay.toString && switchItem.state == ON) {
logInfo(filename, "Change switch " + switchItem.name + " to OFF (time)")
switchItem.sendCommand(OFF)
}
})
})
end
The rule is triggered by cron job every full hour. It gets all schedules for the member of the gAutomatic group, finds the corresponding switchable item and evaluates the configured schedule. I have a second rule which does the same but triggered by sunrise/sunset events:
rule "Automatic program sun"
when
Channel 'astro:sun:home:rise#event' triggered START or
Channel 'astro:sun:home:set#event' triggered START
then
Thread::sleep(5000) // make sure Day_Phase was already updated
// iterate all automatic programs we have in gAutomatic group
gAutomatic.members.filter[program|program.state.toString.contains("SUN")].forEach(program| {
// get switch by program name from gSwitches
val switchName = program.name.substring(5, program.name.length)
val switchItem = gSwitches.members.findFirst[SwitchItem ctrl | ctrl.name == switchName ]
if (null === switchItem) {
logInfo(filename, "No switch found for " + program.name)
return
}
// get all schedules
logInfo(filename, "Schdeule for " + switchItem.name + ": " + program.state.toString)
val programsForSwitch = program.state.toString.split("\\|")
programsForSwitch.forEach(item | {
var schedule = item
if (item.startsWith("-")) schedule = "x" + item // x is placeholder for do nothing
if (item.endsWith("-")) schedule = item + "x"
// get ON & OFF time for schedule
val schedulesForSwitch = schedule.split("-")
if (schedulesForSwitch.length != 2) {
logInfo(filename, "Schedule " + schedule + " for " + switchItem.name + " is not valid, must be <on>-<off>")
return
}
// switch ON if not already
if (switchItem.state == OFF && schedulesForSwitch.get(0) == Day_Phase.state.toString) {
logInfo(filename, "Change switch " + switchItem.name + " to ON (" + Day_Phase.state.toString + ")")
switchItem.sendCommand(ON)
}
// switch OFF if not already
if (switchItem.state == ON && schedulesForSwitch.get(1) == Day_Phase.state.toString) {
logInfo(filename, "Change switch " + switchItem.name + " to OFF (" + Day_Phase.state.toString + ")")
switchItem.sendCommand(OFF)
}
})
})
end
I have this running for one week now, currently I have 10 devices controlled with this pattern. In a few weeks when the Christmas season starts I will add a lot more for all the Christmas lights.
Let me know thoughts ![]()