This is essentially what I do. Honestly, I’d recommend investing the $6 in a NodeMCU with a photoresistor. I would say that the cloudy approach is maybe 50% useful. But since you ask, here it is. Note the following:
I’ll let you look at the DPs for the Time of Day stuff.
I’ve created two Groups for each time of day, one Group to contain those lights that should turn on at the start of that time period and another to contain those lights that should turn off at the start of the time period. I’m using Associated Items naming. I’ve also a Group for those lights that should turn on and off when it is cloudy as well as a Group to store the override flags (i.e. when a light is manually turned ON or OFF, do not change that light based on how cloudy it is, just leave it ON or OFF until the next time period).
Group:Switch:OR(ON, OFF) gLights_ON_MORNING (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_MORNING (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_DAY (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_DAY (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_AFTERNOON (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_AFTERNOON (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_EVENING (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_EVENING (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_NIGHT (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_NIGHT (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_BED (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_BED (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_WEATHER
Group:Switch:OR(ON, OFF) gLights_WEATHER_OVERRIDE
The following Rule turns on/off the light based on the time of day:
val logName = "lights"
// Theory of operation: Turn off the light that are members of gLights_OFF_<TOD> and
// then turn on the lights that are members of gLights_ON_<TOD>. Reset the overrides.
rule "Set lights based on Time of Day"
when
Item vTimeOfDay changed
then
// reset overrides
gLights_WEATHER_OVERRIDE.postUpdate(OFF)
val offGroupName = "gLights_OFF_"+vTimeOfDay.state.toString
val onGroupName = "gLights_ON_"+vTimeOfDay.state.toString
logInfo(logName, "Turning off lights for " + offGroupName)
val GroupItem offItems = gLights_OFF.members.filter[ g | g.name == offGroupName ].head as GroupItem
offItems.members.filter[ l | l.state != OFF ].forEach[ l | l.sendCommand(OFF) ]
logInfo(logName, "Turning on lights for " + onGroupName)
val GroupItem onItems = gLights_ON.members.filter[ g| g.name == onGroupName ].head as GroupItem
onItems.members.filter[ l | l.state != ON].forEach[ l | l.sendCommand(ON) ]
end
Here is the Rule that runs when the isCloudy Item changes. I only adjust the lights based on coudy conditions during the DAY.
// Thoery of operation: If it is day time, turn on/off the weather lights when cloudy conditions
// change. Trigger the rule when it first becomes day so we can apply cloudy to lights then as well.
rule "Turn on lights when it is cloudy"
when
Item vIsCloudy changed or
Item vTimeOfDay changed
then
// We only care about daytime
if(vTimeOfDay.state != "DAY") return;
// give the side effects of time of day time to complete
if(triggeringItem.name == "vTimeOfDay") Thread::sleep(500)
logInfo(logName, "It is DAY and cloudy changed: " + vIsCloudy.state.toString)
// Apply the cloudy state to all the lights in the weather group
gLights_ON_WEATHER.members.forEach[ l |
val overrideName = l.name+"_Override"
val override = gLights_WEATHER_OVERRIDE.members.findFirst[ o | o.name == overrideName ] as SwitchItem
if(override.state != ON && l.state != vIsCloudy.state) l.sendCommand(vIsCloudy.state as OnOffType)
logInfo(logName, l.name + if(override.state == ON) " is overridden" else " is not overridden")
]
end
Notice how I only turn on/off the light if the associated override switch is OFF.
Next we have the rule that detects when there is a manual override on a light:
// Theory of operation: any change in the relevant lights that occur more than five seconds after
// the change to DAY or after a change caused by cloudy is an override
rule "Watch for overrides"
when
Member of gLights_ON_WEATHER
then
// wait a minute before reacting after vTimeOfDay changes, ignore all other times of day
if(vTimeOfDay.state != "DAY" || vTimeOfDay.lastUpdate("mapdb").isAfter(now.minusMinutes(1).millis)) return;
// Assume any change to a light that occurs more than n seconds after time of day or cloudy is a manual override
val n = 5
val causedByClouds = vIsCloudy.lastUpdate("mapdb").isAfter(now.minusSeconds(n).millis)
val causedByTime = vTimeOfDay.lastUpdate("mapdb").isAfter(now.minusSeconds(n).millis)
if(!causedByClouds && !causedByTime) {
logInfo(logName, "Manual light trigger detected, overriding cloudy control for " + triggeringItem.name)
postUpdate(triggeringItem.name+"_Override", "ON")
}
end
This rule assumes that any change in state to a member of the weather controlled lights that occurs more than five seconds is a manual override and the override switch is set to ON.
Finally, how is vIsCloudy populated? Like I said before, I use Wunderground. There is a binding now but if you want to use HTTP directly there is a posting Comprehensive Wunderground using HTTP Binding Example that shows that.
Notice, with the above you can have any number of Lights and you can control their behaviors through Group membership alone. If you add or remove Lights or want to change a Light’s behavior all you have to do is change its membership. You never have to touch the Rules.
Finally, I just updated a lot of these rules to take advantage of changes to OH 2.3 (Member of Rule trigger) so there may be a bug that I’ve not seen yet in them. But it has been running for about a day and this part seems to work.