Skip to content

Instantly share code, notes, and snippets.

@zkniebel
Last active February 26, 2020 14:07
Show Gist options
  • Save zkniebel/1258e7779141042e9e43e481438f6347 to your computer and use it in GitHub Desktop.
Save zkniebel/1258e7779141042e9e43e481438f6347 to your computer and use it in GitHub Desktop.
Node.js helper functions meant to support the creation of automated circadian lighting scripts. These scripts are based on a port of the Python code from claytonjn's hass-circadian_lighting repository (https://github.com/claytonjn/hass-circadian_lighting)
/**
* CREDIT: The below is a modified JS port of the Python code from claytonjn's hass-circadian_lighting repository
* URL : https://github.com/claytonjn/hass-circadian_lighting
*/
// weather is expected to be a darksky weather response JSON object with at least today and tomorrow as daily forcast objects
// NOTE: this implementation could be made more accurate by making a Time Machine request to darksky, but this would be an
// additional API call and could possibly result in hitting the daily API call limit. As such, yesterday's weather is
// optional and can be omitted in favor of an estimation based on today's values if desired
function getPercent(weather, yesterdayWeather) {
var sunTimes = {
today: {
sunrise: weather.daily.data[0].sunriseTime,
sunset: weather.daily.data[0].sunsetTime
},
tomorrow: {
sunrise: weather.daily.data[1].sunriseTime,
sunset: weather.daily.data[1].sunsetTime
},
dayAfterTomorrow: {
sunrise: weather.daily.data[2].sunriseTime
}
};
// if historical data was provided
if (yesterdayWeather) {
sunTimes.yesterday = {
sunset: yesterdayWeather.daily.data[0].sunsetTime
};
} else {
// estimated by using today's sunset time - seconds/day
sunTimes.yesterday = {
sunset: sunTimes.today.sunset - 86400
};
}
// set relevant solar noon and solar midnight properties
// yesterday (only need solar midnight)
sunTimes.yesterday.solarMidnight = Math.round(
sunTimes.yesterday.sunset + (sunTimes.today.sunrise - sunTimes.yesterday.sunset) / 2);
// today
sunTimes.today.solarNoon = Math.round(
sunTimes.today.sunrise + (sunTimes.today.sunset - sunTimes.today.sunrise) / 2);
sunTimes.today.solarMidnight = Math.round(
sunTimes.today.sunset + (sunTimes.tomorrow.sunrise - sunTimes.today.sunset) / 2);
// tomorrow
sunTimes.tomorrow.solarNoon = Math.round(
sunTimes.tomorrow.sunrise + (sunTimes.tomorrow.sunset - sunTimes.tomorrow.sunrise) / 2);
sunTimes.tomorrow.solarMidnight = Math.round(
sunTimes.tomorrow.sunset + (sunTimes.dayAfterTomorrow.sunrise - sunTimes.tomorrow.sunset) / 2);
// set chosen time defaults
var now = Math.round(new Date().getTime() / 1000);
var sunrise = sunTimes.today.sunrise;
var sunset = sunTimes.today.sunset;
var solarNoon = sunTimes.today.solarNoon;
var solarMidnight = sunTimes.today.solarMidnight;
if (now < sunrise) { // if before sunrise/after midnight
// it's before sunrise/after midnight -> sunset must have happend yesterday
sunset = sunTimes.yesterday.sunset;
if (sunTimes.today.solarMidnight > sunTimes.today.sunset
&& sunTimes.yesterday.solarMidnight > sunTimes.yesterday.sunset) {
// solar midnight is after sunset -> use yesterdays's time
solarMidnight = sunTimes.yesterday.solarMidnight;
}
} else if (now > sunset) { // if after sunset/before midnight
// it's after sunset/before midnight -> sunrise must happen tomorrow
sunrise = sunTimes.tomorrow.sunrise;
if (sunTimes.today.solarMidnight < sunTimes.today.sunrise
&& sunTimes.tomorrow.solarMidnight < sunTimes.tomorrow.sunrise) {
// solar midnight is before sunrise -> use tomorrow's time
solarMidnight = sunTimes.tomorrow.solarMidnight;
}
}
// generate the parabola based on current time and whether it's before/after sunset/sunrise and solar noon/midnight
var h,
k,
x,
y;
// sunrise-sunset parabola
if (now > sunrise && now < sunset) {
h = solarNoon;
k = 100;
y = 0;
// if before solar noon
if (now < solarNoon) {
x = sunrise;
} else {
x = sunset;
}
// sunset-sunrise parabola
} else if (now > sunset && now < sunrise) {
h = solarMidnight;
k = -100;
y = 0;
// if before solar midnight
if (now < solarMidnight) {
x = sunset;
} else {
x = sunrise;
}
}
return ((y - k) / Math.pow(h - x, 2)) * Math.pow(now - h, 2) + k;
}
function getColorTemp(percent, minColorTemp, maxColorTemp) {
if (percent > 0) {
return ((maxColorTemp - minColorTemp) * (percent / 100)) + minColorTemp;
}
return minColorTemp;
}
function getBrightness(percent, minBrightness, maxBrightness) {
const hueMaxBrightness = 255;
if (percent <= 0) {
return minBrightness / hueMaxBrightness * 100;
} else {
var britValue = Math.round(((maxBrightness - minBrightness) * percent / 100) + minBrightness);
var britPercentage = Math.round(britValue / hueMaxBrightness * 100);
return britPercentage;
}
}
@zkniebel
Copy link
Author

For those interested in using this gist, note that getColorTemp returns the color temperature in Kelvin, and also expects the minColorTemp and maxColorTemp parameters to be in Kelvin. For my use case, I pass 2500 as the minColorTemp and 5500 as the maxColorTemp. Feel free to pass your own values as desired.

@zkniebel
Copy link
Author

Added in missing function for getting brightness (was accidentally excluded from original)

@zkniebel
Copy link
Author

Updated brightness function to more clearly produce two possible values: a brightness value (within the range of [minBrightness,maxBrightness]) and percentage. I use the percentage and previously included that logic outside of the functions in this gist, but in retrospect I didn't think this was as clear or legible as including both as variables within the function. Feel free to modify at your discretion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment