RLK back with another Design Pattern to add to the list.
Time Of Day Triggers
The Problem:
Many of us have rules that we want to only execute or behave differently during certain time periods of the day. As our rules grow there is the potential that more and more parts of the system will need to have this capability (e.g. lighting, HVAC, blinds, etc). Sometimes the times that define a period are fixed (e.g. 11pm) and others they are based on celestial events (e.g. sunset). This can result in a scattering of cron and Astro triggers throughout your rules which would need to be updated should you decide to change the time things happen.
The Solution:
Create a set of switches that get turned ON at the start of a time period and OFF at the end of the time period. Then in your rules which care about the time of day check the state of these switches.
I’ve expanded my lighting rule from above to illustrate this approach.
Items:
// Light Switches and Groups
Group:Switch:OR(OFF,ON) gLights "All Lights" <light>
Group gMorningLights "Lights that turn on before dawn, off at dawn" <light>
Group gWeatherLights "Lights controlled by weather conditions and twilight" <light>
Group gOffTimerLights "Off Timer Lights" <light>
Group gSunsetTimerLights "Sunset On Lights" <light>
Switch S_L_Front "Front Room Lamp" <light> (gLights, gOffTimerLights, gWeatherLights, gMorningLights) {zwave="3:command=switch_binary"}
Switch S_L_Family "Family Room Lamp" <light> (gLights, gOffTimerLights, gWeatherLights) {zwave="4:command=switch_binary"}
Switch S_L_Porch "Front Porch" <light> (gLights, gOffTimerLights, gSunsetTimerLights) {zwave="6:command=switch_binary"}
Switch S_L_All "All Lights" <light>
// Time and Weather Items
Switch Day
Switch Twilight
Switch Night
Switch Morning
Switch Twilight_Event (Weather) { astro="planet=sun, type=set, property=start, offset=-90" }
DateTime Twilight_Time "Twilight [%1$tr]" <moon> (Weather) { astro="planet=sun, type=set, property=start, offset=-90" }
Switch Sunset_Event (Weather) { astro="planet=sun, type=set, property=start" }
DateTime Sunset_Time "Sunset [%1$tr]" <moon> (Weather) { astro="planet=sun, type=set, property=start" }
Switch Sunrise_Event (Weather) { astro="planet=sun, type=rise, property=start" }
DateTime Sunrise_Time "Sunrise [%1$tr]" <sun> (Weather) { astro="planet=sun, type=rise, property=start" }
String Condition_Id "Weather is [MAP(yahoo_weather_code.map):%s]" (Weather) { weather="locationId=home, type=condition, property=id" }
Rules:
import org.openhab.core.types.*
import org.openhab.core.library.items.*
import org.eclipse.xtext.xbase.lib.*
import java.util.Map
import java.util.Set
import org.joda.time.*
//----------------Lighting Rules-------------------------
//-------------------------
// Global Variables
//-------------------------
val String TIMER = "TIMER"
val String WEATHER = "WEATHER"
val String MANUAL = "MANUAL"
val Map<SwitchItem, Boolean> overridden = newHashMap
var String whoCalled = ""
// Yahoo cloudy weather condition IDs
val Set<String> cloudyIds = newImmutableSet("0", "1", "2", "3", "4", "5", "6", "7", "8",
"9", "10", "11", "12", "13", "14", "15", "16", "17",
"18", "19", "20", "26", "28", "35", "41", "43", "45",
"46", "30", "38")
val Functions$Function4 applySwitch = [ State state,
boolean override,
String whoCalled,
SwitchItem light |
if(state != light.state) {
if(!override) {
logInfo("Lights", whoCalled + " turning " + light.name + " " + state.toString)
sendCommand(light, state.toString)
}
else {
logInfo("Lights", whoCalled + " " + light.name + " is overridden")
}
}
]
rule "Lights System Startup"
when
System started
then
whoCalled = MANUAL
gLights?.members.forEach[light | overridden.put(light as SwitchItem, false) ]
end
//-----------------------------------------------------------------------------
// TODO See if I can just use gLights for switching all on and off
rule "Any light in gLight triggered"
when
Item gLights received update
then
Thread::sleep(250) // give lastUpdate time to be populated
val mostRecent = gLights.members.sortBy[lastUpdate].last as SwitchItem
logDebug("Lights", "Most recent is " + mostRecent.name)
if(whoCalled == MANUAL) {
logInfo("Lights", "Overriding " + mostRecent.name)
overridden.put(mostRecent, true)
}
// Keep S_L_All up to date
if(gLights.members.filter(l|l.state==ON).size > 0) S_L_All.postUpdate(ON)
else S_L_All.postUpdate(OFF)
end
//-----------------------------------------------------------------------------
// The Any gLight triggered will be called for each of items in gLights, causing
// them to become overridden
rule "All Lights"
when
Item S_L_All received command
then
gLights.members.forEach(light | sendCommand(light, S_L_All.state.toString))
end
rule "Lights Morning ON"
when
Item Morning changed from OFF to ON
then
logInfo("Lights", "Timer turning on Morning lights.")
whoCalled = TIMER
gMorningLights.members.forEach[light |
overridden.put(light as SwitchItem, false)
applySwitch.apply(ON, false, TIMER, light)
]
end
rule "Light Morning OFF"
when
Item Morning changed from ON to OFF
then
logInfo("Lights", "Timer turning off Morning lights.")
whoCalled = TIMER
gMorningLights.members.forEach[light |
overridden.put(light as SwitchItem, false)
applySwitch.apply(OFF, false, TIMER, light)
]
end
rule "Lights Twilight"
when
Item Twilight changed from OFF to ON
then
logInfo("Lights", "Timer turning Twlight lights.")
whoCalled = TIMER
gWeatherLights.members.forEach[light |
overridden.put(light as SwitchItem, false)
applySwitch.apply(ON, false, TIMER, light)
]
end
rule "Lights Sunset"
when
Item Sunset_Event received update
then
logInfo("Lights", "Timer turning on Sunset lights.")
whoCalled = TIMER
gSunsetTimerLights.members.forEach[light |
overridden.put(light as SwitchItem, false)
applySwitch.apply(ON, false, TIMER, light)
]
end
rule "Lights Night"
when
Item Night changed from OFF to ON
then
logInfo("Lights", "Timer turning off bedtime lights.")
whoCalled = TIMER
gOffTimerLights.members.forEach[light |
overridden.put(light as SwitchItem, false)
applySwitch.apply(OFF, false, TIMER, light)
]
end
rule "Lights Sunrise"
when
Item Day changed from OFF to ON
then
logInfo("Lights", "Good morning, activating Weather Lights rule")
whoCalled = MANUAL
gLights.members.forEach[light |
logDebug("Lights", "Populating overridden map with " + light.name)
overridden.put(light as SwitchItem, false)
]
end
rule "Weather Lights On"
when
Item Condition_Id changed
then
if(Day.state == ON) {
logDebug("Lights", "Checking the weather conditions")
var State state = OFF
if(cloudyIds.contains(Condition_Id.state)) state = ON
logInfo("Lights", "Setting weather lights to " + state)
whoCalled = WEATHER
val i = gWeatherLights?.members.iterator
while(i.hasNext) {
val light = i.next
logDebug("Lights", "Processing " + light.name)
if(overridden.get(light) == null) overridden.put(light as SwitchItem, false)
applySwitch.apply(state, overridden.get(light).booleanValue, WEATHER, light)
}
Thread::sleep(500)
whoCalled = MANUAL
}
end
//----------------Time of Day Rules-------------------------
rule "Get time period for right now"
when
System started
then
val morning = now.withTimeAtStartOfDay.plusHours(6).millis
val sunrise = new DateTime((Sunrise_Time.state as DateTimeType).calendar.timeInMillis)
val twilight = new DateTime((Twilight_Time.state as DateTimeType).calendar.timeInMillis)
val night = now.withTimeAtStartOfDay.plusHours(23).millis
if(now.isAfter(morning) && now.isBefore(sunrise)) {
logInfo("Weather", "Initializing, it is Morning")
Morning.sendCommand(ON)
Day.sendCommand(OFF)
Twilight.sendCommand(OFF)
Night.sendCommand(OFF)
}
else if(now.isAfter(sunrise) && now.isBefore(twilight)) {
logInfo("Weather", "Initializing, it is Day")
Morning.sendCommand(OFF)
Day.sendCommand(ON)
Twilight.sendCommand(OFF)
Night.sendCommand(OFF)
}
else if(now.isAfter(twilight) && now.isBefore(night)) {
logInfo("Weather", "Initializing, it is Twilight")
Morning.sendCommand(OFF)
Day.sendCommand(OFF)
Twilight.sendCommand(ON)
Night.sendCommand(OFF)
}
else {
logInfo("Weather", "Initializing, it is Night")
Morning.sendCommand(OFF)
Day.sendCommand(OFF)
Twilight.sendCommand(OFF)
Night.sendCommand(ON)
}
end
rule "Morning start"
when
Time cron "0 0 6 * * ? *"
then
logInfo("Weather", "Its Morning!")
Morning.sendCommand(ON)
Day.sendCommand(OFF)
Twilight.sendCommand(OFF)
Night.sendCommand(OFF)
end
rule "Sunrise started"
when
Item Sunrise_Event received update
then
logInfo("Weather", "Its Sunrise!")
Morning.sendCommand(OFF)
Day.sendCommand(ON)
Twilight.sendCommand(OFF)
Night.sendCommand(OFF)
end
rule "Twilight started"
when
Item Twilight_Event received update
then
logInfo("Weather", "Its Twilight!")
Morning.sendCommand(OFF)
Day.sendCommand(OFF)
Twilight.sendCommand(ON)
Night.sendCommand(OFF)
end
rule "Night started"
when
Time cron "0 0 23 * * ? *"
then
logInfo("Weather", "Its Night!")
Morning.sendCommand(OFF)
Day.sendCommand(OFF)
Twilight.sendCommand(OFF)
Night.sendCommand(ON)
end
I leave as an exercise to the student how these rules can be collapsed quite a bit using a couple of lambdas.