Hi all,
I came across an example and another question around how to re-produce f.lux style changes with bulbs that are capable of doing so. This had been on my wish list for a while so a month or so back I had a bash at making it a reality. The bulbs I’m using are LIFX Original A21, but this approach should work with most bulbs. This Hue solution served as a starting point, although it had some short comings that I wanted to solve.
My aim was to automatically mimic the transition to a bluer colour of daylight during the day and then to a warmer colour of candlelight / tungsten bulbs in the evening. In order to ensure this adjusted year round I use the sunrise and sunset times from the astro plugin. I experimented quite a bit but eventually landed with making the transition in the two hours before sunrise and sunset.
The main change I made from the Hue solution was to execute on a cron every minute rather than kickoff a background process that does the change over an hour. This makes for much easier testing and also means that restarting OpenHAB will not stop the colour change process.
In addition I found I needed to work around the fact that it isn’t possible to set the colour temperature of a LIFX bulb whilst it is off. Setting it turns the bulb on! My work around is to check the state of the bulb and only update it if it is on. In order to deal with bulbs that are switched on but are the wrong colour because they were last switched on at another time I also trigger my rule on bulb state changes and fix them up as quickly as possible. This does mean that there is sometimes a short period of time (~1s) when it is the wrong colour before the rule can update it. I suspect other bulbs might not have this problem.
My things/astro.things
looks like:
astro:sun:home [ geolocation="51.47135,-0.09212", interval=300]
astro:moon:home [ geolocation="51.47135,-0.09212", interval=300]
and the corresponding items/astro.items
looks like:
DateTime Sunrise_Time "Sunrise [%1$tH:%1$tM]" { channel="astro:sun:home:rise#start" }
DateTime Sunset_Time "Sunset [%1$tH:%1$tM]" { channel="astro:sun:home:set#start" }
My LIFX bulbs are defined as follows:
// LIFX bulbs that have their temperature automatically updated go in this group
Group gColourTemperature
// This group is used to update bulb colour temp when they are switched on and to
// ensure that colour temp of a bulb that isn't on will not be updated.
Group:Switch gColourTemperatureSwitch
Color lounge_sofa_light_color "Sofa" <light> (gLounge) [ "Lighting" ] { channel="lifx:colorlight:D073D500E6EE:color" }
Switch lounge_sofa_light_switch (gLounge, gColourTemperatureSwitch) { channel="lifx:colorlight:D073D500E6EE:color" }
Dimmer lounge_sofa_light_dimmer { channel="lifx:colorlight:D073D500E6EE:color" }
Dimmer lounge_sofa_light_temperature (gColourTemperature) { channel="lifx:colorlight:D073D500E6EE:temperature" }
Color lounge_main_light_color "Main" <light> (gLounge) [ "Lighting" ] { channel="lifx:colorlight:D073D500EC7D:color" }
Switch lounge_main_light_switch (gLounge, gColourTemperatureSwitch) { channel="lifx:colorlight:D073D500EC7D:color" }
Dimmer lounge_main_light_dimmer { channel="lifx:colorlight:D073D500EC7D:color" }
Dimmer lounge_main_light_temperature (gColourTemperature) { channel="lifx:colorlight:D073D500EC7D:temperature" }
Color hallway_light_color "Hallway" <light> (gHallway) [ "Lighting" ] { channel="lifx:colorlight:D073D500E81E:color" }
Switch hallway_light_switch (gHallway, gColourTemperatureSwitch) { channel="lifx:colorlight:D073D500E81E:color" }
Dimmer hallway_light_dimmer { channel="lifx:colorlight:D073D500E81E:color" }
Dimmer hallway_light_temperature (gColourTemperature) { channel="lifx:colorlight:D073D500E81E:temperature" }
Color bedroom_main_light_color "Bed" <light> (gBedroom) [ "Lighting" ] { channel="lifx:colorlight:D073D500E456:color" }
Switch bedroom_main_light_switch (gBedroom, gColourTemperatureSwitch) { channel="lifx:colorlight:D073D500E456:color" }
Dimmer bedroom_main_light_dimmer { channel="lifx:colorlight:D073D500E456:color" }
Dimmer bedroom_main_light_temperature (gColourTemperature) { channel="lifx:colorlight:D073D500E456:temperature" }
Color bedroom_corridor_light_color "Corridor" <light> (gBedroom) [ "Lighting" ] { channel="lifx:colorlight:D073D500E7AC:color" }
Switch bedroom_corridor_light_switch (gBedroom, gColourTemperatureSwitch) { channel="lifx:colorlight:D073D500E7AC:color" }
Dimmer bedroom_corridor_light_dimmer { channel="lifx:colorlight:D073D500E7AC:color" }
Dimmer bedroom_corridor_light_temperature (gColourTemperature) { channel="lifx:colorlight:D073D500E7AC:temperature" }
There are two things to note above:
- the two groups that help the rules to find the dimmer controls and switches of the lights that should be controlled
- that the naming of the switch and temperature have a common root and end in
_switch
and_temperature
respectively
Finally this is the rule in rules/daylight.rule
:
import org.joda.time.*
/*
A rule to update the colour temperature of LIFX bulbs throughout the day.
At present this is warm evening and overnight. It cools the light in the two hours
before sunrise, stays cool throughout the day and then warms again over four
hours around sunset (two before, two after).
Unfortunately, setting the colour temperature of a bulb also turns it on (rolls eyes).
To work around this we do two things:
- Find the switch and check that it is ON, if not we don't update the colour temp
- Ask for updates when a light is switched on or off and ASAP after a bulb is switched
on we set it to the current colour temperature
*/
rule "light_colour_temp"
when
// every 10 seconds for debugging purposes
//Time cron "0/10 * * ? * * *" or
// apply every two minutes when actually running - that should mean it rarely/never changes
// by more than a 1% increment
Time cron "0 0/2 * ? * * *" or
Item gColourTemperatureSwitch received update
then
logInfo("daylight", "Sunset time: "+Sunset_Time.state)
var sunrise = new DateTime(Sunrise_Time.state.toString)
var sunset = new DateTime(Sunset_Time.state.toString)
var preSunrise = sunrise.minusHours(2)
var preSunset = sunset.minusHours(2)
var postSunset = sunset.plusHours(0)
logInfo("daylight", "Times: "+preSunrise+" "+preSunset+" "+postSunset)
var now = new DateTime()
var int CT_COOL = 60
var int CT_WARM = 92
var float colourTemp = 0
if (now.isBefore(preSunrise)) {
logInfo("daylight", "early hours: WARM")
colourTemp = CT_WARM
} else if (now.isBefore(sunrise)) {
logInfo("daylight", "dawn: CHANGING TO COOL")
var long period = sunrise.millis - preSunrise.millis
var long progress = now.millis - preSunrise.millis
var float factor = progress.floatValue / period.floatValue
logInfo("daylight", period+" "+progress+" "+factor)
colourTemp = ((1-factor) * (CT_WARM-CT_COOL) + CT_COOL).intValue
} else if (now.isBefore(preSunset)) {
logInfo("daylight", "daytime: COOL")
colourTemp = CT_COOL
} else if (now.isBefore(postSunset)) {
logInfo("daylight", "evening: CHANGING TO WARM")
var long period = postSunset.millis - preSunset.millis
var long progress = now.millis - preSunset.millis
var float factor = progress.floatValue / period.floatValue
logInfo("daylight", period+" "+progress+" "+factor)
colourTemp = (factor * (CT_WARM-CT_COOL) + CT_COOL).intValue
} else {
logInfo("daylight", "late evening: WARM")
colourTemp = CT_WARM
}
logInfo("daylight", "setting CT to "+colourTemp+"%")
gColourTemperature.members.forEach(ct_dimmer | {
var switchName = ct_dimmer.name.replaceFirst("_temperature$", "_switch")
var switchItem = gColourTemperatureSwitch.members.filter(x|x.name == switchName).map[it].head
logDebug("daylight", "for "+ct_dimmer.name+" got "+switchItem.name)
if (ct_dimmer.state != colourTemp && switchItem.state == ON) {
logDebug("daylight", "updating "+ct_dimmer.name+" to "+colourTemp)
sendCommand(ct_dimmer, colourTemp)
}
})
end
I’ve had this running the the last month or so and have been pleased with how well it works.
I’d be glad of any suggestions for improvements and other feedback.