Hi guys,
I just migrated to OH3 from Homeassistant and already benefited so much from this community that I’d like to give something back, even if its a small thing
One of the things I liked in HA was the Circadian Lighting (or later Adaptive Light) custom component, that allows you to have your lights automatically adjust according to sun position / time of day. Thus I wanted to build something similar for OH3.
The script I wrote is based on claytonjns work here: GitHub - claytonjn/hass-circadian_lighting: Circadian Lighting custom component for Home Assistant so thanks to him for coming up with it.
The Kelvin -> RGB conversion is based on HA core colorutils: core/color.py at dev · home-assistant/core · GitHub
It does not give super accurate results with my RGB LED strips, but its a start.
Caveats:
- I use Tasmota flashed Shelly Duo bulbs and Tasmota flashed LED controllers. This means that the colortemp and brightness can be adjusted without them turning on. If your lights do not support this, you have to build a more sophisticated update rule that checks if the light is on before you modify the color/brightness
- the RGB conversion is not super accurate and has to be tweaked to your RGB lights if you mix them with ColorTemp only lights.
- I am new to OH, so it could very well be that I miss something important
So, first I created a few items to store the calculated values:
and Astro items to get sunrise and sunset:
now I create a rule that every 5 minutes re-calcs the values:
and here comes the crucial part, the script that does the calculations:
(function(context) {
this.logger = (this.logger === undefined) ? Java.type("org.slf4j.LoggerFactory").getLogger("org.openhab") : this.logger;
// defaults
var maxColorTemp = 5500;
var minColorTemp = 2000;
var maxBrightness = 100;
var minBrightness = 20;
// timestamps
var oneDay = 86400;
var sunRise = new Date(ir.getItem("LokaleSonnendaten_Rise_Start").state).getTime()/1000;
var sunSet = new Date(ir.getItem("LokaleSonnendaten_Set_Start").state).getTime()/1000;
var solarNoon = sunRise + (sunSet - sunRise)/2;
var solarMidnight = sunSet + ((sunRise + oneDay) - sunSet)/2;
var now = new Date().getTime()/1000;
var todayTimes = {};
todayTimes = {SUN_RISE: sunRise, SUN_SET: sunSet, SOLAR_NOON: solarNoon, SOLAR_MIDNIGHT: solarMidnight};
var yesterdayTimes = {};
var tomorrowTimes = {};
// initial values
var percentage = 100;
var colorTemp = 5500;
var brightness = 100;
// teile der prozent formel
var h = 0;
var k = 0;
var x = 0;
var y = 0;
// rechnen ob wir schon über mitternacht sind und zeiten anpassen
if (now < todayTimes.SUN_RISE) { // It's before sunrise (after midnight)
yesterdayTimes = {SUN_RISE: sunRise-oneDay, SUN_SET: sunSet-oneDay, SOLAR_NOON: solarNoon-oneDay, SOLAR_MIDNIGHT: solarMidnight-oneDay};
this.logger.info("yesterdays sunrise and -set: "+yesterdayTimes);
sunSet = yesterdayTimes.SUN_SET;
if (todayTimes.SOLAR_MIDNIGHT > todayTimes.SUN_SET && yesterdayTimes.SOLAR_MIDNIGHT > yesterdayTimes.SUN_SET) {
solarMidnight = yesterdayTimes.SOLAR_MIDNIGHT;
}
}
else if (now > todayTimes.SUN_SET) { // It's after sunset (before midnight)
//Because it's after sunset (and before midnight) sunrise should happen tomorrow
tomorrowTimes = {SUN_RISE: sunRise+oneDay, SUN_SET: sunSet+oneDay, SOLAR_NOON: solarNoon+oneDay, SOLAR_MIDNIGHT: solarMidnight+oneDay};
this.logger.info("tomorrow sunrise and -set: "+tomorrowTimes);
sunRise = tomorrowTimes.SUN_RISE;
if (todayTimes.SOLAR_MIDNIGHT < todayTimes.SUN_RISE && tomorrowTimes.SOLAR_MIDNIGHT < tomorrowTimes.SUN_RISE) {
// Solar midnight is before sunrise so use tomorrow's time
solarMidnight = tomorrowTimes.SOLAR_MIDNIGHT;
}
}
this.logger.info("now: "+now);
this.logger.info("sunRise: "+sunRise);
this.logger.info("sunSet: "+sunSet);
this.logger.info("solar noon: "+solarNoon);
this.logger.info("solar midnight: "+solarMidnight);
// sunrise-sunset parabola
if (now > sunRise && now < sunSet) {
this.logger.info("sunrise-sunset parabola")
h = solarNoon;
k = 100;
y = 0;
if (now < solarNoon) {
x = sunRise;
} else {
x = sunSet;
}
} else if (now > sunSet && now < sunRise) {
this.logger.info("sunset-sunrise parabola")
h = solarMidnight
k = -100
y = 0
// parabola before solar_midnight
if (now < solarMidnight) {
x = sunSet
}
// parabola after solar_midnight
else{
x = sunRise;
}
}
this.logger.info("y: "+y+", k: "+k+", h: "+h+", x:"+x);
var a = (y-k)/Math.pow((h-x),2);
this.logger.info("a: "+a);
percentage = a*Math.pow((now-h),2)+k;
if (percentage > 0) {
colorTemp = ((maxColorTemp - minColorTemp) * (percentage / 100)) + minColorTemp;
brightness = maxBrightness;
}
else {
colorTemp = minColorTemp;
brightness = ((maxBrightness - minBrightness) * ((100+percentage) / 100)) + minBrightness;
}
// calculate RGB values for LED Strips
var tempInternal = colorTemp/100
var red = 0;
var green = 0;
var blue = 0;
if (tempInternal <= 66) {
red = 255
green = 99.4708025861 * Math.log(tempInternal) - 161.1195681661
} else {
red = 329.698727446 * Math.pow(tempInternal - 60, -0.1332047592)
green = 288.1221695283 * Math.pow(tempInternal - 60, -0.0755148492)
}
if (tempInternal >= 66) {
blue = 255
} else if (tempInternal <= 19) {
blue = 0
} else {
blue = 138.5177312231 * Math.log(tempInternal - 10) - 305.0447927307
}
// adjustments to shift color a bit so my RGB LED Strips match the colortemp lights better
red = red - 30
if (red < 0) {
red = 0;
}
blue = blue +20
if (blue > 255) {
blue = 255;
}
red = parseInt(red)
blue = parseInt(blue)
green = parseInt(green)
this.logger.info("rot: "+red+", gruen: "+green+", blau: "+blue)
this.logger.info("percentage: "+percentage+", temp: "+colorTemp+", brightness: "+brightness);
events.postUpdate('AdaptivesLichtFarbtemperatur', Math.round(colorTemp));
events.postUpdate('AdaptivesLichtHelligkeit', brightness);
events.postUpdate('AdaptivesLichtProzent', percentage);
events.postUpdate('AdaptiveLichttemperaturHSB', HSBType.fromRGB(red,green,blue))
// I adjust the RGB lights directly here, because I only have two and then I do not need another rule for that
events.sendCommand('Kuchenkastl_ColorValueHSBRGBorCIExyY', HSBType.fromRGB(red,green,blue))
events.sendCommand('Kinderzimmerkasten_ColorValueHSBRGBorCIExyY', HSBType.fromRGB(red,green,blue))
})(this)
So this calculates the values, puts them into the items (and sets the RGB LED strips directly to the desired color, because I had no time yet to adapt the other rule to include the strips).
so thats more or less it. What is now needed is a rule that reacts to changes of the color/brightness items and updates the lights as desired. For this I created three groups:
- group of colortemps to adjust
- group of dimmers to adjust
- group of dimmers to adjust that should not go below 30%
For convenience sake I also created a switch item to disable automatic adjustments. Sometimes in the evening I want to rake up brightness in the livingroom and I do not want this to be overruled by OH again:
The rule to update colortemp and brightness:
and the script thats executed by the rule to update the group members. Again, this only works like that for Tasmota lights. For others you have to first check if the state of the light is ON, otherwise they will turn on. Hue lights work like that for example.
gLichttemperaturenDieZuJustierenSind.members.forEach[ licht | licht.sendCommand((1000000/(AdaptivesLichtFarbtemperatur.state as Number))) ]
val zielHelligkeit = AdaptivesLichtHelligkeit.state as Number
gHelligkeitDieZuJustierenSind.members.forEach[ licht |
licht.sendCommand(zielHelligkeit) ]
if (zielHelligkeit < 30) {
gHelligkeitDieZuJustierenSindMin30.members.forEach[ licht |
licht.sendCommand(30) ]
} else {
gHelligkeitDieZuJustierenSindMin30.members.forEach[ licht |
licht.sendCommand(zielHelligkeit) ]
}
I hope this helps somebody
LG