From 4f332a514ccf491ef1bcdb171b33d36f759679a0 Mon Sep 17 00:00:00 2001 From: Clayton Nummer Date: Sat, 1 Jun 2019 14:20:47 -0400 Subject: [PATCH 1/2] Re-write of values calculation The basis of Circadian Lighting is to figure out how to adjust lights to mimic the sun, which is roughly parabolic above and below the horizon. Originally I used three points to calculate the parabolas - sunrise, sunset, and solar noon/midnight. I did a bunch of generic arithmetic to generate the parabola from those three points. The problem is that this arithmetic generates a parabola from _any_ three points - in reality the points I have are the intercepts and vertex. The problem with the old method is that if solar noon/midnight did not fall exactly between sunrise and sunset then the calculated vertex could be more or less than 100%, which would cause the integration to set color/brightness values above or below the set min/max. Generating a parabola from the intercepts and vertex is much simpler and also should guarantee the min/max are what's expected. In addition to this change, I'm also now calculating 4 "half" parabolas instead of just one for above horizon and one for below horizon. The "half" parabolas are: sunrise->solar-noon, solar-noon->sunset, sunset->solar-midnight, solar-midnight->sunrise. This is necessary for the case where solar noon/midnight don't fall exactly half way between sunrise and sunset, which is especially prevalent for users who specify a specific time for sunrise or sunset but not both. I'm embarrassed at how it's been before it dawned on me that I could calculate the parabolas from intercepts/vertex rather than the three random points method, but hopefully this fixes inconsistencies for users - it should definitely fix issue #25! --- .../circadian_lighting/__init__.py | 119 +++++++++--------- custom_updater.json | 4 +- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index b4e4cb9d..ad97d3fc 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -48,7 +48,7 @@ from homeassistant.util.dt import utcnow as dt_utcnow, as_local from datetime import datetime, timedelta -VERSION = '1.0.4' +VERSION = '1.0.5' _LOGGER = logging.getLogger(__name__) @@ -199,77 +199,78 @@ class CircadianLighting(object): def calc_percent(self): utcnow = dt_utcnow() now = as_local(utcnow) + _LOGGER.debug("now: " + str(now)) + today_sun_times = self.get_sunrise_sunset(now) + _LOGGER.debug("today_sun_times: " + str(today_sun_times)) + # Convert everything to epoch timestamps for easy calculation now_seconds = now.timestamp() - today_sunrise_seconds = today_sun_times[SUN_EVENT_SUNRISE].timestamp() - today_sunset_seconds = today_sun_times[SUN_EVENT_SUNSET].timestamp() - today_solar_noon_seconds = today_sun_times['solar_noon'].timestamp() - today_solar_midnight_seconds = today_sun_times['solar_midnight'].timestamp() + sunrise_seconds = today_sun_times[SUN_EVENT_SUNRISE].timestamp() + sunset_seconds = today_sun_times[SUN_EVENT_SUNSET].timestamp() + solar_noon_seconds = today_sun_times['solar_noon'].timestamp() + solar_midnight_seconds = today_sun_times['solar_midnight'].timestamp() - _LOGGER.debug("now: " + str(now) + "\n\n today_sun_times: " + str(today_sun_times)) - - if now < today_sun_times[SUN_EVENT_SUNRISE]: + if now < today_sun_times[SUN_EVENT_SUNRISE]: # It's before sunrise (after midnight) + # Because it's before sunrise (and after midnight) sunset must have happend yesterday yesterday_sun_times = self.get_sunrise_sunset(now - timedelta(days=1)) - yesterday_sunrise_seconds = yesterday_sun_times[SUN_EVENT_SUNRISE].timestamp() - yesterday_sunset_seconds = yesterday_sun_times[SUN_EVENT_SUNSET].timestamp() - yesterday_solar_midnight_seconds = yesterday_sun_times['solar_midnight'].timestamp() - _LOGGER.debug("yesterday_sun_times: " + str(yesterday_sun_times)) - - x1 = yesterday_sunset_seconds - y1 = 0 - - if today_sun_times['solar_midnight'] > yesterday_sun_times[SUN_EVENT_SUNSET] and today_sun_times['solar_midnight'] < today_sun_times[SUN_EVENT_SUNRISE]: - x2 = today_solar_midnight_seconds - else: - x2 = yesterday_solar_midnight_seconds - y2 = -100 - - x3 = today_sunrise_seconds - y3 = 0 - elif now > today_sun_times[SUN_EVENT_SUNSET]: + sunset_seconds = yesterday_sun_times[SUN_EVENT_SUNSET].timestamp() + if today_sun_times['solar_midnight'] > today_sun_times[SUN_EVENT_SUNSET] and yesterday_sun_times['solar_midnight'] > yesterday_sun_times[SUN_EVENT_SUNSET]: + # Solar midnight is after sunset so use yesterdays's time + solar_midnight_seconds = yesterday_sun_times['solar_midnight'].timestamp() + elif now > today_sun_times[SUN_EVENT_SUNSET]: # It's after sunset (before midnight) + # Because it's after sunset (and before midnight) sunrise should happen tomorrow tomorrow_sun_times = self.get_sunrise_sunset(now + timedelta(days=1)) - tomorrow_sunrise_seconds = tomorrow_sun_times[SUN_EVENT_SUNRISE].timestamp() - tomorrow_sunset_seconds = tomorrow_sun_times[SUN_EVENT_SUNSET].timestamp() - tomorrow_solar_midnight_seconds = tomorrow_sun_times['solar_midnight'].timestamp() + _LOGGER.debug("tomorrow_sun_times: " + str(tomorrow_sun_times)) + sunrise_seconds = tomorrow_sun_times[SUN_EVENT_SUNRISE].timestamp() + if today_sun_times['solar_midnight'] < today_sun_times[SUN_EVENT_SUNRISE] and tomorrow_sun_times['solar_midnight'] < tomorrow_sun_times[SUN_EVENT_SUNRISE]: + # Solar midnight is before sunrise so use tomorrow's time + solar_midnight_seconds = tomorrow_sun_times['solar_midnight'].timestamp() - x1 = today_sunset_seconds - y1 = 0 + _LOGGER.debug("now_seconds: " + str(now_seconds)) + _LOGGER.debug("sunrise_seconds: " + str(sunrise_seconds)) + _LOGGER.debug("sunset_seconds: " + str(sunset_seconds)) + _LOGGER.debug("solar_midnight_seconds: " + str(solar_midnight_seconds)) + _LOGGER.debug("solar_noon_seconds: " + str(solar_noon_seconds)) - if today_sun_times['solar_midnight'] > today_sun_times[SUN_EVENT_SUNSET] and today_sun_times['solar_midnight'] < tomorrow_sun_times[SUN_EVENT_SUNRISE]: - x2 = today_solar_midnight_seconds + # Figure out where we are in time so we know which half of the parabola to calculate + # We're generating a different sunset-sunrise parabola for before and after solar midnight + # because it might not be half way between sunrise and sunset + # We're also (obviously) generating a different parabola for sunrise-sunset + + # sunrise-sunset parabola + if now_seconds > sunrise_seconds and now_seconds < sunset_seconds: + h = solar_noon_seconds + k = 100 + # parabola before solar_noon + if now_seconds < solar_noon_seconds: + x = sunrise_seconds + # parabola after solar_noon else: - x2 = tomorrow_solar_midnight_seconds - y2 = -100 + x = sunset_seconds + y = 0 - x3 = tomorrow_sunrise_seconds - y3 = 0 - else: - x1 = today_sunrise_seconds - y1 = 0 - x2 = today_solar_noon_seconds - y2 = 100 - x3 = today_sunset_seconds - y3 = 0 + # sunset_sunrise parabola + elif now_seconds > sunset_seconds and now_seconds < sunrise_seconds: + h = solar_midnight_seconds + k = -100 + # parabola before solar_midnight + if now_seconds < solar_midnight_seconds: + x = sunset_seconds + # parabola after solar_midnight + else: + x = sunrise_seconds + y = 0 - _LOGGER.debug("x1: " + str(x1) + "\n\n y1: " + str(y1) + "\n\n x2: " + str(x2) + "\n\n y2: " + str(y2)) - - # Generate color temperature parabola from points - a1 = -x1**2+x2**2 - b1 = -x1+x2 - d1 = -y1+y2 - a2 = -x2**2+x3**2 - b2 = -x2+x3 - d2 = -y2+y3 - bm = -(b2/b1) - a3 = bm*a1+a2 - d3 = bm*d1+d2 - a = d3/a3 - b = (d1-a1*a)/b1 - c = y1-a*x1**2-b*x1 - percentage = a*now_seconds**2+b*now_seconds+c + a = (y-k)/(h-x)**2 + percentage = a*(now_seconds-h)**2+k + _LOGGER.debug("h: " + str(h)) + _LOGGER.debug("k: " + str(k)) + _LOGGER.debug("x: " + str(x)) + _LOGGER.debug("y: " + str(y)) + _LOGGER.debug("a: " + str(a)) _LOGGER.debug("percentage: " + str(percentage)) return percentage diff --git a/custom_updater.json b/custom_updater.json index d865f3c5..f2a32ea4 100644 --- a/custom_updater.json +++ b/custom_updater.json @@ -1,7 +1,7 @@ { "circadian_lighting": { - "updated_at": "2019-05-09", - "version": "1.0.4", + "updated_at": "2019-06-01", + "version": "1.0.5", "local_location": "/custom_components/circadian_lighting/__init__.py", "remote_location": "https://raw.githubusercontent.com/claytonjn/hass-circadian_lighting/master/custom_components/circadian_lighting/__init__.py", "visit_repo": "https://github.com/claytonjn/hass-circadian_lighting", From 6d9df861ce446f583734170eff96d3135fa2356a Mon Sep 17 00:00:00 2001 From: Clayton Nummer Date: Sat, 8 Jun 2019 11:20:58 -0400 Subject: [PATCH 2/2] Tweak default interval/transition for better compatibility Some types of lights seem to have issues with long transitions --- custom_components/circadian_lighting/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index ad97d3fc..8976e285 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -66,7 +66,8 @@ CONF_SUNSET_OFFSET = 'sunset_offset' CONF_SUNRISE_TIME = 'sunrise_time' CONF_SUNSET_TIME = 'sunset_time' CONF_INTERVAL = 'interval' -DEFAULT_INTERVAL = 900 +DEFAULT_INTERVAL = 300 +DEFAULT_TRANSITION = 60 CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -82,7 +83,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_LONGITUDE): cv.longitude, vol.Optional(CONF_ELEVATION): float, vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.positive_int, - vol.Optional(ATTR_TRANSITION, default=DEFAULT_INTERVAL): VALID_TRANSITION + vol.Optional(ATTR_TRANSITION, default=DEFAULT_TRANSITION): VALID_TRANSITION }), }, extra=vol.ALLOW_EXTRA)