From 9b8a4ad51f991730b675eb4d40f934d0bea100f8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:08:33 +0200 Subject: [PATCH 01/69] run black, pyupgrade, and isort --- .../circadian_lighting/__init__.py | 336 +++++++++++------- .../circadian_lighting/sensor.py | 44 ++- .../circadian_lighting/switch.py | 331 +++++++++++------ 3 files changed, 470 insertions(+), 241 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 3acf0473..79d8de1d 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -28,64 +28,78 @@ Technical notes: I had to make a lot of assumptions when writing this app """ import logging - -import voluptuous as vol +from datetime import datetime, timedelta import homeassistant.helpers.config_validation as cv -from homeassistant.components.light import ( - VALID_TRANSITION, ATTR_TRANSITION) +import voluptuous as vol +from homeassistant.components.light import ATTR_TRANSITION, VALID_TRANSITION from homeassistant.const import ( - CONF_LATITUDE, CONF_LONGITUDE, CONF_ELEVATION, - SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET) -from homeassistant.util import Throttle + CONF_ELEVATION, + CONF_LATITUDE, + CONF_LONGITUDE, + SUN_EVENT_SUNRISE, + SUN_EVENT_SUNSET, +) from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.event import track_sunrise, track_sunset, track_time_change +from homeassistant.util import Throttle from homeassistant.util.color import ( - color_temperature_to_rgb, color_RGB_to_xy, - color_xy_to_hs) -from homeassistant.util.dt import now as dt_now, get_time_zone + color_RGB_to_xy, + color_temperature_to_rgb, + color_xy_to_hs, +) +from homeassistant.util.dt import get_time_zone +from homeassistant.util.dt import now as dt_now -from datetime import datetime, timedelta - -VERSION = '1.0.13' +VERSION = "1.0.13" _LOGGER = logging.getLogger(__name__) -DOMAIN = 'circadian_lighting' -CIRCADIAN_LIGHTING_PLATFORMS = ['sensor', 'switch'] -CIRCADIAN_LIGHTING_UPDATE_TOPIC = '{0}_update'.format(DOMAIN) -DATA_CIRCADIAN_LIGHTING = 'data_cl' +DOMAIN = "circadian_lighting" +CIRCADIAN_LIGHTING_PLATFORMS = ["sensor", "switch"] +CIRCADIAN_LIGHTING_UPDATE_TOPIC = "{}_update".format(DOMAIN) +DATA_CIRCADIAN_LIGHTING = "data_cl" -CONF_MIN_CT = 'min_colortemp' +CONF_MIN_CT = "min_colortemp" DEFAULT_MIN_CT = 2500 -CONF_MAX_CT = 'max_colortemp' +CONF_MAX_CT = "max_colortemp" DEFAULT_MAX_CT = 5500 -CONF_SUNRISE_OFFSET = 'sunrise_offset' -CONF_SUNSET_OFFSET = 'sunset_offset' -CONF_SUNRISE_TIME = 'sunrise_time' -CONF_SUNSET_TIME = 'sunset_time' -CONF_INTERVAL = 'interval' +CONF_SUNRISE_OFFSET = "sunrise_offset" +CONF_SUNSET_OFFSET = "sunset_offset" +CONF_SUNRISE_TIME = "sunrise_time" +CONF_SUNSET_TIME = "sunset_time" +CONF_INTERVAL = "interval" DEFAULT_INTERVAL = 300 DEFAULT_TRANSITION = 60 -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_MIN_CT, default=DEFAULT_MIN_CT): - vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)), - vol.Optional(CONF_MAX_CT, default=DEFAULT_MAX_CT): - vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)), - vol.Optional(CONF_SUNRISE_OFFSET): cv.time_period_str, - vol.Optional(CONF_SUNSET_OFFSET): cv.time_period_str, - vol.Optional(CONF_SUNRISE_TIME): cv.time, - vol.Optional(CONF_SUNSET_TIME): cv.time, - vol.Optional(CONF_LATITUDE): cv.latitude, - 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_TRANSITION): VALID_TRANSITION - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_MIN_CT, default=DEFAULT_MIN_CT): vol.All( + vol.Coerce(int), vol.Range(min=1000, max=10000) + ), + vol.Optional(CONF_MAX_CT, default=DEFAULT_MAX_CT): vol.All( + vol.Coerce(int), vol.Range(min=1000, max=10000) + ), + vol.Optional(CONF_SUNRISE_OFFSET): cv.time_period_str, + vol.Optional(CONF_SUNSET_OFFSET): cv.time_period_str, + vol.Optional(CONF_SUNRISE_TIME): cv.time, + vol.Optional(CONF_SUNSET_TIME): cv.time, + vol.Optional(CONF_LATITUDE): cv.latitude, + 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_TRANSITION + ): VALID_TRANSITION, + } + ), + }, + extra=vol.ALLOW_EXTRA, +) + def setup(hass, config): """Set up the Circadian Lighting component.""" @@ -101,110 +115,171 @@ def setup(hass, config): longitude = conf.get(CONF_LONGITUDE, hass.config.longitude) elevation = conf.get(CONF_ELEVATION, hass.config.elevation) - load_platform(hass, 'sensor', DOMAIN, {}, config) + load_platform(hass, "sensor", DOMAIN, {}, config) interval = conf.get(CONF_INTERVAL) transition = conf.get(ATTR_TRANSITION) - cl = CircadianLighting(hass, min_colortemp, max_colortemp, - sunrise_offset, sunset_offset, sunrise_time, sunset_time, - latitude, longitude, elevation, - interval, transition) + cl = CircadianLighting( + hass, + min_colortemp, + max_colortemp, + sunrise_offset, + sunset_offset, + sunrise_time, + sunset_time, + latitude, + longitude, + elevation, + interval, + transition, + ) hass.data[DATA_CIRCADIAN_LIGHTING] = cl return True + class CircadianLighting(object): """Calculate universal Circadian values.""" - def __init__(self, hass, min_colortemp, max_colortemp, - sunrise_offset, sunset_offset, sunrise_time, sunset_time, - latitude, longitude, elevation, - interval, transition): + def __init__( + self, + hass, + min_colortemp, + max_colortemp, + sunrise_offset, + sunset_offset, + sunrise_time, + sunset_time, + latitude, + longitude, + elevation, + interval, + transition, + ): self.hass = hass self.data = {} - self.data['min_colortemp'] = min_colortemp - self.data['max_colortemp'] = max_colortemp - self.data['sunrise_offset'] = sunrise_offset - self.data['sunset_offset'] = sunset_offset - self.data['sunrise_time'] = sunrise_time - self.data['sunset_time'] = sunset_time - self.data['latitude'] = latitude - self.data['longitude'] = longitude - self.data['elevation'] = elevation - self.data['interval'] = interval - self.data['transition'] = transition - self.data['timezone'] = self.get_timezone() - self.data['percent'] = self.calc_percent() - self.data['colortemp'] = self.calc_colortemp() - self.data['rgb_color'] = self.calc_rgb() - self.data['xy_color'] = self.calc_xy() - self.data['hs_color'] = self.calc_hs() + self.data["min_colortemp"] = min_colortemp + self.data["max_colortemp"] = max_colortemp + self.data["sunrise_offset"] = sunrise_offset + self.data["sunset_offset"] = sunset_offset + self.data["sunrise_time"] = sunrise_time + self.data["sunset_time"] = sunset_time + self.data["latitude"] = latitude + self.data["longitude"] = longitude + self.data["elevation"] = elevation + self.data["interval"] = interval + self.data["transition"] = transition + self.data["timezone"] = self.get_timezone() + self.data["percent"] = self.calc_percent() + self.data["colortemp"] = self.calc_colortemp() + self.data["rgb_color"] = self.calc_rgb() + self.data["xy_color"] = self.calc_xy() + self.data["hs_color"] = self.calc_hs() self.update = Throttle(timedelta(seconds=interval))(self._update) - if self.data['sunrise_time'] is not None: - track_time_change(self.hass, self._update, hour=int(self.data['sunrise_time'].strftime("%H")), minute=int(self.data['sunrise_time'].strftime("%M")), second=int(self.data['sunrise_time'].strftime("%S"))) + if self.data["sunrise_time"] is not None: + track_time_change( + self.hass, + self._update, + hour=int(self.data["sunrise_time"].strftime("%H")), + minute=int(self.data["sunrise_time"].strftime("%M")), + second=int(self.data["sunrise_time"].strftime("%S")), + ) else: - track_sunrise(self.hass, self._update, self.data['sunrise_offset']) - if self.data['sunset_time'] is not None: - track_time_change(self.hass, self._update, hour=int(self.data['sunset_time'].strftime("%H")), minute=int(self.data['sunset_time'].strftime("%M")), second=int(self.data['sunset_time'].strftime("%S"))) + track_sunrise(self.hass, self._update, self.data["sunrise_offset"]) + if self.data["sunset_time"] is not None: + track_time_change( + self.hass, + self._update, + hour=int(self.data["sunset_time"].strftime("%H")), + minute=int(self.data["sunset_time"].strftime("%M")), + second=int(self.data["sunset_time"].strftime("%S")), + ) else: - track_sunset(self.hass, self._update, self.data['sunset_offset']) + track_sunset(self.hass, self._update, self.data["sunset_offset"]) def get_timezone(self): from timezonefinder import TimezoneFinder + tf = TimezoneFinder() - timezone_string = tf.timezone_at(lng=self.data['longitude'], lat=self.data['latitude']) + timezone_string = tf.timezone_at( + lng=self.data["longitude"], lat=self.data["latitude"] + ) timezone = get_time_zone(timezone_string) _LOGGER.debug("Timezone: " + str(timezone)) return timezone - - def get_sunrise_sunset(self, date = None): - if self.data['sunrise_time'] is not None and self.data['sunset_time'] is not None: + + def get_sunrise_sunset(self, date=None): + if ( + self.data["sunrise_time"] is not None + and self.data["sunset_time"] is not None + ): if date is None: - date = dt_now(self.data['timezone']) - sunrise = date.replace(hour=int(self.data['sunrise_time'].strftime("%H")), minute=int(self.data['sunrise_time'].strftime("%M")), second=int(self.data['sunrise_time'].strftime("%S")), microsecond=int(self.data['sunrise_time'].strftime("%f"))) - sunset = date.replace(hour=int(self.data['sunset_time'].strftime("%H")), minute=int(self.data['sunset_time'].strftime("%M")), second=int(self.data['sunset_time'].strftime("%S")), microsecond=int(self.data['sunset_time'].strftime("%f"))) - solar_noon = sunrise + (sunset - sunrise)/2 - solar_midnight = sunset + ((sunrise + timedelta(days=1)) - sunset)/2 + date = dt_now(self.data["timezone"]) + sunrise = date.replace( + hour=int(self.data["sunrise_time"].strftime("%H")), + minute=int(self.data["sunrise_time"].strftime("%M")), + second=int(self.data["sunrise_time"].strftime("%S")), + microsecond=int(self.data["sunrise_time"].strftime("%f")), + ) + sunset = date.replace( + hour=int(self.data["sunset_time"].strftime("%H")), + minute=int(self.data["sunset_time"].strftime("%M")), + second=int(self.data["sunset_time"].strftime("%S")), + microsecond=int(self.data["sunset_time"].strftime("%f")), + ) + solar_noon = sunrise + (sunset - sunrise) / 2 + solar_midnight = sunset + ((sunrise + timedelta(days=1)) - sunset) / 2 else: import astral + location = astral.Location() - location.name = 'name' - location.region = 'region' - location.latitude = self.data['latitude'] - location.longitude = self.data['longitude'] - location.elevation = self.data['elevation'] + location.name = "name" + location.region = "region" + location.latitude = self.data["latitude"] + location.longitude = self.data["longitude"] + location.elevation = self.data["elevation"] _LOGGER.debug("Astral location: " + str(location)) - if self.data['sunrise_time'] is not None: + if self.data["sunrise_time"] is not None: if date is None: - date = dt_now(self.data['timezone']) - sunrise = date.replace(hour=int(self.data['sunrise_time'].strftime("%H")), minute=int(self.data['sunrise_time'].strftime("%M")), second=int(self.data['sunrise_time'].strftime("%S")), microsecond=int(self.data['sunrise_time'].strftime("%f"))) + date = dt_now(self.data["timezone"]) + sunrise = date.replace( + hour=int(self.data["sunrise_time"].strftime("%H")), + minute=int(self.data["sunrise_time"].strftime("%M")), + second=int(self.data["sunrise_time"].strftime("%S")), + microsecond=int(self.data["sunrise_time"].strftime("%f")), + ) else: sunrise = location.sunrise(date) - if self.data['sunset_time'] is not None: + if self.data["sunset_time"] is not None: if date is None: - date = dt_now(self.data['timezone']) - sunset = date.replace(hour=int(self.data['sunset_time'].strftime("%H")), minute=int(self.data['sunset_time'].strftime("%M")), second=int(self.data['sunset_time'].strftime("%S")), microsecond=int(self.data['sunset_time'].strftime("%f"))) + date = dt_now(self.data["timezone"]) + sunset = date.replace( + hour=int(self.data["sunset_time"].strftime("%H")), + minute=int(self.data["sunset_time"].strftime("%M")), + second=int(self.data["sunset_time"].strftime("%S")), + microsecond=int(self.data["sunset_time"].strftime("%f")), + ) else: sunset = location.sunset(date) solar_noon = location.solar_noon(date) solar_midnight = location.solar_midnight(date) - if self.data['sunrise_offset'] is not None: - sunrise = sunrise + self.data['sunrise_offset'] - if self.data['sunset_offset'] is not None: - sunset = sunset + self.data['sunset_offset'] + if self.data["sunrise_offset"] is not None: + sunrise = sunrise + self.data["sunrise_offset"] + if self.data["sunset_offset"] is not None: + sunset = sunset + self.data["sunset_offset"] return { - SUN_EVENT_SUNRISE: sunrise.astimezone(self.data['timezone']), - SUN_EVENT_SUNSET: sunset.astimezone(self.data['timezone']), - 'solar_noon': solar_noon.astimezone(self.data['timezone']), - 'solar_midnight': solar_midnight.astimezone(self.data['timezone']) + SUN_EVENT_SUNRISE: sunrise.astimezone(self.data["timezone"]), + SUN_EVENT_SUNSET: sunset.astimezone(self.data["timezone"]), + "solar_noon": solar_noon.astimezone(self.data["timezone"]), + "solar_midnight": solar_midnight.astimezone(self.data["timezone"]), } def calc_percent(self): - now = dt_now(self.data['timezone']) + now = dt_now(self.data["timezone"]) _LOGGER.debug("now: " + str(now)) today_sun_times = self.get_sunrise_sunset(now) @@ -214,25 +289,41 @@ class CircadianLighting(object): now_seconds = now.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() + solar_noon_seconds = today_sun_times["solar_noon"].timestamp() + solar_midnight_seconds = today_sun_times["solar_midnight"].timestamp() - if now < today_sun_times[SUN_EVENT_SUNRISE]: # It's before sunrise (after midnight) + 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)) _LOGGER.debug("yesterday_sun_times: " + str(yesterday_sun_times)) 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]: + 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) + 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)) _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]: + 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() + solar_midnight_seconds = tomorrow_sun_times[ + "solar_midnight" + ].timestamp() _LOGGER.debug("now_seconds: " + str(now_seconds)) _LOGGER.debug("sunrise_seconds: " + str(sunrise_seconds)) @@ -269,8 +360,8 @@ class CircadianLighting(object): x = sunrise_seconds y = 0 - a = (y-k)/(h-x)**2 - percentage = a*(now_seconds-h)**2+k + a = (y - k) / (h - x) ** 2 + percentage = a * (now_seconds - h) ** 2 + k _LOGGER.debug("h: " + str(h)) _LOGGER.debug("k: " + str(k)) @@ -282,13 +373,16 @@ class CircadianLighting(object): return percentage def calc_colortemp(self): - if self.data['percent'] > 0: - return ((self.data['max_colortemp'] - self.data['min_colortemp']) * (self.data['percent'] / 100)) + self.data['min_colortemp'] + if self.data["percent"] > 0: + return ( + (self.data["max_colortemp"] - self.data["min_colortemp"]) + * (self.data["percent"] / 100) + ) + self.data["min_colortemp"] else: - return self.data['min_colortemp'] + return self.data["min_colortemp"] def calc_rgb(self): - return color_temperature_to_rgb(self.data['colortemp']) + return color_temperature_to_rgb(self.data["colortemp"]) def calc_xy(self): rgb = self.calc_rgb() @@ -307,10 +401,10 @@ class CircadianLighting(object): def _update(self, *args, **kwargs): """Update Circadian Values.""" - self.data['percent'] = self.calc_percent() - self.data['colortemp'] = self.calc_colortemp() - self.data['rgb_color'] = self.calc_rgb() - self.data['xy_color'] = self.calc_xy() - self.data['hs_color'] = self.calc_hs() + self.data["percent"] = self.calc_percent() + self.data["colortemp"] = self.calc_colortemp() + self.data["rgb_color"] = self.calc_rgb() + self.data["xy_color"] = self.calc_xy() + self.data["hs_color"] = self.calc_hs() dispatcher_send(self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC) _LOGGER.debug("Circadian Lighting Component Updated") diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index 756d5799..dc6cd738 100644 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -2,20 +2,24 @@ Circadian Lighting Sensor for Home-Assistant. """ -DEPENDENCIES = ['circadian_lighting'] +DEPENDENCIES = ["circadian_lighting"] +import datetime import logging -from custom_components.circadian_lighting import DOMAIN, CIRCADIAN_LIGHTING_UPDATE_TOPIC, DATA_CIRCADIAN_LIGHTING - from homeassistant.helpers.dispatcher import dispatcher_connect from homeassistant.helpers.entity import Entity -import datetime +from custom_components.circadian_lighting import ( + CIRCADIAN_LIGHTING_UPDATE_TOPIC, + DATA_CIRCADIAN_LIGHTING, + DOMAIN, +) _LOGGER = logging.getLogger(__name__) -ICON = 'mdi:theme-light-dark' +ICON = "mdi:theme-light-dark" + def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Circadian Lighting sensor.""" @@ -27,28 +31,30 @@ def setup_platform(hass, config, add_devices, discovery_info=None): def update(call=None): """Update component.""" cl._update() + service_name = "values_update" hass.services.register(DOMAIN, service_name, update) return True else: return False + class CircadianSensor(Entity): """Representation of a Circadian Lighting sensor.""" def __init__(self, hass, cl): """Initialize the Circadian Lighting sensor.""" self._cl = cl - self._name = 'Circadian Values' - self._entity_id = 'sensor.circadian_values' - self._state = self._cl.data['percent'] - self._unit_of_measurement = '%' + self._name = "Circadian Values" + self._entity_id = "sensor.circadian_values" + self._state = self._cl.data["percent"] + self._unit_of_measurement = "%" self._icon = ICON - self._hs_color = self._cl.data['hs_color'] + self._hs_color = self._cl.data["hs_color"] self._attributes = {} - self._attributes['colortemp'] = self._cl.data['colortemp'] - self._attributes['rgb_color'] = self._cl.data['rgb_color'] - self._attributes['xy_color'] = self._cl.data['xy_color'] + self._attributes["colortemp"] = self._cl.data["colortemp"] + self._attributes["rgb_color"] = self._cl.data["rgb_color"] + self._attributes["xy_color"] = self._cl.data["xy_color"] """Register callbacks.""" dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_sensor) @@ -96,9 +102,9 @@ class CircadianSensor(Entity): def update_sensor(self): if self._cl.data is not None: - self._state = self._cl.data['percent'] - self._hs_color = self._cl.data['hs_color'] - self._attributes['colortemp'] = self._cl.data['colortemp'] - self._attributes['rgb_color'] = self._cl.data['rgb_color'] - self._attributes['xy_color'] = self._cl.data['xy_color'] - _LOGGER.debug("Circadian Lighting Sensor Updated") \ No newline at end of file + self._state = self._cl.data["percent"] + self._hs_color = self._cl.data["hs_color"] + self._attributes["colortemp"] = self._cl.data["colortemp"] + self._attributes["rgb_color"] = self._cl.data["rgb_color"] + self._attributes["xy_color"] = self._cl.data["xy_color"] + _LOGGER.debug("Circadian Lighting Sensor Updated") diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 3b58c1c5..59aca5a9 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -2,80 +2,105 @@ Circadian Lighting Switch for Home-Assistant. """ -DEPENDENCIES = ['circadian_lighting', 'light'] +DEPENDENCIES = ["circadian_lighting", "light"] import logging -from custom_components.circadian_lighting import DOMAIN, CIRCADIAN_LIGHTING_UPDATE_TOPIC, DATA_CIRCADIAN_LIGHTING - -import voluptuous as vol - import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP, + ATTR_RGB_COLOR, + ATTR_TRANSITION, + ATTR_WHITE_VALUE, + ATTR_XY_COLOR, +) +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.components.light import VALID_TRANSITION, is_on +from homeassistant.const import ( + ATTR_ENTITY_ID, + CONF_NAME, + CONF_PLATFORM, + SERVICE_TURN_ON, + STATE_ON, +) from homeassistant.helpers.dispatcher import dispatcher_connect from homeassistant.helpers.event import track_state_change from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.components.light import ( - is_on, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION, - VALID_TRANSITION, ATTR_WHITE_VALUE, ATTR_XY_COLOR, DOMAIN as LIGHT_DOMAIN) - +from homeassistant.util import slugify +from homeassistant.util.color import ( + color_RGB_to_xy, + color_temperature_kelvin_to_mired, + color_temperature_to_rgb, + color_xy_to_hs, +) + +from custom_components.circadian_lighting import ( + CIRCADIAN_LIGHTING_UPDATE_TOPIC, + DATA_CIRCADIAN_LIGHTING, + DOMAIN, +) + try: from homeassistant.components.switch import SwitchEntity except ImportError: from homeassistant.components.switch import SwitchDevice as SwitchEntity - -from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_NAME, CONF_PLATFORM, STATE_ON, - SERVICE_TURN_ON) -from homeassistant.util import slugify -from homeassistant.util.color import ( - color_RGB_to_xy, color_temperature_kelvin_to_mired, - color_temperature_to_rgb, color_xy_to_hs) + _LOGGER = logging.getLogger(__name__) -ICON = 'mdi:theme-light-dark' +ICON = "mdi:theme-light-dark" -CONF_LIGHTS_CT = 'lights_ct' -CONF_LIGHTS_RGB = 'lights_rgb' -CONF_LIGHTS_XY = 'lights_xy' -CONF_LIGHTS_BRIGHT = 'lights_brightness' -CONF_DISABLE_BRIGHTNESS_ADJUST = 'disable_brightness_adjust' -CONF_MIN_BRIGHT = 'min_brightness' +CONF_LIGHTS_CT = "lights_ct" +CONF_LIGHTS_RGB = "lights_rgb" +CONF_LIGHTS_XY = "lights_xy" +CONF_LIGHTS_BRIGHT = "lights_brightness" +CONF_DISABLE_BRIGHTNESS_ADJUST = "disable_brightness_adjust" +CONF_MIN_BRIGHT = "min_brightness" DEFAULT_MIN_BRIGHT = 1 -CONF_MAX_BRIGHT = 'max_brightness' +CONF_MAX_BRIGHT = "max_brightness" DEFAULT_MAX_BRIGHT = 100 -CONF_SLEEP_ENTITY = 'sleep_entity' -CONF_SLEEP_STATE = 'sleep_state' -CONF_SLEEP_CT = 'sleep_colortemp' -CONF_SLEEP_BRIGHT = 'sleep_brightness' -CONF_DISABLE_ENTITY = 'disable_entity' -CONF_DISABLE_STATE = 'disable_state' -CONF_INITIAL_TRANSITION = 'initial_transition' +CONF_SLEEP_ENTITY = "sleep_entity" +CONF_SLEEP_STATE = "sleep_state" +CONF_SLEEP_CT = "sleep_colortemp" +CONF_SLEEP_BRIGHT = "sleep_brightness" +CONF_DISABLE_ENTITY = "disable_entity" +CONF_DISABLE_STATE = "disable_state" +CONF_INITIAL_TRANSITION = "initial_transition" DEFAULT_INITIAL_TRANSITION = 1 -PLATFORM_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'circadian_lighting', - vol.Optional(CONF_NAME, default="Circadian Lighting"): cv.string, - vol.Optional(CONF_LIGHTS_CT): cv.entity_ids, - vol.Optional(CONF_LIGHTS_RGB): cv.entity_ids, - vol.Optional(CONF_LIGHTS_XY): cv.entity_ids, - vol.Optional(CONF_LIGHTS_BRIGHT): cv.entity_ids, - vol.Optional(CONF_DISABLE_BRIGHTNESS_ADJUST, default=False): cv.boolean, - vol.Optional(CONF_MIN_BRIGHT, default=DEFAULT_MIN_BRIGHT): - vol.All(vol.Coerce(int), vol.Range(min=1, max=100)), - vol.Optional(CONF_MAX_BRIGHT, default=DEFAULT_MAX_BRIGHT): - vol.All(vol.Coerce(int), vol.Range(min=1, max=100)), - vol.Optional(CONF_SLEEP_ENTITY): cv.entity_id, - vol.Optional(CONF_SLEEP_STATE): cv.string, - vol.Optional(CONF_SLEEP_CT): - vol.All(vol.Coerce(int), vol.Range(min=1000, max=10000)), - vol.Optional(CONF_SLEEP_BRIGHT): - vol.All(vol.Coerce(int), vol.Range(min=1, max=100)), - vol.Optional(CONF_DISABLE_ENTITY): cv.entity_id, - vol.Optional(CONF_DISABLE_STATE): cv.string, - vol.Optional(CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION): - VALID_TRANSITION -}) +PLATFORM_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "circadian_lighting", + vol.Optional(CONF_NAME, default="Circadian Lighting"): cv.string, + vol.Optional(CONF_LIGHTS_CT): cv.entity_ids, + vol.Optional(CONF_LIGHTS_RGB): cv.entity_ids, + vol.Optional(CONF_LIGHTS_XY): cv.entity_ids, + vol.Optional(CONF_LIGHTS_BRIGHT): cv.entity_ids, + vol.Optional(CONF_DISABLE_BRIGHTNESS_ADJUST, default=False): cv.boolean, + vol.Optional(CONF_MIN_BRIGHT, default=DEFAULT_MIN_BRIGHT): vol.All( + vol.Coerce(int), vol.Range(min=1, max=100) + ), + vol.Optional(CONF_MAX_BRIGHT, default=DEFAULT_MAX_BRIGHT): vol.All( + vol.Coerce(int), vol.Range(min=1, max=100) + ), + vol.Optional(CONF_SLEEP_ENTITY): cv.entity_id, + vol.Optional(CONF_SLEEP_STATE): cv.string, + vol.Optional(CONF_SLEEP_CT): vol.All( + vol.Coerce(int), vol.Range(min=1000, max=10000) + ), + vol.Optional(CONF_SLEEP_BRIGHT): vol.All( + vol.Coerce(int), vol.Range(min=1, max=100) + ), + vol.Optional(CONF_DISABLE_ENTITY): cv.entity_id, + vol.Optional(CONF_DISABLE_STATE): cv.string, + vol.Optional( + CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION + ): VALID_TRANSITION, + } +) + def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Circadian Lighting switches.""" @@ -96,15 +121,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None): disable_entity = config.get(CONF_DISABLE_ENTITY) disable_state = config.get(CONF_DISABLE_STATE) initial_transition = config.get(CONF_INITIAL_TRANSITION) - cs = CircadianSwitch(hass, cl, name, lights_ct, lights_rgb, lights_xy, lights_brightness, - disable_brightness_adjust, min_brightness, max_brightness, - sleep_entity, sleep_state, sleep_colortemp, sleep_brightness, - disable_entity, disable_state, initial_transition) + cs = CircadianSwitch( + hass, + cl, + name, + lights_ct, + lights_rgb, + lights_xy, + lights_brightness, + disable_brightness_adjust, + min_brightness, + max_brightness, + sleep_entity, + sleep_state, + sleep_colortemp, + sleep_brightness, + disable_entity, + disable_state, + initial_transition, + ) add_devices([cs]) def update(call=None): """Update lights.""" cs.update_switch() + return True else: return False @@ -113,15 +154,33 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class CircadianSwitch(SwitchEntity, RestoreEntity): """Representation of a Circadian Lighting switch.""" - def __init__(self, hass, cl, name, lights_ct, lights_rgb, lights_xy, lights_brightness, - disable_brightness_adjust, min_brightness, max_brightness, - sleep_entity, sleep_state, sleep_colortemp, sleep_brightness, - disable_entity, disable_state, initial_transition): + def __init__( + self, + hass, + cl, + name, + lights_ct, + lights_rgb, + lights_xy, + lights_brightness, + disable_brightness_adjust, + min_brightness, + max_brightness, + sleep_entity, + sleep_state, + sleep_colortemp, + sleep_brightness, + disable_entity, + disable_state, + initial_transition, + ): """Initialize the Circadian Lighting switch.""" self.hass = hass self._cl = cl self._name = name - self._entity_id = "switch." + slugify("{} {}".format('circadian_lighting', name)) + self._entity_id = "switch." + slugify( + "{} {}".format("circadian_lighting", name) + ) self._state = None self._icon = ICON self._hs_color = None @@ -140,8 +199,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._disable_state = disable_state self._initial_transition = initial_transition self._attributes = {} - self._attributes['hs_color'] = self._hs_color - self._attributes['brightness'] = None + self._attributes["hs_color"] = self._hs_color + self._attributes["brightness"] = None self._lights = [] if lights_ct != None: @@ -214,25 +273,28 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._state = False self.schedule_update_ha_state() self._hs_color = None - self._attributes['hs_color'] = self._hs_color - self._attributes['brightness'] = None + self._attributes["hs_color"] = self._hs_color + self._attributes["brightness"] = None def is_sleep(self): - return self._sleep_entity is not None and self.hass.states.get(self._sleep_entity).state == self._sleep_state + return ( + self._sleep_entity is not None + and self.hass.states.get(self._sleep_entity).state == self._sleep_state + ) def calc_ct(self): if self.is_sleep(): _LOGGER.debug(self._name + " in Sleep mode") return color_temperature_kelvin_to_mired(self._sleep_colortemp) else: - return color_temperature_kelvin_to_mired(self._cl.data['colortemp']) + return color_temperature_kelvin_to_mired(self._cl.data["colortemp"]) def calc_rgb(self): if self.is_sleep(): _LOGGER.debug(self._name + " in Sleep mode") return color_temperature_to_rgb(self._sleep_colortemp) else: - return color_temperature_to_rgb(self._cl.data['colortemp']) + return color_temperature_to_rgb(self._cl.data["colortemp"]) def calc_xy(self): return color_RGB_to_xy(*self.calc_rgb()) @@ -248,16 +310,19 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): _LOGGER.debug(self._name + " in Sleep mode") return self._sleep_brightness else: - if self._cl.data['percent'] > 0: + if self._cl.data["percent"] > 0: return self._max_brightness else: - return ((self._max_brightness - self._min_brightness) * ((100+self._cl.data['percent']) / 100)) + self._min_brightness + return ( + (self._max_brightness - self._min_brightness) + * ((100 + self._cl.data["percent"]) / 100) + ) + self._min_brightness def update_switch(self, transition=None): if self._cl.data is not None: self._hs_color = self.calc_hs() - self._attributes['hs_color'] = self._hs_color - self._attributes['brightness'] = self.calc_brightness() + self._attributes["hs_color"] = self._hs_color + self._attributes["brightness"] = self.calc_brightness() _LOGGER.debug(self._name + " Switch Updated") self.adjust_lights(self._lights, transition) @@ -269,7 +334,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): elif self._cl.data is None: _LOGGER.debug(self._name + " could not retrieve Circadian Lighting data") return False - elif self._disable_entity is not None and self.hass.states.get(self._disable_entity).state == self._disable_state: + elif ( + self._disable_entity is not None + and self.hass.states.get(self._disable_entity).state == self._disable_state + ): _LOGGER.debug(self._name + " disabled by " + str(self._disable_entity)) return False else: @@ -278,16 +346,28 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def adjust_lights(self, lights, transition=None): if self.should_adjust(): if transition == None: - transition = self._cl.data['transition'] + transition = self._cl.data["transition"] - brightness = int((self._attributes['brightness'] / 100) * 254) if self._attributes['brightness'] is not None else None + brightness = ( + int((self._attributes["brightness"] / 100) * 254) + if self._attributes["brightness"] is not None + else None + ) mired = int(self.calc_ct()) if self._lights_ct is not None else None - rgb = tuple(map(int, self.calc_rgb())) if self._lights_rgb is not None else None + rgb = ( + tuple(map(int, self.calc_rgb())) + if self._lights_rgb is not None + else None + ) xy = self.calc_xy() if self._lights_xy is not None else None for light in lights: """Set color of array of ct light if on.""" - if self._lights_ct is not None and light in self._lights_ct and is_on(self.hass, light): + if ( + self._lights_ct is not None + and light in self._lights_ct + and is_on(self.hass, light) + ): service_data = {ATTR_ENTITY_ID: light} if mired is not None: service_data[ATTR_COLOR_TEMP] = mired @@ -295,12 +375,23 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): service_data[ATTR_BRIGHTNESS] = brightness if transition is not None: service_data[ATTR_TRANSITION] = transition - self.hass.services.call( - LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug(light + " CT Adjusted - color_temp: " + str(mired) + ", brightness: " + str(brightness) + ", transition: " + str(transition)) + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + _LOGGER.debug( + light + + " CT Adjusted - color_temp: " + + str(mired) + + ", brightness: " + + str(brightness) + + ", transition: " + + str(transition) + ) """Set color of array of rgb light if on.""" - if self._lights_rgb is not None and light in self._lights_rgb and is_on(self.hass, light): + if ( + self._lights_rgb is not None + and light in self._lights_rgb + and is_on(self.hass, light) + ): service_data = {ATTR_ENTITY_ID: light} if rgb is not None: service_data[ATTR_RGB_COLOR] = rgb @@ -308,12 +399,23 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): service_data[ATTR_BRIGHTNESS] = brightness if transition is not None: service_data[ATTR_TRANSITION] = transition - self.hass.services.call( - LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug(light + " RGB Adjusted - rgb_color: " + str(rgb) + ", brightness: " + str(brightness) + ", transition: " + str(transition)) + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + _LOGGER.debug( + light + + " RGB Adjusted - rgb_color: " + + str(rgb) + + ", brightness: " + + str(brightness) + + ", transition: " + + str(transition) + ) """Set color of array of xy light if on.""" - if self._lights_xy is not None and light in self._lights_xy and is_on(self.hass, light): + if ( + self._lights_xy is not None + and light in self._lights_xy + and is_on(self.hass, light) + ): service_data = {ATTR_ENTITY_ID: light} if xy is not None: service_data[ATTR_XY_COLOR] = xy @@ -322,40 +424,67 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): service_data[ATTR_WHITE_VALUE] = brightness if transition is not None: service_data[ATTR_TRANSITION] = transition - self.hass.services.call( - LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug(light + " XY Adjusted - xy_color: " + str(xy) + ", brightness: " + str(brightness) + ", transition: " + str(transition) + ", white_value: " + str(brightness)) + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + _LOGGER.debug( + light + + " XY Adjusted - xy_color: " + + str(xy) + + ", brightness: " + + str(brightness) + + ", transition: " + + str(transition) + + ", white_value: " + + str(brightness) + ) """Set color of array of brightness light if on.""" - if self._lights_brightness is not None and light in self._lights_brightness and is_on(self.hass, light): + if ( + self._lights_brightness is not None + and light in self._lights_brightness + and is_on(self.hass, light) + ): service_data = {ATTR_ENTITY_ID: light} if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness if transition is not None: service_data[ATTR_TRANSITION] = transition - self.hass.services.call( - LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug(light + " Brightness Adjusted - brightness: " + str(brightness) + ", transition: " + str(transition)) + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + _LOGGER.debug( + light + + " Brightness Adjusted - brightness: " + + str(brightness) + + ", transition: " + + str(transition) + ) def light_state_changed(self, entity_id, from_state, to_state): try: - _LOGGER.debug(entity_id + " change from " + str(from_state) + " to " + str(to_state)) - if to_state.state == 'on' and from_state.state != 'on': + _LOGGER.debug( + entity_id + " change from " + str(from_state) + " to " + str(to_state) + ) + if to_state.state == "on" and from_state.state != "on": self.adjust_lights([entity_id], self._initial_transition) except: pass def sleep_state_changed(self, entity_id, from_state, to_state): try: - _LOGGER.debug(entity_id + " change from " + str(from_state) + " to " + str(to_state)) - if to_state.state == self._sleep_state or from_state.state == self._sleep_state: + _LOGGER.debug( + entity_id + " change from " + str(from_state) + " to " + str(to_state) + ) + if ( + to_state.state == self._sleep_state + or from_state.state == self._sleep_state + ): self.update_switch(self._initial_transition) except: pass - + def disable_state_changed(self, entity_id, from_state, to_state): try: - _LOGGER.debug(entity_id + " change from " + str(from_state) + " to " + str(to_state)) + _LOGGER.debug( + entity_id + " change from " + str(from_state) + " to " + str(to_state) + ) if from_state.state == self._disable_state: self.update_switch(self._initial_transition) except: From f7119aebaf49462abcad528dd973f28385a7ec09 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:10:41 +0200 Subject: [PATCH 02/69] use 'is None' and 'is not None' --- custom_components/circadian_lighting/switch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 59aca5a9..c184fb22 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -203,13 +203,13 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._attributes["brightness"] = None self._lights = [] - if lights_ct != None: + if lights_ct is not None: self._lights += lights_ct - if lights_rgb != None: + if lights_rgb is not None: self._lights += lights_rgb - if lights_xy != None: + if lights_xy is not None: self._lights += lights_xy - if lights_brightness != None: + if lights_brightness is not None: self._lights += lights_brightness """Register callbacks.""" From 5448caced2d641e0e8d7b951215f524e1889990f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:16:20 +0200 Subject: [PATCH 03/69] use f-strings in logs --- .../circadian_lighting/switch.py | 64 ++++++------------- 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index c184fb22..c031b3d9 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -284,14 +284,14 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def calc_ct(self): if self.is_sleep(): - _LOGGER.debug(self._name + " in Sleep mode") + _LOGGER.debug(f"{self._name} in Sleep mode") return color_temperature_kelvin_to_mired(self._sleep_colortemp) else: return color_temperature_kelvin_to_mired(self._cl.data["colortemp"]) def calc_rgb(self): if self.is_sleep(): - _LOGGER.debug(self._name + " in Sleep mode") + _LOGGER.debug(f"{self._name} in Sleep mode") return color_temperature_to_rgb(self._sleep_colortemp) else: return color_temperature_to_rgb(self._cl.data["colortemp"]) @@ -307,7 +307,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return None else: if self.is_sleep(): - _LOGGER.debug(self._name + " in Sleep mode") + _LOGGER.debug(f"{self._name} in Sleep mode") return self._sleep_brightness else: if self._cl.data["percent"] > 0: @@ -323,29 +323,29 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._hs_color = self.calc_hs() self._attributes["hs_color"] = self._hs_color self._attributes["brightness"] = self.calc_brightness() - _LOGGER.debug(self._name + " Switch Updated") + _LOGGER.debug(f"{self._name} Switch Updated") self.adjust_lights(self._lights, transition) def should_adjust(self): if self._state is not True: - _LOGGER.debug(self._name + " off - not adjusting") + _LOGGER.debug(f"{self._name} off - not adjusting") return False elif self._cl.data is None: - _LOGGER.debug(self._name + " could not retrieve Circadian Lighting data") + _LOGGER.debug(f"{self._name} could not retrieve Circadian Lighting data") return False elif ( self._disable_entity is not None and self.hass.states.get(self._disable_entity).state == self._disable_state ): - _LOGGER.debug(self._name + " disabled by " + str(self._disable_entity)) + _LOGGER.debug(f"{self._name} disabled by " + str(self._disable_entity)) return False else: return True def adjust_lights(self, lights, transition=None): if self.should_adjust(): - if transition == None: + if transition is None: transition = self._cl.data["transition"] brightness = ( @@ -377,13 +377,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): service_data[ATTR_TRANSITION] = transition self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) _LOGGER.debug( - light - + " CT Adjusted - color_temp: " - + str(mired) - + ", brightness: " - + str(brightness) - + ", transition: " - + str(transition) + f"{light} CT Adjusted - color_temp: {mired}, " + f"brightness: {brightness}, transition: {transition}" ) """Set color of array of rgb light if on.""" @@ -401,13 +396,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): service_data[ATTR_TRANSITION] = transition self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) _LOGGER.debug( - light - + " RGB Adjusted - rgb_color: " - + str(rgb) - + ", brightness: " - + str(brightness) - + ", transition: " - + str(transition) + f"{light} RGB Adjusted - rgb_color: {rgb}, " + f"brightness: {brightness}, transition: {transition}" ) """Set color of array of xy light if on.""" @@ -426,15 +416,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): service_data[ATTR_TRANSITION] = transition self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) _LOGGER.debug( - light - + " XY Adjusted - xy_color: " - + str(xy) - + ", brightness: " - + str(brightness) - + ", transition: " - + str(transition) - + ", white_value: " - + str(brightness) + f"{light} XY Adjusted - xy_color: {xy}, brightness: {brightness}, " + f"transition: {transition}, white_value: {brightness}" ) """Set color of array of brightness light if on.""" @@ -450,18 +433,13 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): service_data[ATTR_TRANSITION] = transition self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) _LOGGER.debug( - light - + " Brightness Adjusted - brightness: " - + str(brightness) - + ", transition: " - + str(transition) + f"{light} Brightness Adjusted - brightness: {brightness}, " + f"transition: {transition}" ) def light_state_changed(self, entity_id, from_state, to_state): try: - _LOGGER.debug( - entity_id + " change from " + str(from_state) + " to " + str(to_state) - ) + _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") if to_state.state == "on" and from_state.state != "on": self.adjust_lights([entity_id], self._initial_transition) except: @@ -469,9 +447,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def sleep_state_changed(self, entity_id, from_state, to_state): try: - _LOGGER.debug( - entity_id + " change from " + str(from_state) + " to " + str(to_state) - ) + _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") if ( to_state.state == self._sleep_state or from_state.state == self._sleep_state @@ -482,9 +458,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def disable_state_changed(self, entity_id, from_state, to_state): try: - _LOGGER.debug( - entity_id + " change from " + str(from_state) + " to " + str(to_state) - ) + _LOGGER.debug("{entity_id} change from {from_state} to {to_state}") if from_state.state == self._disable_state: self.update_switch(self._initial_transition) except: From 26796a449d8cb7267983c32610d6449dd5e1ebc5 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:17:16 +0200 Subject: [PATCH 04/69] use contextlib.suppress --- custom_components/circadian_lighting/switch.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index c031b3d9..3486e4f5 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -5,6 +5,7 @@ Circadian Lighting Switch for Home-Assistant. DEPENDENCIES = ["circadian_lighting", "light"] import logging +from contextlib import suppress import homeassistant.helpers.config_validation as cv import voluptuous as vol @@ -438,28 +439,22 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): ) def light_state_changed(self, entity_id, from_state, to_state): - try: + with suppress(Exception): _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") if to_state.state == "on" and from_state.state != "on": self.adjust_lights([entity_id], self._initial_transition) - except: - pass def sleep_state_changed(self, entity_id, from_state, to_state): - try: + with suppress(Exception): _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") if ( to_state.state == self._sleep_state or from_state.state == self._sleep_state ): self.update_switch(self._initial_transition) - except: - pass def disable_state_changed(self, entity_id, from_state, to_state): - try: + with suppress(Exception): _LOGGER.debug("{entity_id} change from {from_state} to {to_state}") if from_state.state == self._disable_state: self.update_switch(self._initial_transition) - except: - pass From ce7e08d8249f8b5ee33e76173d80e004083a738b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:18:21 +0200 Subject: [PATCH 05/69] fix last f-string --- custom_components/circadian_lighting/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 3486e4f5..3aaa4951 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -339,7 +339,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._disable_entity is not None and self.hass.states.get(self._disable_entity).state == self._disable_state ): - _LOGGER.debug(f"{self._name} disabled by " + str(self._disable_entity)) + _LOGGER.debug(f"{self._name} disabled by {self._disable_entity}") return False else: return True From 5f1c7ad82760dcca67880a304935512062f8deb4 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:18:46 +0200 Subject: [PATCH 06/69] remove unused import --- custom_components/circadian_lighting/switch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 3aaa4951..7750ea6e 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -40,7 +40,6 @@ from homeassistant.util.color import ( from custom_components.circadian_lighting import ( CIRCADIAN_LIGHTING_UPDATE_TOPIC, DATA_CIRCADIAN_LIGHTING, - DOMAIN, ) try: From 860557830178cc96419e09c5a3711c23daa8c75d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:21:48 +0200 Subject: [PATCH 07/69] more simplifications --- custom_components/circadian_lighting/switch.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 7750ea6e..e33053e9 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -178,9 +178,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self.hass = hass self._cl = cl self._name = name - self._entity_id = "switch." + slugify( - "{} {}".format("circadian_lighting", name) - ) + self._entity_id = "switch." + slugify(f"circadian_lighting {name}") self._state = None self._icon = ICON self._hs_color = None @@ -198,9 +196,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._disable_entity = disable_entity self._disable_state = disable_state self._initial_transition = initial_transition - self._attributes = {} - self._attributes["hs_color"] = self._hs_color - self._attributes["brightness"] = None + self._attributes = {"hs_color": self._hs_color, "brightness": None} self._lights = [] if lights_ct is not None: From 56a5a49f64e04d99d9d9256b3557acc10b994a87 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:23:12 +0200 Subject: [PATCH 08/69] simplify calc_brightness --- .../circadian_lighting/switch.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index e33053e9..b7c1f3c4 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -301,18 +301,16 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def calc_brightness(self): if self._disable_brightness_adjust is True: return None + elif self.is_sleep(): + _LOGGER.debug(f"{self._name} in Sleep mode") + return self._sleep_brightness + elif self._cl.data["percent"] > 0: + return self._max_brightness else: - if self.is_sleep(): - _LOGGER.debug(f"{self._name} in Sleep mode") - return self._sleep_brightness - else: - if self._cl.data["percent"] > 0: - return self._max_brightness - else: - return ( - (self._max_brightness - self._min_brightness) - * ((100 + self._cl.data["percent"]) / 100) - ) + self._min_brightness + return ( + (self._max_brightness - self._min_brightness) + * ((100 + self._cl.data["percent"]) / 100) + ) + self._min_brightness def update_switch(self, transition=None): if self._cl.data is not None: From 97598cfaac7060117f809518174c313bac134ff2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:25:36 +0200 Subject: [PATCH 09/69] reduce indentation of adjust_lights --- .../circadian_lighting/switch.py | 172 +++++++++--------- 1 file changed, 85 insertions(+), 87 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index b7c1f3c4..6af78d16 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -338,98 +338,96 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return True def adjust_lights(self, lights, transition=None): - if self.should_adjust(): - if transition is None: - transition = self._cl.data["transition"] + if not self.should_adjust(): + return - brightness = ( - int((self._attributes["brightness"] / 100) * 254) - if self._attributes["brightness"] is not None - else None - ) - mired = int(self.calc_ct()) if self._lights_ct is not None else None - rgb = ( - tuple(map(int, self.calc_rgb())) - if self._lights_rgb is not None - else None - ) - xy = self.calc_xy() if self._lights_xy is not None else None + if transition is None: + transition = self._cl.data["transition"] - for light in lights: - """Set color of array of ct light if on.""" - if ( - self._lights_ct is not None - and light in self._lights_ct - and is_on(self.hass, light) - ): - service_data = {ATTR_ENTITY_ID: light} - if mired is not None: - service_data[ATTR_COLOR_TEMP] = mired - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = brightness - if transition is not None: - service_data[ATTR_TRANSITION] = transition - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug( - f"{light} CT Adjusted - color_temp: {mired}, " - f"brightness: {brightness}, transition: {transition}" - ) + brightness = ( + int((self._attributes["brightness"] / 100) * 254) + if self._attributes["brightness"] is not None + else None + ) + mired = int(self.calc_ct()) if self._lights_ct is not None else None + rgb = tuple(map(int, self.calc_rgb())) if self._lights_rgb is not None else None + xy = self.calc_xy() if self._lights_xy is not None else None - """Set color of array of rgb light if on.""" - if ( - self._lights_rgb is not None - and light in self._lights_rgb - and is_on(self.hass, light) - ): - service_data = {ATTR_ENTITY_ID: light} - if rgb is not None: - service_data[ATTR_RGB_COLOR] = rgb - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = brightness - if transition is not None: - service_data[ATTR_TRANSITION] = transition - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug( - f"{light} RGB Adjusted - rgb_color: {rgb}, " - f"brightness: {brightness}, transition: {transition}" - ) + for light in lights: + """Set color of array of ct light if on.""" + if ( + self._lights_ct is not None + and light in self._lights_ct + and is_on(self.hass, light) + ): + service_data = {ATTR_ENTITY_ID: light} + if mired is not None: + service_data[ATTR_COLOR_TEMP] = mired + if brightness is not None: + service_data[ATTR_BRIGHTNESS] = brightness + if transition is not None: + service_data[ATTR_TRANSITION] = transition + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + _LOGGER.debug( + f"{light} CT Adjusted - color_temp: {mired}, " + f"brightness: {brightness}, transition: {transition}" + ) - """Set color of array of xy light if on.""" - if ( - self._lights_xy is not None - and light in self._lights_xy - and is_on(self.hass, light) - ): - service_data = {ATTR_ENTITY_ID: light} - if xy is not None: - service_data[ATTR_XY_COLOR] = xy - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = brightness - service_data[ATTR_WHITE_VALUE] = brightness - if transition is not None: - service_data[ATTR_TRANSITION] = transition - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug( - f"{light} XY Adjusted - xy_color: {xy}, brightness: {brightness}, " - f"transition: {transition}, white_value: {brightness}" - ) + """Set color of array of rgb light if on.""" + if ( + self._lights_rgb is not None + and light in self._lights_rgb + and is_on(self.hass, light) + ): + service_data = {ATTR_ENTITY_ID: light} + if rgb is not None: + service_data[ATTR_RGB_COLOR] = rgb + if brightness is not None: + service_data[ATTR_BRIGHTNESS] = brightness + if transition is not None: + service_data[ATTR_TRANSITION] = transition + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + _LOGGER.debug( + f"{light} RGB Adjusted - rgb_color: {rgb}, " + f"brightness: {brightness}, transition: {transition}" + ) - """Set color of array of brightness light if on.""" - if ( - self._lights_brightness is not None - and light in self._lights_brightness - and is_on(self.hass, light) - ): - service_data = {ATTR_ENTITY_ID: light} - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = brightness - if transition is not None: - service_data[ATTR_TRANSITION] = transition - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug( - f"{light} Brightness Adjusted - brightness: {brightness}, " - f"transition: {transition}" - ) + """Set color of array of xy light if on.""" + if ( + self._lights_xy is not None + and light in self._lights_xy + and is_on(self.hass, light) + ): + service_data = {ATTR_ENTITY_ID: light} + if xy is not None: + service_data[ATTR_XY_COLOR] = xy + if brightness is not None: + service_data[ATTR_BRIGHTNESS] = brightness + service_data[ATTR_WHITE_VALUE] = brightness + if transition is not None: + service_data[ATTR_TRANSITION] = transition + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + _LOGGER.debug( + f"{light} XY Adjusted - xy_color: {xy}, brightness: {brightness}, " + f"transition: {transition}, white_value: {brightness}" + ) + + """Set color of array of brightness light if on.""" + if ( + self._lights_brightness is not None + and light in self._lights_brightness + and is_on(self.hass, light) + ): + service_data = {ATTR_ENTITY_ID: light} + if brightness is not None: + service_data[ATTR_BRIGHTNESS] = brightness + if transition is not None: + service_data[ATTR_TRANSITION] = transition + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + _LOGGER.debug( + f"{light} Brightness Adjusted - brightness: {brightness}, " + f"transition: {transition}" + ) def light_state_changed(self, entity_id, from_state, to_state): with suppress(Exception): From e381d0c0e395f67736a2da55d0225eeef1f12b15 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:27:48 +0200 Subject: [PATCH 10/69] simplify conditions in adjust_lights --- .../circadian_lighting/switch.py | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 6af78d16..34c97121 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -354,12 +354,11 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): xy = self.calc_xy() if self._lights_xy is not None else None for light in lights: - """Set color of array of ct light if on.""" - if ( - self._lights_ct is not None - and light in self._lights_ct - and is_on(self.hass, light) - ): + if not is_on(self.hass, light): + continue + + # Set color of array of ct. + if self._lights_ct is not None and light in self._lights_ct: service_data = {ATTR_ENTITY_ID: light} if mired is not None: service_data[ATTR_COLOR_TEMP] = mired @@ -373,12 +372,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): f"brightness: {brightness}, transition: {transition}" ) - """Set color of array of rgb light if on.""" - if ( - self._lights_rgb is not None - and light in self._lights_rgb - and is_on(self.hass, light) - ): + # Set color of array of rgb. + if self._lights_rgb is not None and light in self._lights_rgb: service_data = {ATTR_ENTITY_ID: light} if rgb is not None: service_data[ATTR_RGB_COLOR] = rgb @@ -392,12 +387,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): f"brightness: {brightness}, transition: {transition}" ) - """Set color of array of xy light if on.""" - if ( - self._lights_xy is not None - and light in self._lights_xy - and is_on(self.hass, light) - ): + # Set color of array of xy. + if self._lights_xy is not None and light in self._lights_xy: service_data = {ATTR_ENTITY_ID: light} if xy is not None: service_data[ATTR_XY_COLOR] = xy @@ -412,12 +403,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): f"transition: {transition}, white_value: {brightness}" ) - """Set color of array of brightness light if on.""" - if ( - self._lights_brightness is not None - and light in self._lights_brightness - and is_on(self.hass, light) - ): + # Set color of array of brightness. + if self._lights_brightness is not None and light in self._lights_brightness: service_data = {ATTR_ENTITY_ID: light} if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness From 34c4fdb71a770d26bfcf0095bc1e847619414350 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:30:08 +0200 Subject: [PATCH 11/69] don't run if statements that will never run --- custom_components/circadian_lighting/switch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 34c97121..6cf89b52 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -373,7 +373,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): ) # Set color of array of rgb. - if self._lights_rgb is not None and light in self._lights_rgb: + elif self._lights_rgb is not None and light in self._lights_rgb: service_data = {ATTR_ENTITY_ID: light} if rgb is not None: service_data[ATTR_RGB_COLOR] = rgb @@ -388,7 +388,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): ) # Set color of array of xy. - if self._lights_xy is not None and light in self._lights_xy: + elif self._lights_xy is not None and light in self._lights_xy: service_data = {ATTR_ENTITY_ID: light} if xy is not None: service_data[ATTR_XY_COLOR] = xy @@ -404,7 +404,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): ) # Set color of array of brightness. - if self._lights_brightness is not None and light in self._lights_brightness: + elif self._lights_brightness is not None and light in self._lights_brightness: service_data = {ATTR_ENTITY_ID: light} if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness From 38dc367c565c5f738941db4351162be6ff7f26e8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:39:13 +0200 Subject: [PATCH 12/69] simplify adjust_lights even more --- .../circadian_lighting/switch.py | 48 +++++++------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 6cf89b52..474e2ed1 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -357,64 +357,50 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if not is_on(self.hass, light): continue + which = None + service_data = {ATTR_ENTITY_ID: light} + if transition is not None: + service_data[ATTR_TRANSITION] = transition + # Set color of array of ct. if self._lights_ct is not None and light in self._lights_ct: - service_data = {ATTR_ENTITY_ID: light} + which = "CT" if mired is not None: service_data[ATTR_COLOR_TEMP] = mired if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness - if transition is not None: - service_data[ATTR_TRANSITION] = transition - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug( - f"{light} CT Adjusted - color_temp: {mired}, " - f"brightness: {brightness}, transition: {transition}" - ) # Set color of array of rgb. elif self._lights_rgb is not None and light in self._lights_rgb: - service_data = {ATTR_ENTITY_ID: light} + which = "RGB" if rgb is not None: service_data[ATTR_RGB_COLOR] = rgb if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness - if transition is not None: - service_data[ATTR_TRANSITION] = transition - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug( - f"{light} RGB Adjusted - rgb_color: {rgb}, " - f"brightness: {brightness}, transition: {transition}" - ) # Set color of array of xy. elif self._lights_xy is not None and light in self._lights_xy: - service_data = {ATTR_ENTITY_ID: light} + which = "XY" if xy is not None: service_data[ATTR_XY_COLOR] = xy if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness service_data[ATTR_WHITE_VALUE] = brightness - if transition is not None: - service_data[ATTR_TRANSITION] = transition - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug( - f"{light} XY Adjusted - xy_color: {xy}, brightness: {brightness}, " - f"transition: {transition}, white_value: {brightness}" - ) # Set color of array of brightness. - elif self._lights_brightness is not None and light in self._lights_brightness: - service_data = {ATTR_ENTITY_ID: light} + elif ( + self._lights_brightness is not None and light in self._lights_brightness + ): + which = "Brightness" if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness - if transition is not None: - service_data[ATTR_TRANSITION] = transition + + if which is not None: self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug( - f"{light} Brightness Adjusted - brightness: {brightness}, " - f"transition: {transition}" + msg = ", ".join( + [f"{k}: v" for k, v in d.items() if k != ATTR_ENTITY_ID] ) + _LOGGER.debug(f"{light} {which} Adjusted - {msg}") def light_state_changed(self, entity_id, from_state, to_state): with suppress(Exception): From 8a49a6fe87edd011766808b634b6213a5b92a0da Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:41:58 +0200 Subject: [PATCH 13/69] use extend instead of += --- custom_components/circadian_lighting/switch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 474e2ed1..01c1ad66 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -200,13 +200,13 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._lights = [] if lights_ct is not None: - self._lights += lights_ct + self._lights.extend(lights_ct) if lights_rgb is not None: - self._lights += lights_rgb + self._lights.extend(lights_rgb) if lights_xy is not None: - self._lights += lights_xy + self._lights.extend(lights_xy) if lights_brightness is not None: - self._lights += lights_brightness + self._lights.extend(lights_brightness) """Register callbacks.""" dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_switch) From 3ea906ea2e058fdf68628a44af939618fe63ee71 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:43:07 +0200 Subject: [PATCH 14/69] use comment when inline --- custom_components/circadian_lighting/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 01c1ad66..c42a531e 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -208,7 +208,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if lights_brightness is not None: self._lights.extend(lights_brightness) - """Register callbacks.""" + # Register callbacks dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_switch) track_state_change(hass, self._lights, self.light_state_changed) if self._sleep_entity is not None: From 643d69bbf855f6789baac2aa07c28f5e4bd5f1fd Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:45:10 +0200 Subject: [PATCH 15/69] do is sleep logging in is_sleep method --- custom_components/circadian_lighting/switch.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index c42a531e..139a6c06 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -273,21 +273,23 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._attributes["brightness"] = None def is_sleep(self): - return ( + is_sleep = ( self._sleep_entity is not None and self.hass.states.get(self._sleep_entity).state == self._sleep_state ) + if is_sleep: + _LOGGER.debug(f"{self._name} in Sleep mode") + + return is_sleep def calc_ct(self): if self.is_sleep(): - _LOGGER.debug(f"{self._name} in Sleep mode") return color_temperature_kelvin_to_mired(self._sleep_colortemp) else: return color_temperature_kelvin_to_mired(self._cl.data["colortemp"]) def calc_rgb(self): if self.is_sleep(): - _LOGGER.debug(f"{self._name} in Sleep mode") return color_temperature_to_rgb(self._sleep_colortemp) else: return color_temperature_to_rgb(self._cl.data["colortemp"]) @@ -302,7 +304,6 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if self._disable_brightness_adjust is True: return None elif self.is_sleep(): - _LOGGER.debug(f"{self._name} in Sleep mode") return self._sleep_brightness elif self._cl.data["percent"] > 0: return self._max_brightness From 3be5f4448876f0ace7f712154d5d621bfeca99ce Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:46:52 +0200 Subject: [PATCH 16/69] simplify calc_ct and calc_rgb --- custom_components/circadian_lighting/switch.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 139a6c06..76aaa6f7 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -283,16 +283,11 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return is_sleep def calc_ct(self): - if self.is_sleep(): - return color_temperature_kelvin_to_mired(self._sleep_colortemp) - else: - return color_temperature_kelvin_to_mired(self._cl.data["colortemp"]) + col_temp = self._sleep_colortemp if self.is_sleep() else self._cl.data["colortemp"] + return color_temperature_kelvin_to_mired(col_temp) def calc_rgb(self): - if self.is_sleep(): - return color_temperature_to_rgb(self._sleep_colortemp) - else: - return color_temperature_to_rgb(self._cl.data["colortemp"]) + return self.calc_ct() def calc_xy(self): return color_RGB_to_xy(*self.calc_rgb()) From c0082175b6679e4ca4850ffac7dff4aadbc4d011 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:52:23 +0200 Subject: [PATCH 17/69] simplify debug logging call --- .../circadian_lighting/__init__.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 79d8de1d..2aca343d 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -325,11 +325,13 @@ class CircadianLighting(object): "solar_midnight" ].timestamp() - _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)) + _LOGGER.debug( + f"now_seconds: {now_seconds}, " + f"sunrise_seconds: {sunrise_seconds}, " + f"sunset_seconds: {sunset_seconds}, " + f"solar_midnight_seconds: {solar_midnight_seconds}, " + f"solar_noon_seconds: {solar_noon_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 @@ -363,12 +365,9 @@ class CircadianLighting(object): 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)) + _LOGGER.debug( + f"h: {h}, k: {k}, x: {x}, y: {y}, a: {a}, percentage: {percentage}" + ) return percentage From d2dda06802f64d7636aff33650b50a00c314bce5 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:52:48 +0200 Subject: [PATCH 18/69] remove unused import --- custom_components/circadian_lighting/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 2aca343d..af76c78e 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -28,7 +28,7 @@ Technical notes: I had to make a lot of assumptions when writing this app """ import logging -from datetime import datetime, timedelta +from datetime import timedelta import homeassistant.helpers.config_validation as cv import voluptuous as vol From 5b98fedf1264c31ede7245a40987436dfd1de6e0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:52:58 +0200 Subject: [PATCH 19/69] black --- custom_components/circadian_lighting/switch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 76aaa6f7..5491758f 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -283,7 +283,9 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return is_sleep def calc_ct(self): - col_temp = self._sleep_colortemp if self.is_sleep() else self._cl.data["colortemp"] + col_temp = ( + self._sleep_colortemp if self.is_sleep() else self._cl.data["colortemp"] + ) return color_temperature_kelvin_to_mired(col_temp) def calc_rgb(self): From 1053e749562259f53368a14924c89682f0256944 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:55:33 +0200 Subject: [PATCH 20/69] don't assign variables but directly pass them --- .../circadian_lighting/switch.py | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 5491758f..7d2fc80d 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -106,39 +106,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Circadian Lighting switches.""" cl = hass.data.get(DATA_CIRCADIAN_LIGHTING) if cl: - lights_ct = config.get(CONF_LIGHTS_CT) - lights_rgb = config.get(CONF_LIGHTS_RGB) - lights_xy = config.get(CONF_LIGHTS_XY) - lights_brightness = config.get(CONF_LIGHTS_BRIGHT) - disable_brightness_adjust = config.get(CONF_DISABLE_BRIGHTNESS_ADJUST) - name = config.get(CONF_NAME) - min_brightness = config.get(CONF_MIN_BRIGHT) - max_brightness = config.get(CONF_MAX_BRIGHT) - sleep_entity = config.get(CONF_SLEEP_ENTITY) - sleep_state = config.get(CONF_SLEEP_STATE) - sleep_colortemp = config.get(CONF_SLEEP_CT) - sleep_brightness = config.get(CONF_SLEEP_BRIGHT) - disable_entity = config.get(CONF_DISABLE_ENTITY) - disable_state = config.get(CONF_DISABLE_STATE) - initial_transition = config.get(CONF_INITIAL_TRANSITION) cs = CircadianSwitch( hass, cl, - name, - lights_ct, - lights_rgb, - lights_xy, - lights_brightness, - disable_brightness_adjust, - min_brightness, - max_brightness, - sleep_entity, - sleep_state, - sleep_colortemp, - sleep_brightness, - disable_entity, - disable_state, - initial_transition, + name=config.get(CONF_NAME), + lights_ct=config.get(CONF_LIGHTS_CT), + lights_rgb=config.get(CONF_LIGHTS_RGB), + lights_xy=config.get(CONF_LIGHTS_XY), + lights_brightness=config.get(CONF_LIGHTS_BRIGHT), + disable_brightness_adjust=config.get(CONF_DISABLE_BRIGHTNESS_ADJUST), + min_brightness=config.get(CONF_MIN_BRIGHT), + max_brightness=config.get(CONF_MAX_BRIGHT), + sleep_entity=config.get(CONF_SLEEP_ENTITY), + sleep_state=config.get(CONF_SLEEP_STATE), + sleep_colortemp=config.get(CONF_SLEEP_CT), + sleep_brightness=config.get(CONF_SLEEP_BRIGHT), + disable_entity=config.get(CONF_DISABLE_ENTITY), + disable_state=config.get(CONF_DISABLE_STATE), + initial_transition=config.get(CONF_INITIAL_TRANSITION), ) add_devices([cs]) From 2d064e519500fa5b5728bb933524586efb5900c3 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:59:04 +0200 Subject: [PATCH 21/69] fix in calc_rgb --- custom_components/circadian_lighting/switch.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 7d2fc80d..96afdfbd 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -267,14 +267,15 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return is_sleep + @property + def _color_temperature(self): + return self._sleep_colortemp if self.is_sleep() else self._cl.data["colortemp"] + def calc_ct(self): - col_temp = ( - self._sleep_colortemp if self.is_sleep() else self._cl.data["colortemp"] - ) - return color_temperature_kelvin_to_mired(col_temp) + return color_temperature_kelvin_to_mired(self._color_temperature) def calc_rgb(self): - return self.calc_ct() + return color_temperature_to_rgb(self._color_temperature) def calc_xy(self): return color_RGB_to_xy(*self.calc_rgb()) From 0780879a0b9cd99f7323d21cc02d33dec2b3a185 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 19:02:18 +0200 Subject: [PATCH 22/69] simplify setup() --- .../circadian_lighting/__init__.py | 40 ++++++------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index af76c78e..ec13e7ac 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -104,39 +104,23 @@ CONFIG_SCHEMA = vol.Schema( def setup(hass, config): """Set up the Circadian Lighting component.""" conf = config[DOMAIN] - min_colortemp = conf.get(CONF_MIN_CT) - max_colortemp = conf.get(CONF_MAX_CT) - sunrise_offset = conf.get(CONF_SUNRISE_OFFSET) - sunset_offset = conf.get(CONF_SUNSET_OFFSET) - sunrise_time = conf.get(CONF_SUNRISE_TIME) - sunset_time = conf.get(CONF_SUNSET_TIME) - - latitude = conf.get(CONF_LATITUDE, hass.config.latitude) - longitude = conf.get(CONF_LONGITUDE, hass.config.longitude) - elevation = conf.get(CONF_ELEVATION, hass.config.elevation) - load_platform(hass, "sensor", DOMAIN, {}, config) - interval = conf.get(CONF_INTERVAL) - transition = conf.get(ATTR_TRANSITION) - - cl = CircadianLighting( + hass.data[DATA_CIRCADIAN_LIGHTING] = CircadianLighting( hass, - min_colortemp, - max_colortemp, - sunrise_offset, - sunset_offset, - sunrise_time, - sunset_time, - latitude, - longitude, - elevation, - interval, - transition, + min_colortemp=conf.get(CONF_MIN_CT), + max_colortemp=conf.get(CONF_MAX_CT), + sunrise_offset=conf.get(CONF_SUNRISE_OFFSET), + sunset_offset=conf.get(CONF_SUNSET_OFFSET), + sunrise_time=conf.get(CONF_SUNRISE_TIME), + sunset_time=conf.get(CONF_SUNSET_TIME), + latitude=conf.get(CONF_LATITUDE, hass.config.latitude), + longitude=conf.get(CONF_LONGITUDE, hass.config.longitude), + elevation=conf.get(CONF_ELEVATION, hass.config.elevation), + interval=conf.get(CONF_INTERVAL), + transition=conf.get(ATTR_TRANSITION), ) - hass.data[DATA_CIRCADIAN_LIGHTING] = cl - return True From b3e48344212dadaeaa77558e753cecf0927f2e24 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 19:06:00 +0200 Subject: [PATCH 23/69] simplify time.replace calls --- .../circadian_lighting/__init__.py | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index ec13e7ac..b7e196c4 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -196,6 +196,14 @@ class CircadianLighting(object): _LOGGER.debug("Timezone: " + str(timezone)) return timezone + def _time_dict(self, key): + return dict( + hour=int(self.data[key].strftime("%H")), + minute=int(self.data[key].strftime("%M")), + second=int(self.data[key].strftime("%S")), + microsecond=int(self.data[key].strftime("%f")), + ) + def get_sunrise_sunset(self, date=None): if ( self.data["sunrise_time"] is not None @@ -203,18 +211,8 @@ class CircadianLighting(object): ): if date is None: date = dt_now(self.data["timezone"]) - sunrise = date.replace( - hour=int(self.data["sunrise_time"].strftime("%H")), - minute=int(self.data["sunrise_time"].strftime("%M")), - second=int(self.data["sunrise_time"].strftime("%S")), - microsecond=int(self.data["sunrise_time"].strftime("%f")), - ) - sunset = date.replace( - hour=int(self.data["sunset_time"].strftime("%H")), - minute=int(self.data["sunset_time"].strftime("%M")), - second=int(self.data["sunset_time"].strftime("%S")), - microsecond=int(self.data["sunset_time"].strftime("%f")), - ) + sunrise = date.replace(**self._time_dict("sunrise_time")) + sunset = date.replace(**self._time_dict("sunset_time")) solar_noon = sunrise + (sunset - sunrise) / 2 solar_midnight = sunset + ((sunrise + timedelta(days=1)) - sunset) / 2 else: @@ -230,23 +228,13 @@ class CircadianLighting(object): if self.data["sunrise_time"] is not None: if date is None: date = dt_now(self.data["timezone"]) - sunrise = date.replace( - hour=int(self.data["sunrise_time"].strftime("%H")), - minute=int(self.data["sunrise_time"].strftime("%M")), - second=int(self.data["sunrise_time"].strftime("%S")), - microsecond=int(self.data["sunrise_time"].strftime("%f")), - ) + sunrise = date.replace(**self._time_dict("sunrise_time")) else: sunrise = location.sunrise(date) if self.data["sunset_time"] is not None: if date is None: date = dt_now(self.data["timezone"]) - sunset = date.replace( - hour=int(self.data["sunset_time"].strftime("%H")), - minute=int(self.data["sunset_time"].strftime("%M")), - second=int(self.data["sunset_time"].strftime("%S")), - microsecond=int(self.data["sunset_time"].strftime("%f")), - ) + sunset = date.replace(**self._time_dict("sunset_time")) else: sunset = location.sunset(date) solar_noon = location.solar_noon(date) From c75b1238d84d6c851fe060b12e056e510fe67842 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 19:12:37 +0200 Subject: [PATCH 24/69] simplify track_time_change setup --- .../circadian_lighting/__init__.py | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index b7e196c4..38f48dae 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -164,26 +164,20 @@ class CircadianLighting(object): self.update = Throttle(timedelta(seconds=interval))(self._update) - if self.data["sunrise_time"] is not None: - track_time_change( - self.hass, - self._update, - hour=int(self.data["sunrise_time"].strftime("%H")), - minute=int(self.data["sunrise_time"].strftime("%M")), - second=int(self.data["sunrise_time"].strftime("%S")), - ) - else: - track_sunrise(self.hass, self._update, self.data["sunrise_offset"]) - if self.data["sunset_time"] is not None: - track_time_change( - self.hass, - self._update, - hour=int(self.data["sunset_time"].strftime("%H")), - minute=int(self.data["sunset_time"].strftime("%M")), - second=int(self.data["sunset_time"].strftime("%S")), - ) - else: - track_sunset(self.hass, self._update, self.data["sunset_offset"]) + for which in ["sunrise", "sunrise"]: + time = self.data[f"{which}_time"] + if time is not None: + track_time_change( + self.hass, + self._update, + hour=int(time.strftime("%H")), + minute=int(time.strftime("%M")), + second=int(time.strftime("%S")), + ) + elif which == "sunrise": + track_sunrise(self.hass, self._update, self.data["sunrise_offset"]) + elif which == "sunset": + track_sunset(self.hass, self._update, self.data["sunset_offset"]) def get_timezone(self): from timezonefinder import TimezoneFinder From be0e58c3f01b0e2b8f0405f6c8e6a4cc962b5ca7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 19:14:02 +0200 Subject: [PATCH 25/69] move comments --- custom_components/circadian_lighting/__init__.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 38f48dae..715125c6 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -258,9 +258,8 @@ class CircadianLighting(object): solar_noon_seconds = today_sun_times["solar_noon"].timestamp() solar_midnight_seconds = today_sun_times["solar_midnight"].timestamp() - if ( - now < today_sun_times[SUN_EVENT_SUNRISE] - ): # It's before sunrise (after midnight) + 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)) _LOGGER.debug("yesterday_sun_times: " + str(yesterday_sun_times)) @@ -274,9 +273,8 @@ class CircadianLighting(object): solar_midnight_seconds = yesterday_sun_times[ "solar_midnight" ].timestamp() - elif ( - now > today_sun_times[SUN_EVENT_SUNSET] - ): # It's after sunset (before midnight) + 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)) _LOGGER.debug("tomorrow_sun_times: " + str(tomorrow_sun_times)) From 2f5b92f80a96a85717a43551815ba686fed361bd Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Aug 2020 22:23:31 +0200 Subject: [PATCH 26/69] fix variable d -> service_data --- custom_components/circadian_lighting/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 96afdfbd..aae9bad8 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -382,7 +382,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if which is not None: self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) msg = ", ".join( - [f"{k}: v" for k, v in d.items() if k != ATTR_ENTITY_ID] + [f"{k}: {v}" for k, v in service_data.items() if k != ATTR_ENTITY_ID] ) _LOGGER.debug(f"{light} {which} Adjusted - {msg}") From 6050e991892711ff013004e9a79279181fc71841 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Aug 2020 23:18:04 +0200 Subject: [PATCH 27/69] move lines into if statement --- custom_components/circadian_lighting/switch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index aae9bad8..6ee48d0d 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -333,9 +333,6 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if self._attributes["brightness"] is not None else None ) - mired = int(self.calc_ct()) if self._lights_ct is not None else None - rgb = tuple(map(int, self.calc_rgb())) if self._lights_rgb is not None else None - xy = self.calc_xy() if self._lights_xy is not None else None for light in lights: if not is_on(self.hass, light): @@ -349,6 +346,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): # Set color of array of ct. if self._lights_ct is not None and light in self._lights_ct: which = "CT" + mired = int(self.calc_ct()) if self._lights_ct is not None else None if mired is not None: service_data[ATTR_COLOR_TEMP] = mired if brightness is not None: @@ -357,6 +355,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): # Set color of array of rgb. elif self._lights_rgb is not None and light in self._lights_rgb: which = "RGB" + rgb = tuple(map(int, self.calc_rgb())) if self._lights_rgb is not None else None if rgb is not None: service_data[ATTR_RGB_COLOR] = rgb if brightness is not None: @@ -365,6 +364,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): # Set color of array of xy. elif self._lights_xy is not None and light in self._lights_xy: which = "XY" + xy = self.calc_xy() if self._lights_xy is not None else None if xy is not None: service_data[ATTR_XY_COLOR] = xy if brightness is not None: From 5491c7dcf173a9b34166c1d07475808bc8bb2417 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Aug 2020 23:22:38 +0200 Subject: [PATCH 28/69] simplify color settings --- custom_components/circadian_lighting/switch.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 6ee48d0d..9c5f1dd8 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -346,27 +346,21 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): # Set color of array of ct. if self._lights_ct is not None and light in self._lights_ct: which = "CT" - mired = int(self.calc_ct()) if self._lights_ct is not None else None - if mired is not None: - service_data[ATTR_COLOR_TEMP] = mired + service_data[ATTR_COLOR_TEMP] = int(self.calc_ct()) if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness # Set color of array of rgb. elif self._lights_rgb is not None and light in self._lights_rgb: which = "RGB" - rgb = tuple(map(int, self.calc_rgb())) if self._lights_rgb is not None else None - if rgb is not None: - service_data[ATTR_RGB_COLOR] = rgb + service_data[ATTR_RGB_COLOR] = tuple(map(int, self.calc_rgb())) if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness # Set color of array of xy. elif self._lights_xy is not None and light in self._lights_xy: which = "XY" - xy = self.calc_xy() if self._lights_xy is not None else None - if xy is not None: - service_data[ATTR_XY_COLOR] = xy + service_data[ATTR_XY_COLOR] = self.calc_xy() if brightness is not None: service_data[ATTR_BRIGHTNESS] = brightness service_data[ATTR_WHITE_VALUE] = brightness From c5e4486f00ff1f47e8d3f93fe222ee473bfa6536 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Aug 2020 23:32:32 +0200 Subject: [PATCH 29/69] simplify setting brightness --- .../circadian_lighting/switch.py | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 9c5f1dd8..536ac024 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -328,18 +328,16 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if transition is None: transition = self._cl.data["transition"] - brightness = ( - int((self._attributes["brightness"] / 100) * 254) - if self._attributes["brightness"] is not None - else None - ) - for light in lights: if not is_on(self.hass, light): continue which = None service_data = {ATTR_ENTITY_ID: light} + if self._attributes["brightness"] is not None: + service_data[ATTR_BRIGHTNESS] = int( + (self._attributes["brightness"] / 100) * 254 + ) if transition is not None: service_data[ATTR_TRANSITION] = transition @@ -347,37 +345,31 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if self._lights_ct is not None and light in self._lights_ct: which = "CT" service_data[ATTR_COLOR_TEMP] = int(self.calc_ct()) - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = brightness # Set color of array of rgb. elif self._lights_rgb is not None and light in self._lights_rgb: which = "RGB" service_data[ATTR_RGB_COLOR] = tuple(map(int, self.calc_rgb())) - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = brightness # Set color of array of xy. elif self._lights_xy is not None and light in self._lights_xy: which = "XY" service_data[ATTR_XY_COLOR] = self.calc_xy() - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = brightness - service_data[ATTR_WHITE_VALUE] = brightness + if service_data.get(ATTR_BRIGHTNESS, False): + service_data[ATTR_WHITE_VALUE] = service_data[ATTR_BRIGHTNESS] # Set color of array of brightness. elif ( self._lights_brightness is not None and light in self._lights_brightness ): which = "Brightness" - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = brightness if which is not None: self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - msg = ", ".join( - [f"{k}: {v}" for k, v in service_data.items() if k != ATTR_ENTITY_ID] - ) + key_value_strings = [ + f"{k}: {v}" for k, v in service_data.items() if k != ATTR_ENTITY_ID + ] + msg = ", ".join(key_value_strings) _LOGGER.debug(f"{light} {which} Adjusted - {msg}") def light_state_changed(self, entity_id, from_state, to_state): From b7daa6c05c9617b890475941811304fb3d8d3572 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Aug 2020 23:35:22 +0200 Subject: [PATCH 30/69] make light_* empty lists instead of None --- .../circadian_lighting/switch.py | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 536ac024..e64ce37b 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -110,10 +110,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hass, cl, name=config.get(CONF_NAME), - lights_ct=config.get(CONF_LIGHTS_CT), - lights_rgb=config.get(CONF_LIGHTS_RGB), - lights_xy=config.get(CONF_LIGHTS_XY), - lights_brightness=config.get(CONF_LIGHTS_BRIGHT), + lights_ct=config.get(CONF_LIGHTS_CT, []), + lights_rgb=config.get(CONF_LIGHTS_RGB, []), + lights_xy=config.get(CONF_LIGHTS_XY, []), + lights_brightness=config.get(CONF_LIGHTS_BRIGHT, []), disable_brightness_adjust=config.get(CONF_DISABLE_BRIGHTNESS_ADJUST), min_brightness=config.get(CONF_MIN_BRIGHT), max_brightness=config.get(CONF_MAX_BRIGHT), @@ -183,15 +183,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._initial_transition = initial_transition self._attributes = {"hs_color": self._hs_color, "brightness": None} - self._lights = [] - if lights_ct is not None: - self._lights.extend(lights_ct) - if lights_rgb is not None: - self._lights.extend(lights_rgb) - if lights_xy is not None: - self._lights.extend(lights_xy) - if lights_brightness is not None: - self._lights.extend(lights_brightness) + self._lights = lights_ct + lights_rgb + lights_xy + lights_brightness # Register callbacks dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_switch) @@ -342,26 +334,24 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): service_data[ATTR_TRANSITION] = transition # Set color of array of ct. - if self._lights_ct is not None and light in self._lights_ct: + if light in self._lights_ct: which = "CT" service_data[ATTR_COLOR_TEMP] = int(self.calc_ct()) # Set color of array of rgb. - elif self._lights_rgb is not None and light in self._lights_rgb: + elif light in self._lights_rgb: which = "RGB" service_data[ATTR_RGB_COLOR] = tuple(map(int, self.calc_rgb())) # Set color of array of xy. - elif self._lights_xy is not None and light in self._lights_xy: + elif light in self._lights_xy: which = "XY" service_data[ATTR_XY_COLOR] = self.calc_xy() if service_data.get(ATTR_BRIGHTNESS, False): service_data[ATTR_WHITE_VALUE] = service_data[ATTR_BRIGHTNESS] # Set color of array of brightness. - elif ( - self._lights_brightness is not None and light in self._lights_brightness - ): + elif light in self._lights_brightness: which = "Brightness" if which is not None: From 88d355561422d1df6f402f0e397a829a05126d1c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Aug 2020 23:40:59 +0200 Subject: [PATCH 31/69] assign brightness variable --- custom_components/circadian_lighting/switch.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index e64ce37b..080f14d9 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -326,10 +326,9 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): which = None service_data = {ATTR_ENTITY_ID: light} - if self._attributes["brightness"] is not None: - service_data[ATTR_BRIGHTNESS] = int( - (self._attributes["brightness"] / 100) * 254 - ) + brightness = self._attributes["brightness"] + if brightness is not None: + service_data[ATTR_BRIGHTNESS] = int((brightness / 100) * 254) if transition is not None: service_data[ATTR_TRANSITION] = transition From 7ef47b19c8b285444dddd0f2ff4878fab02a551c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 27 Aug 2020 23:47:28 +0200 Subject: [PATCH 32/69] which can never not be set --- custom_components/circadian_lighting/switch.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 080f14d9..e3fc2fea 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -324,7 +324,6 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if not is_on(self.hass, light): continue - which = None service_data = {ATTR_ENTITY_ID: light} brightness = self._attributes["brightness"] if brightness is not None: @@ -353,13 +352,12 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): elif light in self._lights_brightness: which = "Brightness" - if which is not None: - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - key_value_strings = [ - f"{k}: {v}" for k, v in service_data.items() if k != ATTR_ENTITY_ID - ] - msg = ", ".join(key_value_strings) - _LOGGER.debug(f"{light} {which} Adjusted - {msg}") + self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) + key_value_strings = [ + f"{k}: {v}" for k, v in service_data.items() if k != ATTR_ENTITY_ID + ] + msg = ", ".join(key_value_strings) + _LOGGER.debug(f"{light} {which} Adjusted - {msg}") def light_state_changed(self, entity_id, from_state, to_state): with suppress(Exception): From 2f9a6d7706cf526716095da9b8f463f81ad1b8b9 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 00:06:34 +0200 Subject: [PATCH 33/69] implement once_only and remove _attributes --- .../circadian_lighting/switch.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index e3fc2fea..e3b8f951 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -6,6 +6,7 @@ DEPENDENCIES = ["circadian_lighting", "light"] import logging from contextlib import suppress +from typing import Optional import homeassistant.helpers.config_validation as cv import voluptuous as vol @@ -69,6 +70,7 @@ CONF_DISABLE_ENTITY = "disable_entity" CONF_DISABLE_STATE = "disable_state" CONF_INITIAL_TRANSITION = "initial_transition" DEFAULT_INITIAL_TRANSITION = 1 +CONF_ONCE_ONLY = "once_only" PLATFORM_SCHEMA = vol.Schema( { @@ -98,6 +100,7 @@ PLATFORM_SCHEMA = vol.Schema( vol.Optional( CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION ): VALID_TRANSITION, + vol.Optional(CONF_ONCE_ONLY): cv.bool, } ) @@ -124,13 +127,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): disable_entity=config.get(CONF_DISABLE_ENTITY), disable_state=config.get(CONF_DISABLE_STATE), initial_transition=config.get(CONF_INITIAL_TRANSITION), + once_only=config.get(CONF_ONCE_ONLY), ) add_devices([cs]) - def update(call=None): - """Update lights.""" - cs.update_switch() - return True else: return False @@ -158,6 +158,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): disable_entity, disable_state, initial_transition, + once_only, ): """Initialize the Circadian Lighting switch.""" self.hass = hass @@ -181,12 +182,12 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._disable_entity = disable_entity self._disable_state = disable_state self._initial_transition = initial_transition - self._attributes = {"hs_color": self._hs_color, "brightness": None} + self._once_only = once_only self._lights = lights_ct + lights_rgb + lights_xy + lights_brightness # Register callbacks - dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_switch) + dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self._update_switch) track_state_change(hass, self._lights, self.light_state_changed) if self._sleep_entity is not None: track_state_change(hass, self._sleep_entity, self.sleep_state_changed) @@ -230,14 +231,14 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): @property def device_state_attributes(self): """Return the attributes of the switch.""" - return self._attributes + return {"hs_color": self._hs_color, "brightness": self._brightness} def turn_on(self, **kwargs): """Turn on circadian lighting.""" self._state = True # Make initial update - self.update_switch(self._initial_transition) + self._update_switch(self._initial_transition, force=True) self.schedule_update_ha_state() @@ -246,8 +247,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._state = False self.schedule_update_ha_state() self._hs_color = None - self._attributes["hs_color"] = self._hs_color - self._attributes["brightness"] = None + self._brightness = None def is_sleep(self): is_sleep = ( @@ -283,16 +283,16 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): elif self._cl.data["percent"] > 0: return self._max_brightness else: - return ( - (self._max_brightness - self._min_brightness) - * ((100 + self._cl.data["percent"]) / 100) - ) + self._min_brightness + delta_brightness = self._max_brightness - self._min_brightness + procent = (100 + self._cl.data["percent"]) / 100 + return (delta_brightness * procent) + self._min_brightness - def update_switch(self, transition=None): + def _update_switch(self, transition=None, force=False): + if self._once_only and not force: + return if self._cl.data is not None: self._hs_color = self.calc_hs() - self._attributes["hs_color"] = self._hs_color - self._attributes["brightness"] = self.calc_brightness() + self._brightness = self.calc_brightness() _LOGGER.debug(f"{self._name} Switch Updated") self.adjust_lights(self._lights, transition) @@ -325,7 +325,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): continue service_data = {ATTR_ENTITY_ID: light} - brightness = self._attributes["brightness"] + brightness = self._brightness if brightness is not None: service_data[ATTR_BRIGHTNESS] = int((brightness / 100) * 254) if transition is not None: @@ -372,10 +372,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): to_state.state == self._sleep_state or from_state.state == self._sleep_state ): - self.update_switch(self._initial_transition) + self._update_switch(self._initial_transition, force=True) def disable_state_changed(self, entity_id, from_state, to_state): with suppress(Exception): _LOGGER.debug("{entity_id} change from {from_state} to {to_state}") if from_state.state == self._disable_state: - self.update_switch(self._initial_transition) + self._update_switch(self._initial_transition, force=True) From dfa0f8cc609d6f1500c3ff253f86c4ffebf80d6b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 00:15:32 +0200 Subject: [PATCH 34/69] renames --- custom_components/circadian_lighting/__init__.py | 6 ++---- custom_components/circadian_lighting/sensor.py | 3 +-- custom_components/circadian_lighting/switch.py | 9 ++++----- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 715125c6..d4398cc8 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -57,9 +57,7 @@ VERSION = "1.0.13" _LOGGER = logging.getLogger(__name__) DOMAIN = "circadian_lighting" -CIRCADIAN_LIGHTING_PLATFORMS = ["sensor", "switch"] -CIRCADIAN_LIGHTING_UPDATE_TOPIC = "{}_update".format(DOMAIN) -DATA_CIRCADIAN_LIGHTING = "data_cl" +CIRCADIAN_LIGHTING_UPDATE_TOPIC = f"{DOMAIN}_update" CONF_MIN_CT = "min_colortemp" DEFAULT_MIN_CT = 2500 @@ -106,7 +104,7 @@ def setup(hass, config): conf = config[DOMAIN] load_platform(hass, "sensor", DOMAIN, {}, config) - hass.data[DATA_CIRCADIAN_LIGHTING] = CircadianLighting( + hass.data[DOMAIN] = CircadianLighting( hass, min_colortemp=conf.get(CONF_MIN_CT), max_colortemp=conf.get(CONF_MAX_CT), diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index dc6cd738..0d3016c1 100644 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -12,7 +12,6 @@ from homeassistant.helpers.entity import Entity from custom_components.circadian_lighting import ( CIRCADIAN_LIGHTING_UPDATE_TOPIC, - DATA_CIRCADIAN_LIGHTING, DOMAIN, ) @@ -23,7 +22,7 @@ ICON = "mdi:theme-light-dark" def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Circadian Lighting sensor.""" - cl = hass.data.get(DATA_CIRCADIAN_LIGHTING) + cl = hass.data.get(DOMAIN) if cl: cs = CircadianSensor(hass, cl) add_devices([cs]) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index e3b8f951..189c30fd 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -6,7 +6,6 @@ DEPENDENCIES = ["circadian_lighting", "light"] import logging from contextlib import suppress -from typing import Optional import homeassistant.helpers.config_validation as cv import voluptuous as vol @@ -40,7 +39,7 @@ from homeassistant.util.color import ( from custom_components.circadian_lighting import ( CIRCADIAN_LIGHTING_UPDATE_TOPIC, - DATA_CIRCADIAN_LIGHTING, + DOMAIN, ) try: @@ -107,9 +106,9 @@ PLATFORM_SCHEMA = vol.Schema( def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Circadian Lighting switches.""" - cl = hass.data.get(DATA_CIRCADIAN_LIGHTING) + cl = hass.data.get(DOMAIN) if cl: - cs = CircadianSwitch( + switch = CircadianSwitch( hass, cl, name=config.get(CONF_NAME), @@ -129,7 +128,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): initial_transition=config.get(CONF_INITIAL_TRANSITION), once_only=config.get(CONF_ONCE_ONLY), ) - add_devices([cs]) + add_devices([switch]) return True else: From d4551746c62cb93811db2e32643c1e93910d7b45 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 00:20:25 +0200 Subject: [PATCH 35/69] do not define a .data dict --- .../circadian_lighting/__init__.py | 88 +++++++++---------- .../circadian_lighting/sensor.py | 20 ++--- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index d4398cc8..7207fa2c 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -142,23 +142,23 @@ class CircadianLighting(object): ): self.hass = hass self.data = {} - self.data["min_colortemp"] = min_colortemp - self.data["max_colortemp"] = max_colortemp - self.data["sunrise_offset"] = sunrise_offset - self.data["sunset_offset"] = sunset_offset + self._min_colortemp = min_colortemp + self._max_colortemp = max_colortemp + self._sunrise_offset = sunrise_offset + self._sunset_offset = sunset_offset self.data["sunrise_time"] = sunrise_time self.data["sunset_time"] = sunset_time - self.data["latitude"] = latitude - self.data["longitude"] = longitude - self.data["elevation"] = elevation - self.data["interval"] = interval - self.data["transition"] = transition - self.data["timezone"] = self.get_timezone() - self.data["percent"] = self.calc_percent() - self.data["colortemp"] = self.calc_colortemp() - self.data["rgb_color"] = self.calc_rgb() - self.data["xy_color"] = self.calc_xy() - self.data["hs_color"] = self.calc_hs() + self._latitude = latitude + self._longitude = longitude + self._elevation = elevation + self._interval = interval + self._transition = transition + self._timezone = self.get_timezone() + self._percent = self.calc_percent() + self._colortemp = self.calc_colortemp() + self._rgb_color = self.calc_rgb() + self._xy_color = self.calc_xy() + self._hs_color = self.calc_hs() self.update = Throttle(timedelta(seconds=interval))(self._update) @@ -173,16 +173,16 @@ class CircadianLighting(object): second=int(time.strftime("%S")), ) elif which == "sunrise": - track_sunrise(self.hass, self._update, self.data["sunrise_offset"]) + track_sunrise(self.hass, self._update, self._sunrise_offset) elif which == "sunset": - track_sunset(self.hass, self._update, self.data["sunset_offset"]) + track_sunset(self.hass, self._update, self._sunset_offset) def get_timezone(self): from timezonefinder import TimezoneFinder tf = TimezoneFinder() timezone_string = tf.timezone_at( - lng=self.data["longitude"], lat=self.data["latitude"] + lng=self._longitude, lat=self._latitude ) timezone = get_time_zone(timezone_string) _LOGGER.debug("Timezone: " + str(timezone)) @@ -202,7 +202,7 @@ class CircadianLighting(object): and self.data["sunset_time"] is not None ): if date is None: - date = dt_now(self.data["timezone"]) + date = dt_now(self._timezone) sunrise = date.replace(**self._time_dict("sunrise_time")) sunset = date.replace(**self._time_dict("sunset_time")) solar_noon = sunrise + (sunset - sunrise) / 2 @@ -213,37 +213,37 @@ class CircadianLighting(object): location = astral.Location() location.name = "name" location.region = "region" - location.latitude = self.data["latitude"] - location.longitude = self.data["longitude"] - location.elevation = self.data["elevation"] + location.latitude = self._latitude + location.longitude = self._longitude + location.elevation = self._elevation _LOGGER.debug("Astral location: " + str(location)) if self.data["sunrise_time"] is not None: if date is None: - date = dt_now(self.data["timezone"]) + date = dt_now(self._timezone) sunrise = date.replace(**self._time_dict("sunrise_time")) else: sunrise = location.sunrise(date) if self.data["sunset_time"] is not None: if date is None: - date = dt_now(self.data["timezone"]) + date = dt_now(self._timezone) sunset = date.replace(**self._time_dict("sunset_time")) else: sunset = location.sunset(date) solar_noon = location.solar_noon(date) solar_midnight = location.solar_midnight(date) - if self.data["sunrise_offset"] is not None: - sunrise = sunrise + self.data["sunrise_offset"] - if self.data["sunset_offset"] is not None: - sunset = sunset + self.data["sunset_offset"] + if self._sunrise_offset is not None: + sunrise = sunrise + self._sunrise_offset + if self._sunset_offset is not None: + sunset = sunset + self._sunset_offset return { - SUN_EVENT_SUNRISE: sunrise.astimezone(self.data["timezone"]), - SUN_EVENT_SUNSET: sunset.astimezone(self.data["timezone"]), - "solar_noon": solar_noon.astimezone(self.data["timezone"]), - "solar_midnight": solar_midnight.astimezone(self.data["timezone"]), + SUN_EVENT_SUNRISE: sunrise.astimezone(self._timezone), + SUN_EVENT_SUNSET: sunset.astimezone(self._timezone), + "solar_noon": solar_noon.astimezone(self._timezone), + "solar_midnight": solar_midnight.astimezone(self._timezone), } def calc_percent(self): - now = dt_now(self.data["timezone"]) + now = dt_now(self._timezone) _LOGGER.debug("now: " + str(now)) today_sun_times = self.get_sunrise_sunset(now) @@ -334,16 +334,16 @@ class CircadianLighting(object): return percentage def calc_colortemp(self): - if self.data["percent"] > 0: + if self._percent > 0: return ( - (self.data["max_colortemp"] - self.data["min_colortemp"]) - * (self.data["percent"] / 100) - ) + self.data["min_colortemp"] + (self._max_colortemp - self._min_colortemp) + * (self._percent / 100) + ) + self._min_colortemp else: - return self.data["min_colortemp"] + return self._min_colortemp def calc_rgb(self): - return color_temperature_to_rgb(self.data["colortemp"]) + return color_temperature_to_rgb(self._colortemp) def calc_xy(self): rgb = self.calc_rgb() @@ -362,10 +362,10 @@ class CircadianLighting(object): def _update(self, *args, **kwargs): """Update Circadian Values.""" - self.data["percent"] = self.calc_percent() - self.data["colortemp"] = self.calc_colortemp() - self.data["rgb_color"] = self.calc_rgb() - self.data["xy_color"] = self.calc_xy() - self.data["hs_color"] = self.calc_hs() + self._percent = self.calc_percent() + self._colortemp = self.calc_colortemp() + self._rgb_color = self.calc_rgb() + self._xy_color = self.calc_xy() + self._hs_color = self.calc_hs() dispatcher_send(self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC) _LOGGER.debug("Circadian Lighting Component Updated") diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index 0d3016c1..6cad713d 100644 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -46,14 +46,14 @@ class CircadianSensor(Entity): self._cl = cl self._name = "Circadian Values" self._entity_id = "sensor.circadian_values" - self._state = self._cl.data["percent"] + self._state = self._cl._percent self._unit_of_measurement = "%" self._icon = ICON - self._hs_color = self._cl.data["hs_color"] + self._hs_color = self._cl._hs_color self._attributes = {} - self._attributes["colortemp"] = self._cl.data["colortemp"] - self._attributes["rgb_color"] = self._cl.data["rgb_color"] - self._attributes["xy_color"] = self._cl.data["xy_color"] + self._attributes["colortemp"] = self._cl._colortemp + self._attributes["rgb_color"] = self._cl._rgb_color + self._attributes["xy_color"] = self._cl._xy_color """Register callbacks.""" dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_sensor) @@ -101,9 +101,9 @@ class CircadianSensor(Entity): def update_sensor(self): if self._cl.data is not None: - self._state = self._cl.data["percent"] - self._hs_color = self._cl.data["hs_color"] - self._attributes["colortemp"] = self._cl.data["colortemp"] - self._attributes["rgb_color"] = self._cl.data["rgb_color"] - self._attributes["xy_color"] = self._cl.data["xy_color"] + self._state = self._cl._percent + self._hs_color = self._cl._hs_color + self._attributes["colortemp"] = self._cl._colortemp + self._attributes["rgb_color"] = self._cl._rgb_color + self._attributes["xy_color"] = self._cl._xy_color _LOGGER.debug("Circadian Lighting Sensor Updated") From a59b03eb6781f6726867ce33e3cf3fc23d5b60c4 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 00:22:32 +0200 Subject: [PATCH 36/69] remove ._attributes --- .../circadian_lighting/sensor.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index 6cad713d..f3e98113 100644 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -50,12 +50,11 @@ class CircadianSensor(Entity): self._unit_of_measurement = "%" self._icon = ICON self._hs_color = self._cl._hs_color - self._attributes = {} - self._attributes["colortemp"] = self._cl._colortemp - self._attributes["rgb_color"] = self._cl._rgb_color - self._attributes["xy_color"] = self._cl._xy_color + self._colortemp = self._cl._colortemp + self._rgb_color = self._cl._rgb_color + self._xy_color = self._cl._xy_color - """Register callbacks.""" + # Register callbacks dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_sensor) @property @@ -90,7 +89,11 @@ class CircadianSensor(Entity): @property def device_state_attributes(self): """Return the attributes of the sensor.""" - return self._attributes + return { + "colortemp": self._cl._colortemp, + "rgb_color": self._cl._rgb_color, + "xy_color": self._cl._xy_color, + } def update(self): """Fetch new state data for the sensor. @@ -103,7 +106,7 @@ class CircadianSensor(Entity): if self._cl.data is not None: self._state = self._cl._percent self._hs_color = self._cl._hs_color - self._attributes["colortemp"] = self._cl._colortemp - self._attributes["rgb_color"] = self._cl._rgb_color - self._attributes["xy_color"] = self._cl._xy_color + self._colortemp = self._cl._colortemp + self._rgb_color = self._cl._rgb_color + self._xy_color = self._cl._xy_color _LOGGER.debug("Circadian Lighting Sensor Updated") From 5ff9be00905ba1bfb8cc6c8899cd71b89a21271b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 00:26:19 +0200 Subject: [PATCH 37/69] create ._time dict --- .../circadian_lighting/__init__.py | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 7207fa2c..934c31cb 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -141,13 +141,14 @@ class CircadianLighting(object): transition, ): self.hass = hass - self.data = {} self._min_colortemp = min_colortemp self._max_colortemp = max_colortemp self._sunrise_offset = sunrise_offset self._sunset_offset = sunset_offset - self.data["sunrise_time"] = sunrise_time - self.data["sunset_time"] = sunset_time + self._time = { + "sunrise": sunrise_time, + "sunset": sunset_time, + } self._latitude = latitude self._longitude = longitude self._elevation = elevation @@ -163,7 +164,7 @@ class CircadianLighting(object): self.update = Throttle(timedelta(seconds=interval))(self._update) for which in ["sunrise", "sunrise"]: - time = self.data[f"{which}_time"] + time = self._time[which] if time is not None: track_time_change( self.hass, @@ -181,30 +182,28 @@ class CircadianLighting(object): from timezonefinder import TimezoneFinder tf = TimezoneFinder() - timezone_string = tf.timezone_at( - lng=self._longitude, lat=self._latitude - ) + timezone_string = tf.timezone_at(lng=self._longitude, lat=self._latitude) timezone = get_time_zone(timezone_string) _LOGGER.debug("Timezone: " + str(timezone)) return timezone def _time_dict(self, key): return dict( - hour=int(self.data[key].strftime("%H")), - minute=int(self.data[key].strftime("%M")), - second=int(self.data[key].strftime("%S")), - microsecond=int(self.data[key].strftime("%f")), + hour=int(self._time[key].strftime("%H")), + minute=int(self._time[key].strftime("%M")), + second=int(self._time[key].strftime("%S")), + microsecond=int(self._time[key].strftime("%f")), ) def get_sunrise_sunset(self, date=None): if ( - self.data["sunrise_time"] is not None - and self.data["sunset_time"] is not None + self._time["sunrise"] is not None + and self._time["sunset"] is not None ): if date is None: date = dt_now(self._timezone) - sunrise = date.replace(**self._time_dict("sunrise_time")) - sunset = date.replace(**self._time_dict("sunset_time")) + sunrise = date.replace(**self._time_dict("sunrise")) + sunset = date.replace(**self._time_dict("sunset")) solar_noon = sunrise + (sunset - sunrise) / 2 solar_midnight = sunset + ((sunrise + timedelta(days=1)) - sunset) / 2 else: @@ -217,16 +216,16 @@ class CircadianLighting(object): location.longitude = self._longitude location.elevation = self._elevation _LOGGER.debug("Astral location: " + str(location)) - if self.data["sunrise_time"] is not None: + if self._time["sunrise"] is not None: if date is None: date = dt_now(self._timezone) - sunrise = date.replace(**self._time_dict("sunrise_time")) + sunrise = date.replace(**self._time_dict("sunrise")) else: sunrise = location.sunrise(date) - if self.data["sunset_time"] is not None: + if self._time["sunset"] is not None: if date is None: date = dt_now(self._timezone) - sunset = date.replace(**self._time_dict("sunset_time")) + sunset = date.replace(**self._time_dict("sunset")) else: sunset = location.sunset(date) solar_noon = location.solar_noon(date) @@ -336,8 +335,7 @@ class CircadianLighting(object): def calc_colortemp(self): if self._percent > 0: return ( - (self._max_colortemp - self._min_colortemp) - * (self._percent / 100) + (self._max_colortemp - self._min_colortemp) * (self._percent / 100) ) + self._min_colortemp else: return self._min_colortemp From 875542650e5e324e7766f923354eb11c148b7f91 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 00:39:41 +0200 Subject: [PATCH 38/69] more simplifications and renames --- .../circadian_lighting/__init__.py | 26 +++-------- .../circadian_lighting/sensor.py | 45 +++++++++---------- .../circadian_lighting/switch.py | 33 +++++++------- 3 files changed, 45 insertions(+), 59 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 934c31cb..5492511e 100644 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -122,7 +122,7 @@ def setup(hass, config): return True -class CircadianLighting(object): +class CircadianLighting: """Calculate universal Circadian values.""" def __init__( @@ -196,10 +196,7 @@ class CircadianLighting(object): ) def get_sunrise_sunset(self, date=None): - if ( - self._time["sunrise"] is not None - and self._time["sunset"] is not None - ): + if self._time["sunrise"] is not None and self._time["sunset"] is not None: if date is None: date = dt_now(self._timezone) sunrise = date.replace(**self._time_dict("sunrise")) @@ -334,9 +331,9 @@ class CircadianLighting(object): def calc_colortemp(self): if self._percent > 0: - return ( - (self._max_colortemp - self._min_colortemp) * (self._percent / 100) - ) + self._min_colortemp + delta = self._max_colortemp - self._min_colortemp + percent = self._percent / 100 + return (delta * percent) + self._min_colortemp else: return self._min_colortemp @@ -344,19 +341,10 @@ class CircadianLighting(object): return color_temperature_to_rgb(self._colortemp) def calc_xy(self): - rgb = self.calc_rgb() - iR = rgb[0] - iG = rgb[1] - iB = rgb[2] - - return color_RGB_to_xy(iR, iG, iB) + return color_RGB_to_xy(*self.calc_rgb()) def calc_hs(self): - xy = self.calc_xy() - vX = xy[0] - vY = xy[1] - - return color_xy_to_hs(vX, vY) + return color_xy_to_hs(*self.calc_xy()) def _update(self, *args, **kwargs): """Update Circadian Values.""" diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index f3e98113..e5f67282 100644 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -22,14 +22,14 @@ ICON = "mdi:theme-light-dark" def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Circadian Lighting sensor.""" - cl = hass.data.get(DOMAIN) - if cl: - cs = CircadianSensor(hass, cl) - add_devices([cs]) + circadian_lighting = hass.data.get(DOMAIN) + if circadian_lighting is not None: + sensor = CircadianSensor(hass, circadian_lighting) + add_devices([sensor]) def update(call=None): """Update component.""" - cl._update() + circadian_lighting._update() service_name = "values_update" hass.services.register(DOMAIN, service_name, update) @@ -41,18 +41,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class CircadianSensor(Entity): """Representation of a Circadian Lighting sensor.""" - def __init__(self, hass, cl): + def __init__(self, hass, circadian_lighting): """Initialize the Circadian Lighting sensor.""" - self._cl = cl + self._circadian_lighting = circadian_lighting self._name = "Circadian Values" self._entity_id = "sensor.circadian_values" - self._state = self._cl._percent + self._state = self._circadian_lighting._percent self._unit_of_measurement = "%" self._icon = ICON - self._hs_color = self._cl._hs_color - self._colortemp = self._cl._colortemp - self._rgb_color = self._cl._rgb_color - self._xy_color = self._cl._xy_color + self._hs_color = self._circadian_lighting._hs_color + self._colortemp = self._circadian_lighting._colortemp + self._rgb_color = self._circadian_lighting._rgb_color + self._xy_color = self._circadian_lighting._xy_color # Register callbacks dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_sensor) @@ -90,9 +90,9 @@ class CircadianSensor(Entity): def device_state_attributes(self): """Return the attributes of the sensor.""" return { - "colortemp": self._cl._colortemp, - "rgb_color": self._cl._rgb_color, - "xy_color": self._cl._xy_color, + "colortemp": self._circadian_lighting._colortemp, + "rgb_color": self._circadian_lighting._rgb_color, + "xy_color": self._circadian_lighting._xy_color, } def update(self): @@ -100,13 +100,12 @@ class CircadianSensor(Entity): This is the only method that should fetch new data for Home Assistant. """ - self._cl.update() + self._circadian_lighting.update() def update_sensor(self): - if self._cl.data is not None: - self._state = self._cl._percent - self._hs_color = self._cl._hs_color - self._colortemp = self._cl._colortemp - self._rgb_color = self._cl._rgb_color - self._xy_color = self._cl._xy_color - _LOGGER.debug("Circadian Lighting Sensor Updated") + self._state = self._circadian_lighting._percent + self._hs_color = self._circadian_lighting._hs_color + self._colortemp = self._circadian_lighting._colortemp + self._rgb_color = self._circadian_lighting._rgb_color + self._xy_color = self._circadian_lighting._xy_color + _LOGGER.debug("Circadian Lighting Sensor Updated") diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 189c30fd..93dd4457 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -106,11 +106,11 @@ PLATFORM_SCHEMA = vol.Schema( def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the Circadian Lighting switches.""" - cl = hass.data.get(DOMAIN) - if cl: + circadian_lighting = hass.data.get(DOMAIN) + if circadian_lighting is not None: switch = CircadianSwitch( hass, - cl, + circadian_lighting, name=config.get(CONF_NAME), lights_ct=config.get(CONF_LIGHTS_CT, []), lights_rgb=config.get(CONF_LIGHTS_RGB, []), @@ -141,7 +141,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def __init__( self, hass, - cl, + circadian_lighting, name, lights_ct, lights_rgb, @@ -161,7 +161,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): ): """Initialize the Circadian Lighting switch.""" self.hass = hass - self._cl = cl + self._circadian_lighting = circadian_lighting self._name = name self._entity_id = "switch." + slugify(f"circadian_lighting {name}") self._state = None @@ -260,7 +260,11 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): @property def _color_temperature(self): - return self._sleep_colortemp if self.is_sleep() else self._cl.data["colortemp"] + return ( + self._sleep_colortemp + if self.is_sleep() + else self._circadian_lighting._colortemp + ) def calc_ct(self): return color_temperature_kelvin_to_mired(self._color_temperature) @@ -279,30 +283,25 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return None elif self.is_sleep(): return self._sleep_brightness - elif self._cl.data["percent"] > 0: + elif self._circadian_lighting._percent > 0: return self._max_brightness else: delta_brightness = self._max_brightness - self._min_brightness - procent = (100 + self._cl.data["percent"]) / 100 + procent = (100 + self._circadian_lighting._percent) / 100 return (delta_brightness * procent) + self._min_brightness def _update_switch(self, transition=None, force=False): if self._once_only and not force: return - if self._cl.data is not None: - self._hs_color = self.calc_hs() - self._brightness = self.calc_brightness() - _LOGGER.debug(f"{self._name} Switch Updated") - + self._hs_color = self.calc_hs() + self._brightness = self.calc_brightness() + _LOGGER.debug(f"{self._name} Switch Updated") self.adjust_lights(self._lights, transition) def should_adjust(self): if self._state is not True: _LOGGER.debug(f"{self._name} off - not adjusting") return False - elif self._cl.data is None: - _LOGGER.debug(f"{self._name} could not retrieve Circadian Lighting data") - return False elif ( self._disable_entity is not None and self.hass.states.get(self._disable_entity).state == self._disable_state @@ -317,7 +316,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return if transition is None: - transition = self._cl.data["transition"] + transition = self._circadian_lighting._transition for light in lights: if not is_on(self.hass, light): From b9ca03be9cba2df303ca166a2d222077a14ecf86 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 00:40:53 +0200 Subject: [PATCH 39/69] simplify self._entity_id --- custom_components/circadian_lighting/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 93dd4457..256289c3 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -163,7 +163,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self.hass = hass self._circadian_lighting = circadian_lighting self._name = name - self._entity_id = "switch." + slugify(f"circadian_lighting {name}") + self._entity_id = f"switch.circadian_lighting_{slugify(name)}" self._state = None self._icon = ICON self._hs_color = None From 1150c586585131a434851d8d7c5b2b88bff0de00 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 00:57:46 +0200 Subject: [PATCH 40/69] Fix syntax --- custom_components/circadian_lighting/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 256289c3..987ee748 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -99,7 +99,7 @@ PLATFORM_SCHEMA = vol.Schema( vol.Optional( CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION ): VALID_TRANSITION, - vol.Optional(CONF_ONCE_ONLY): cv.bool, + vol.Optional(CONF_ONCE_ONLY): cv.boolean, } ) From c1af45d46a79734a48170a3d7b321577ca4bb29d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 08:36:04 +0200 Subject: [PATCH 41/69] set self._brightness in __init__ --- custom_components/circadian_lighting/switch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 987ee748..bb90bce8 100644 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -167,6 +167,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._state = None self._icon = ICON self._hs_color = None + self._brightness = None self._lights_ct = lights_ct self._lights_rgb = lights_rgb self._lights_xy = lights_xy From d9deee9e52eaa342a152de2fe3a6376402bb23bc Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 08:37:04 +0200 Subject: [PATCH 42/69] simplify calc_percent --- .../circadian_lighting/__init__.py | 104 +++++++++--------- .../circadian_lighting/sensor.py | 0 .../circadian_lighting/switch.py | 23 ++-- 3 files changed, 59 insertions(+), 68 deletions(-) mode change 100644 => 100755 custom_components/circadian_lighting/__init__.py mode change 100644 => 100755 custom_components/circadian_lighting/sensor.py mode change 100644 => 100755 custom_components/circadian_lighting/switch.py diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py old mode 100644 new mode 100755 index 5492511e..5d12a934 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -58,6 +58,8 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "circadian_lighting" CIRCADIAN_LIGHTING_UPDATE_TOPIC = f"{DOMAIN}_update" +SUN_EVENT_NOON = "solar_noon" +SUN_EVENT_MIDNIGHT = "solar_midnight" CONF_MIN_CT = "min_colortemp" DEFAULT_MIN_CT = 2500 @@ -195,7 +197,7 @@ class CircadianLighting: microsecond=int(self._time[key].strftime("%f")), ) - def get_sunrise_sunset(self, date=None): + def get_sunrise_sunset(self, date=None, as_timestamps=False): if self._time["sunrise"] is not None and self._time["sunset"] is not None: if date is None: date = dt_now(self._timezone) @@ -231,65 +233,57 @@ class CircadianLighting: sunrise = sunrise + self._sunrise_offset if self._sunset_offset is not None: sunset = sunset + self._sunset_offset - return { + datetimes = { SUN_EVENT_SUNRISE: sunrise.astimezone(self._timezone), SUN_EVENT_SUNSET: sunset.astimezone(self._timezone), - "solar_noon": solar_noon.astimezone(self._timezone), - "solar_midnight": solar_midnight.astimezone(self._timezone), + SUN_EVENT_NOON: solar_noon.astimezone(self._timezone), + SUN_EVENT_MIDNIGHT: solar_midnight.astimezone(self._timezone), } + if as_timestamps: + return {k: dt.timestamp() for k, dt in datetimes.items()} + else: + return datetimes def calc_percent(self): now = dt_now(self._timezone) _LOGGER.debug("now: " + str(now)) - today_sun_times = self.get_sunrise_sunset(now) - _LOGGER.debug("today_sun_times: " + str(today_sun_times)) + today = self.get_sunrise_sunset(now, as_timestamps=True) + _LOGGER.debug("today: " + str(today)) # Convert everything to epoch timestamps for easy calculation - now_seconds = now.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() + now_ts = now.timestamp() - if now < today_sun_times[SUN_EVENT_SUNRISE]: + if now_ts < today[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)) - _LOGGER.debug("yesterday_sun_times: " + str(yesterday_sun_times)) - sunset_seconds = yesterday_sun_times[SUN_EVENT_SUNSET].timestamp() + yesterday = self.get_sunrise_sunset( + now - timedelta(days=1), as_timestamps=True + ) + _LOGGER.debug("yesterday: " + str(yesterday)) + today[SUN_EVENT_SUNSET] = yesterday[SUN_EVENT_SUNSET] if ( - today_sun_times["solar_midnight"] > today_sun_times[SUN_EVENT_SUNSET] - and yesterday_sun_times["solar_midnight"] - > yesterday_sun_times[SUN_EVENT_SUNSET] + today[SUN_EVENT_MIDNIGHT] > today[SUN_EVENT_SUNSET] + and yesterday[SUN_EVENT_MIDNIGHT] > yesterday[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]: + today[SUN_EVENT_MIDNIGHT] = yesterday[SUN_EVENT_MIDNIGHT] + elif now_ts > today[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)) - _LOGGER.debug("tomorrow_sun_times: " + str(tomorrow_sun_times)) - sunrise_seconds = tomorrow_sun_times[SUN_EVENT_SUNRISE].timestamp() + tomorrow = self.get_sunrise_sunset( + now + timedelta(days=1), as_timestamps=True + ) + _LOGGER.debug("tomorrow: " + str(tomorrow)) + today[SUN_EVENT_SUNRISE] = tomorrow[SUN_EVENT_SUNRISE] if ( - today_sun_times["solar_midnight"] < today_sun_times[SUN_EVENT_SUNRISE] - and tomorrow_sun_times["solar_midnight"] - < tomorrow_sun_times[SUN_EVENT_SUNRISE] + today[SUN_EVENT_MIDNIGHT] < today[SUN_EVENT_SUNRISE] + and tomorrow[SUN_EVENT_MIDNIGHT] < tomorrow[SUN_EVENT_SUNRISE] ): # Solar midnight is before sunrise so use tomorrow's time - solar_midnight_seconds = tomorrow_sun_times[ - "solar_midnight" - ].timestamp() + today[SUN_EVENT_MIDNIGHT] = tomorrow[SUN_EVENT_MIDNIGHT] - _LOGGER.debug( - f"now_seconds: {now_seconds}, " - f"sunrise_seconds: {sunrise_seconds}, " - f"sunset_seconds: {sunset_seconds}, " - f"solar_midnight_seconds: {solar_midnight_seconds}, " - f"solar_noon_seconds: {solar_noon_seconds}" - ) + _LOGGER.debug(f"now_ts: {now_ts}, {today}") # 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 @@ -297,31 +291,31 @@ class CircadianLighting: # 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 + if now_ts > today[SUN_EVENT_SUNRISE] and now_ts < today[SUN_EVENT_SUNSET]: + h = today[SUN_EVENT_NOON] k = 100 - # parabola before solar_noon - if now_seconds < solar_noon_seconds: - x = sunrise_seconds - # parabola after solar_noon - else: - x = sunset_seconds + # parabola before solar_noon else after solar_noon + x = ( + today[SUN_EVENT_SUNRISE] + if now_ts < today[SUN_EVENT_NOON] + else today[SUN_EVENT_SUNSET] + ) y = 0 # sunset_sunrise parabola - elif now_seconds > sunset_seconds and now_seconds < sunrise_seconds: - h = solar_midnight_seconds + elif now_ts > today[SUN_EVENT_SUNSET] and now_ts < today[SUN_EVENT_SUNRISE]: + h = today[SUN_EVENT_MIDNIGHT] k = -100 - # parabola before solar_midnight - if now_seconds < solar_midnight_seconds: - x = sunset_seconds - # parabola after solar_midnight - else: - x = sunrise_seconds + # parabola before solar_midnight else after solar_midnight + x = ( + today[SUN_EVENT_SUNSET] + if now_ts < today[SUN_EVENT_MIDNIGHT] + else today[SUN_EVENT_SUNRISE] + ) y = 0 a = (y - k) / (h - x) ** 2 - percentage = a * (now_seconds - h) ** 2 + k + percentage = a * (now_ts - h) ** 2 + k _LOGGER.debug( f"h: {h}, k: {k}, x: {x}, y: {y}, a: {a}, percentage: {percentage}" diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py old mode 100644 new mode 100755 diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py old mode 100644 new mode 100755 index bb90bce8..d0a781aa --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -99,7 +99,7 @@ PLATFORM_SCHEMA = vol.Schema( vol.Optional( CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION ): VALID_TRANSITION, - vol.Optional(CONF_ONCE_ONLY): cv.boolean, + vol.Optional(CONF_ONCE_ONLY, default=False): cv.boolean, } ) @@ -359,22 +359,19 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): _LOGGER.debug(f"{light} {which} Adjusted - {msg}") def light_state_changed(self, entity_id, from_state, to_state): - with suppress(Exception): - _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") - if to_state.state == "on" and from_state.state != "on": - self.adjust_lights([entity_id], self._initial_transition) + _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") + if to_state.state == "on" and from_state.state != "on": + self.adjust_lights([entity_id], self._initial_transition) def sleep_state_changed(self, entity_id, from_state, to_state): - with suppress(Exception): - _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") - if ( - to_state.state == self._sleep_state + _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") + if ( + to_state.state == self._sleep_state or from_state.state == self._sleep_state ): self._update_switch(self._initial_transition, force=True) def disable_state_changed(self, entity_id, from_state, to_state): - with suppress(Exception): - _LOGGER.debug("{entity_id} change from {from_state} to {to_state}") - if from_state.state == self._disable_state: - self._update_switch(self._initial_transition, force=True) + _LOGGER.debug("{entity_id} change from {from_state} to {to_state}") + if from_state.state == self._disable_state: + self._update_switch(self._initial_transition, force=True) From 1f8324c065c45749738f992a3c8fe48e131aeebb Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 17:42:16 +0200 Subject: [PATCH 43/69] more simplifications --- .../circadian_lighting/__init__.py | 28 ++++++++----------- .../circadian_lighting/manifest.json | 2 +- .../circadian_lighting/sensor.py | 3 -- .../circadian_lighting/switch.py | 10 ++----- 4 files changed, 15 insertions(+), 28 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 5d12a934..36824779 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -30,8 +30,10 @@ Technical notes: I had to make a lot of assumptions when writing this app import logging from datetime import timedelta -import homeassistant.helpers.config_validation as cv import voluptuous as vol + +import astral +import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ATTR_TRANSITION, VALID_TRANSITION from homeassistant.const import ( CONF_ELEVATION, @@ -51,8 +53,7 @@ from homeassistant.util.color import ( ) from homeassistant.util.dt import get_time_zone from homeassistant.util.dt import now as dt_now - -VERSION = "1.0.13" +from timezonefinder import TimezoneFinder _LOGGER = logging.getLogger(__name__) @@ -61,16 +62,13 @@ CIRCADIAN_LIGHTING_UPDATE_TOPIC = f"{DOMAIN}_update" SUN_EVENT_NOON = "solar_noon" SUN_EVENT_MIDNIGHT = "solar_midnight" -CONF_MIN_CT = "min_colortemp" -DEFAULT_MIN_CT = 2500 -CONF_MAX_CT = "max_colortemp" -DEFAULT_MAX_CT = 5500 +CONF_MIN_CT, DEFAULT_MIN_CT = "min_colortemp", 2500 +CONF_MAX_CT, DEFAULT_MAX_CT = "max_colortemp", 5500 +CONF_INTERVAL, DEFAULT_INTERVAL = "interval", 300 CONF_SUNRISE_OFFSET = "sunrise_offset" CONF_SUNSET_OFFSET = "sunset_offset" CONF_SUNRISE_TIME = "sunrise_time" CONF_SUNSET_TIME = "sunset_time" -CONF_INTERVAL = "interval" -DEFAULT_INTERVAL = 300 DEFAULT_TRANSITION = 60 CONFIG_SCHEMA = vol.Schema( @@ -181,8 +179,6 @@ class CircadianLighting: track_sunset(self.hass, self._update, self._sunset_offset) def get_timezone(self): - from timezonefinder import TimezoneFinder - tf = TimezoneFinder() timezone_string = tf.timezone_at(lng=self._longitude, lat=self._latitude) timezone = get_time_zone(timezone_string) @@ -201,13 +197,13 @@ class CircadianLighting: if self._time["sunrise"] is not None and self._time["sunset"] is not None: if date is None: date = dt_now(self._timezone) - sunrise = date.replace(**self._time_dict("sunrise")) + sunrise = date.replace( + **self._time_dict("sunrise") + ) # XXX: redefine _time_dict to do the replace! sunset = date.replace(**self._time_dict("sunset")) solar_noon = sunrise + (sunset - sunrise) / 2 solar_midnight = sunset + ((sunrise + timedelta(days=1)) - sunset) / 2 else: - import astral - location = astral.Location() location.name = "name" location.region = "region" @@ -291,7 +287,7 @@ class CircadianLighting: # We're also (obviously) generating a different parabola for sunrise-sunset # sunrise-sunset parabola - if now_ts > today[SUN_EVENT_SUNRISE] and now_ts < today[SUN_EVENT_SUNSET]: + if today[SUN_EVENT_SUNRISE] < now_ts < today[SUN_EVENT_SUNSET]: h = today[SUN_EVENT_NOON] k = 100 # parabola before solar_noon else after solar_noon @@ -303,7 +299,7 @@ class CircadianLighting: y = 0 # sunset_sunrise parabola - elif now_ts > today[SUN_EVENT_SUNSET] and now_ts < today[SUN_EVENT_SUNRISE]: + elif today[SUN_EVENT_SUNSET] < now_ts < today[SUN_EVENT_SUNRISE]: h = today[SUN_EVENT_MIDNIGHT] k = -100 # parabola before solar_midnight else after solar_midnight diff --git a/custom_components/circadian_lighting/manifest.json b/custom_components/circadian_lighting/manifest.json index 4832a25b..0008ce6b 100644 --- a/custom_components/circadian_lighting/manifest.json +++ b/custom_components/circadian_lighting/manifest.json @@ -4,5 +4,5 @@ "documentation": "https://github.com/claytonjn/hass-circadian_lighting", "dependencies": [], "codeowners": ["@claytonjn"], - "requirements": ["timezonefinder==4.2.0"] + "requirements": ["timezonefinder==4.2.0", "astral==1.10.1"] } diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index e5f67282..6bd28b2a 100755 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -2,9 +2,6 @@ Circadian Lighting Sensor for Home-Assistant. """ -DEPENDENCIES = ["circadian_lighting"] - -import datetime import logging from homeassistant.helpers.dispatcher import dispatcher_connect diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index d0a781aa..5788da4e 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -2,10 +2,7 @@ Circadian Lighting Switch for Home-Assistant. """ -DEPENDENCIES = ["circadian_lighting", "light"] - import logging -from contextlib import suppress import homeassistant.helpers.config_validation as cv import voluptuous as vol @@ -365,11 +362,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def sleep_state_changed(self, entity_id, from_state, to_state): _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") - if ( - to_state.state == self._sleep_state - or from_state.state == self._sleep_state - ): - self._update_switch(self._initial_transition, force=True) + if to_state.state == self._sleep_state or from_state.state == self._sleep_state: + self._update_switch(self._initial_transition, force=True) def disable_state_changed(self, entity_id, from_state, to_state): _LOGGER.debug("{entity_id} change from {from_state} to {to_state}") From e058091f52e9519b3ef29027cd44a7be027c742a Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 17:44:16 +0200 Subject: [PATCH 44/69] allow CONF_SLEEP_STATE and CONF_DISABLE_STATE to be lists and add defaults --- .../circadian_lighting/__init__.py | 3 +- .../circadian_lighting/switch.py | 33 +++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 36824779..0054d4ed 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -102,8 +102,6 @@ CONFIG_SCHEMA = vol.Schema( def setup(hass, config): """Set up the Circadian Lighting component.""" conf = config[DOMAIN] - load_platform(hass, "sensor", DOMAIN, {}, config) - hass.data[DOMAIN] = CircadianLighting( hass, min_colortemp=conf.get(CONF_MIN_CT), @@ -118,6 +116,7 @@ def setup(hass, config): interval=conf.get(CONF_INTERVAL), transition=conf.get(ATTR_TRANSITION), ) + load_platform(hass, "sensor", DOMAIN, {}, config) return True diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 5788da4e..9f06d883 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -54,18 +54,15 @@ CONF_LIGHTS_RGB = "lights_rgb" CONF_LIGHTS_XY = "lights_xy" CONF_LIGHTS_BRIGHT = "lights_brightness" CONF_DISABLE_BRIGHTNESS_ADJUST = "disable_brightness_adjust" -CONF_MIN_BRIGHT = "min_brightness" -DEFAULT_MIN_BRIGHT = 1 -CONF_MAX_BRIGHT = "max_brightness" -DEFAULT_MAX_BRIGHT = 100 +CONF_MIN_BRIGHT, DEFAULT_MIN_BRIGHT = ("min_brightness", 1) +CONF_MAX_BRIGHT, DEFAULT_MAX_BRIGHT = ("max_brightness", 100) CONF_SLEEP_ENTITY = "sleep_entity" CONF_SLEEP_STATE = "sleep_state" -CONF_SLEEP_CT = "sleep_colortemp" -CONF_SLEEP_BRIGHT = "sleep_brightness" +CONF_SLEEP_CT, DEFAULT_SLEEP_CT = ("sleep_colortemp", 1000) +CONF_SLEEP_BRIGHT, DEFAULT_SLEEP_BRIGHT = ("sleep_brightness", 1) CONF_DISABLE_ENTITY = "disable_entity" CONF_DISABLE_STATE = "disable_state" -CONF_INITIAL_TRANSITION = "initial_transition" -DEFAULT_INITIAL_TRANSITION = 1 +CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION = ("initial_transition", 1) CONF_ONCE_ONLY = "once_only" PLATFORM_SCHEMA = vol.Schema( @@ -84,15 +81,15 @@ PLATFORM_SCHEMA = vol.Schema( vol.Coerce(int), vol.Range(min=1, max=100) ), vol.Optional(CONF_SLEEP_ENTITY): cv.entity_id, - vol.Optional(CONF_SLEEP_STATE): cv.string, - vol.Optional(CONF_SLEEP_CT): vol.All( + vol.Optional(CONF_SLEEP_STATE): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_SLEEP_CT, default=DEFAULT_SLEEP_CT): vol.All( vol.Coerce(int), vol.Range(min=1000, max=10000) ), - vol.Optional(CONF_SLEEP_BRIGHT): vol.All( + vol.Optional(CONF_SLEEP_BRIGHT, default=DEFAULT_SLEEP_BRIGHT): vol.All( vol.Coerce(int), vol.Range(min=1, max=100) ), vol.Optional(CONF_DISABLE_ENTITY): cv.entity_id, - vol.Optional(CONF_DISABLE_STATE): cv.string, + vol.Optional(CONF_DISABLE_STATE): vol.All(cv.ensure_list, [cv.string]), vol.Optional( CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION ): VALID_TRANSITION, @@ -249,7 +246,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def is_sleep(self): is_sleep = ( self._sleep_entity is not None - and self.hass.states.get(self._sleep_entity).state == self._sleep_state + and self.hass.states.get(self._sleep_entity).state in self._sleep_state ) if is_sleep: _LOGGER.debug(f"{self._name} in Sleep mode") @@ -302,7 +299,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return False elif ( self._disable_entity is not None - and self.hass.states.get(self._disable_entity).state == self._disable_state + and self.hass.states.get(self._disable_entity).state in self._disable_state ): _LOGGER.debug(f"{self._name} disabled by {self._disable_entity}") return False @@ -362,10 +359,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def sleep_state_changed(self, entity_id, from_state, to_state): _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") - if to_state.state == self._sleep_state or from_state.state == self._sleep_state: - self._update_switch(self._initial_transition, force=True) + if to_state.state in self._sleep_state or from_state.state in self._sleep_state: + self._update_switch(transition=self._initial_transition, force=True) def disable_state_changed(self, entity_id, from_state, to_state): _LOGGER.debug("{entity_id} change from {from_state} to {to_state}") - if from_state.state == self._disable_state: - self._update_switch(self._initial_transition, force=True) + if from_state.state in self._disable_state: + self._update_switch(transition=self._initial_transition, force=True) From e257af03bfd409ed1e7f23ed5d4c72264c85f994 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 17:43:58 +0200 Subject: [PATCH 45/69] call self._update_switch in light_state_changed --- custom_components/circadian_lighting/__init__.py | 2 +- custom_components/circadian_lighting/switch.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 0054d4ed..2d2f8253 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -335,7 +335,7 @@ class CircadianLighting: def calc_hs(self): return color_xy_to_hs(*self.calc_xy()) - def _update(self, *args, **kwargs): + def _update(self): """Update Circadian Values.""" self._percent = self.calc_percent() self._colortemp = self.calc_colortemp() diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 9f06d883..2e95a3c4 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -232,7 +232,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._state = True # Make initial update - self._update_switch(self._initial_transition, force=True) + self._update_switch(transition=self._initial_transition, force=True) self.schedule_update_ha_state() @@ -285,15 +285,15 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): procent = (100 + self._circadian_lighting._percent) / 100 return (delta_brightness * procent) + self._min_brightness - def _update_switch(self, transition=None, force=False): + def _update_switch(self, lights=None, transition=None, force=False): if self._once_only and not force: return self._hs_color = self.calc_hs() self._brightness = self.calc_brightness() _LOGGER.debug(f"{self._name} Switch Updated") - self.adjust_lights(self._lights, transition) + self._adjust_lights(lights or self._lights, transition) - def should_adjust(self): + def _should_adjust(self): if self._state is not True: _LOGGER.debug(f"{self._name} off - not adjusting") return False @@ -306,8 +306,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): else: return True - def adjust_lights(self, lights, transition=None): - if not self.should_adjust(): + def _adjust_lights(self, lights, transition=None): + if not self._should_adjust(): return if transition is None: @@ -355,7 +355,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def light_state_changed(self, entity_id, from_state, to_state): _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") if to_state.state == "on" and from_state.state != "on": - self.adjust_lights([entity_id], self._initial_transition) + self._update_switch([entity_id], self._initial_transition, force=True) def sleep_state_changed(self, entity_id, from_state, to_state): _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") From 080aeed541f3f2de7e89dc9e88a335f73d618427 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 22:31:26 +0200 Subject: [PATCH 46/69] add _is_disabled property and move logging to more logical place --- .../circadian_lighting/switch.py | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 2e95a3c4..f3be1dd3 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -230,10 +230,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def turn_on(self, **kwargs): """Turn on circadian lighting.""" self._state = True - - # Make initial update self._update_switch(transition=self._initial_transition, force=True) - self.schedule_update_ha_state() def turn_off(self, **kwargs): @@ -293,14 +290,18 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): _LOGGER.debug(f"{self._name} Switch Updated") self._adjust_lights(lights or self._lights, transition) + @property + def _is_disabled(self): + return ( + self._disable_entity is not None + and self.hass.states.get(self._disable_entity).state in self._disable_state + ) + def _should_adjust(self): if self._state is not True: _LOGGER.debug(f"{self._name} off - not adjusting") return False - elif ( - self._disable_entity is not None - and self.hass.states.get(self._disable_entity).state in self._disable_state - ): + elif self._is_disabled: _LOGGER.debug(f"{self._name} disabled by {self._disable_entity}") return False else: @@ -353,16 +354,25 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): _LOGGER.debug(f"{light} {which} Adjusted - {msg}") def light_state_changed(self, entity_id, from_state, to_state): - _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") if to_state.state == "on" and from_state.state != "on": + _LOGGER.debug( + f"light_state_changed for {self._name}: {entity_id} " + f"change from {from_state} to {to_state}" + ) self._update_switch([entity_id], self._initial_transition, force=True) def sleep_state_changed(self, entity_id, from_state, to_state): - _LOGGER.debug(f"{entity_id} change from {from_state} to {to_state}") if to_state.state in self._sleep_state or from_state.state in self._sleep_state: + _LOGGER.debug( + f"sleep_state_changed for {self._name}: {entity_id} " + f"change from {from_state} to {to_state}" + ) self._update_switch(transition=self._initial_transition, force=True) def disable_state_changed(self, entity_id, from_state, to_state): - _LOGGER.debug("{entity_id} change from {from_state} to {to_state}") if from_state.state in self._disable_state: + _LOGGER.debug( + f"disable_state_changed for {self._name}: {entity_id} " + f"change from {from_state} to {to_state}" + ) self._update_switch(transition=self._initial_transition, force=True) From 063a2386aff5e672406a474ed964a42f6027102b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 22:47:40 +0200 Subject: [PATCH 47/69] add @log decorator --- .../circadian_lighting/__init__.py | 23 +++++++++++- .../circadian_lighting/sensor.py | 3 +- .../circadian_lighting/switch.py | 37 ++++++------------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 2d2f8253..11342f8f 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -29,6 +29,7 @@ Technical notes: I had to make a lot of assumptions when writing this app import logging from datetime import timedelta +import inspect import voluptuous as vol @@ -99,6 +100,26 @@ CONFIG_SCHEMA = vol.Schema( ) +def log(with_return=False, logger=_LOGGER): + def _log(func): + def wrapper(*args, **kwargs): + func_args = inspect.signature(func).bind(*args, **kwargs).arguments + key_value_pairs = ( + f"{k}={v!r}" for k, v in func_args.items() if k != "self" + ) + func_args_str = ", ".join(key_value_pairs) + out = f"{func.__qualname__}({func_args_str})" + result = func(*args, **kwargs) + if with_return: + out += f" -> {result}" + logger.debug(out) + return result + + return wrapper + + return _log + + def setup(hass, config): """Set up the Circadian Lighting component.""" conf = config[DOMAIN] @@ -177,11 +198,11 @@ class CircadianLighting: elif which == "sunset": track_sunset(self.hass, self._update, self._sunset_offset) + @log(with_return=True) def get_timezone(self): tf = TimezoneFinder() timezone_string = tf.timezone_at(lng=self._longitude, lat=self._latitude) timezone = get_time_zone(timezone_string) - _LOGGER.debug("Timezone: " + str(timezone)) return timezone def _time_dict(self, key): diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index 6bd28b2a..082250d3 100755 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -10,6 +10,7 @@ from homeassistant.helpers.entity import Entity from custom_components.circadian_lighting import ( CIRCADIAN_LIGHTING_UPDATE_TOPIC, DOMAIN, + log, ) _LOGGER = logging.getLogger(__name__) @@ -99,10 +100,10 @@ class CircadianSensor(Entity): """ self._circadian_lighting.update() + @log(logger=_LOGGER) def update_sensor(self): self._state = self._circadian_lighting._percent self._hs_color = self._circadian_lighting._hs_color self._colortemp = self._circadian_lighting._colortemp self._rgb_color = self._circadian_lighting._rgb_color self._xy_color = self._circadian_lighting._xy_color - _LOGGER.debug("Circadian Lighting Sensor Updated") diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index f3be1dd3..729046c4 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -37,6 +37,7 @@ from homeassistant.util.color import ( from custom_components.circadian_lighting import ( CIRCADIAN_LIGHTING_UPDATE_TOPIC, DOMAIN, + log, ) try: @@ -240,17 +241,13 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._hs_color = None self._brightness = None + @log(with_return=True, logger=_LOGGER) def is_sleep(self): - is_sleep = ( + return ( self._sleep_entity is not None and self.hass.states.get(self._sleep_entity).state in self._sleep_state ) - if is_sleep: - _LOGGER.debug(f"{self._name} in Sleep mode") - return is_sleep - - @property def _color_temperature(self): return ( self._sleep_colortemp @@ -259,10 +256,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): ) def calc_ct(self): - return color_temperature_kelvin_to_mired(self._color_temperature) + return color_temperature_kelvin_to_mired(self._color_temperature()) def calc_rgb(self): - return color_temperature_to_rgb(self._color_temperature) + return color_temperature_to_rgb(self._color_temperature()) def calc_xy(self): return color_RGB_to_xy(*self.calc_rgb()) @@ -282,27 +279,26 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): procent = (100 + self._circadian_lighting._percent) / 100 return (delta_brightness * procent) + self._min_brightness + @log(logger=_LOGGER) def _update_switch(self, lights=None, transition=None, force=False): if self._once_only and not force: return self._hs_color = self.calc_hs() self._brightness = self.calc_brightness() - _LOGGER.debug(f"{self._name} Switch Updated") self._adjust_lights(lights or self._lights, transition) - @property + @log(with_return=True, logger=_LOGGER) def _is_disabled(self): return ( self._disable_entity is not None and self.hass.states.get(self._disable_entity).state in self._disable_state ) + @log(with_return=True, logger=_LOGGER) def _should_adjust(self): if self._state is not True: - _LOGGER.debug(f"{self._name} off - not adjusting") return False - elif self._is_disabled: - _LOGGER.debug(f"{self._name} disabled by {self._disable_entity}") + elif self._is_disabled(): return False else: return True @@ -353,26 +349,17 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): msg = ", ".join(key_value_strings) _LOGGER.debug(f"{light} {which} Adjusted - {msg}") + @log(with_return=True, logger=_LOGGER) def light_state_changed(self, entity_id, from_state, to_state): if to_state.state == "on" and from_state.state != "on": - _LOGGER.debug( - f"light_state_changed for {self._name}: {entity_id} " - f"change from {from_state} to {to_state}" - ) self._update_switch([entity_id], self._initial_transition, force=True) + @log(with_return=True, logger=_LOGGER) def sleep_state_changed(self, entity_id, from_state, to_state): if to_state.state in self._sleep_state or from_state.state in self._sleep_state: - _LOGGER.debug( - f"sleep_state_changed for {self._name}: {entity_id} " - f"change from {from_state} to {to_state}" - ) self._update_switch(transition=self._initial_transition, force=True) + @log(with_return=True, logger=_LOGGER) def disable_state_changed(self, entity_id, from_state, to_state): if from_state.state in self._disable_state: - _LOGGER.debug( - f"disable_state_changed for {self._name}: {entity_id} " - f"change from {from_state} to {to_state}" - ) self._update_switch(transition=self._initial_transition, force=True) From 13bbdd4ad4dca960606eb70178c2bf7ec38906cd Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 23:13:31 +0200 Subject: [PATCH 48/69] create _replace_time method --- .../circadian_lighting/__init__.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 11342f8f..c14aec27 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -205,22 +205,21 @@ class CircadianLighting: timezone = get_time_zone(timezone_string) return timezone - def _time_dict(self, key): - return dict( - hour=int(self._time[key].strftime("%H")), - minute=int(self._time[key].strftime("%M")), - second=int(self._time[key].strftime("%S")), - microsecond=int(self._time[key].strftime("%f")), + def _replace_time(self, date, key): + other_date = self._time[key] + return date.replace( + hour=other_date.hour, + minute=other_date.minute, + second=other_date.second, + microsecond=other_date.microsecond, ) def get_sunrise_sunset(self, date=None, as_timestamps=False): if self._time["sunrise"] is not None and self._time["sunset"] is not None: if date is None: date = dt_now(self._timezone) - sunrise = date.replace( - **self._time_dict("sunrise") - ) # XXX: redefine _time_dict to do the replace! - sunset = date.replace(**self._time_dict("sunset")) + sunrise = self._replace_time(date, "sunrise") + sunset = self._replace_time(date, "sunset") solar_noon = sunrise + (sunset - sunrise) / 2 solar_midnight = sunset + ((sunrise + timedelta(days=1)) - sunset) / 2 else: @@ -231,24 +230,29 @@ class CircadianLighting: location.longitude = self._longitude location.elevation = self._elevation _LOGGER.debug("Astral location: " + str(location)) + if self._time["sunrise"] is not None: if date is None: date = dt_now(self._timezone) - sunrise = date.replace(**self._time_dict("sunrise")) + sunrise = self._replace_time(date, "sunrise") else: sunrise = location.sunrise(date) + if self._time["sunset"] is not None: if date is None: date = dt_now(self._timezone) - sunset = date.replace(**self._time_dict("sunset")) + sunset = self._replace_time(date, "sunset") else: sunset = location.sunset(date) + solar_noon = location.solar_noon(date) solar_midnight = location.solar_midnight(date) + if self._sunrise_offset is not None: sunrise = sunrise + self._sunrise_offset if self._sunset_offset is not None: sunset = sunset + self._sunset_offset + datetimes = { SUN_EVENT_SUNRISE: sunrise.astimezone(self._timezone), SUN_EVENT_SUNSET: sunset.astimezone(self._timezone), From 3475bd9b90ecd958e3d9ce863232ea7434d714c7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 23:38:01 +0200 Subject: [PATCH 49/69] date is never None and renames --- .../circadian_lighting/__init__.py | 82 +++++++------------ 1 file changed, 29 insertions(+), 53 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index c14aec27..f5390072 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -165,7 +165,7 @@ class CircadianLighting: self._max_colortemp = max_colortemp self._sunrise_offset = sunrise_offset self._sunset_offset = sunset_offset - self._time = { + self._manual_time = { "sunrise": sunrise_time, "sunset": sunset_time, } @@ -184,29 +184,27 @@ class CircadianLighting: self.update = Throttle(timedelta(seconds=interval))(self._update) for which in ["sunrise", "sunrise"]: - time = self._time[which] + time = self._manual_time[which] if time is not None: track_time_change( self.hass, self._update, - hour=int(time.strftime("%H")), - minute=int(time.strftime("%M")), - second=int(time.strftime("%S")), + hour=time.hour, + minute=time.minute, + second=time.second, ) - elif which == "sunrise": - track_sunrise(self.hass, self._update, self._sunrise_offset) - elif which == "sunset": - track_sunset(self.hass, self._update, self._sunset_offset) + + track_sunrise(self.hass, self._update, self._sunrise_offset) + track_sunset(self.hass, self._update, self._sunset_offset) @log(with_return=True) def get_timezone(self): tf = TimezoneFinder() timezone_string = tf.timezone_at(lng=self._longitude, lat=self._latitude) - timezone = get_time_zone(timezone_string) - return timezone + return get_time_zone(timezone_string) def _replace_time(self, date, key): - other_date = self._time[key] + other_date = self._manual_time[key] return date.replace( hour=other_date.hour, minute=other_date.minute, @@ -214,10 +212,11 @@ class CircadianLighting: microsecond=other_date.microsecond, ) - def get_sunrise_sunset(self, date=None, as_timestamps=False): - if self._time["sunrise"] is not None and self._time["sunset"] is not None: - if date is None: - date = dt_now(self._timezone) + def get_sunrise_sunset(self, date, as_timestamps=True): + if ( + self._manual_time["sunrise"] is not None + and self._manual_time["sunset"] is not None + ): sunrise = self._replace_time(date, "sunrise") sunset = self._replace_time(date, "sunset") solar_noon = sunrise + (sunset - sunrise) / 2 @@ -229,18 +228,13 @@ class CircadianLighting: location.latitude = self._latitude location.longitude = self._longitude location.elevation = self._elevation - _LOGGER.debug("Astral location: " + str(location)) - if self._time["sunrise"] is not None: - if date is None: - date = dt_now(self._timezone) + if self._manual_time["sunrise"] is not None: sunrise = self._replace_time(date, "sunrise") else: sunrise = location.sunrise(date) - if self._time["sunset"] is not None: - if date is None: - date = dt_now(self._timezone) + if self._manual_time["sunset"] is not None: sunset = self._replace_time(date, "sunset") else: sunset = location.sunset(date) @@ -266,21 +260,13 @@ class CircadianLighting: def calc_percent(self): now = dt_now(self._timezone) - _LOGGER.debug("now: " + str(now)) - - today = self.get_sunrise_sunset(now, as_timestamps=True) - _LOGGER.debug("today: " + str(today)) - - # Convert everything to epoch timestamps for easy calculation now_ts = now.timestamp() + today = self.get_sunrise_sunset(now) if now_ts < today[SUN_EVENT_SUNRISE]: # It's before sunrise (after midnight) # Because it's before sunrise (and after midnight) sunset must have happend yesterday - yesterday = self.get_sunrise_sunset( - now - timedelta(days=1), as_timestamps=True - ) - _LOGGER.debug("yesterday: " + str(yesterday)) + yesterday = self.get_sunrise_sunset(now - timedelta(days=1)) today[SUN_EVENT_SUNSET] = yesterday[SUN_EVENT_SUNSET] if ( today[SUN_EVENT_MIDNIGHT] > today[SUN_EVENT_SUNSET] @@ -291,10 +277,7 @@ class CircadianLighting: elif now_ts > today[SUN_EVENT_SUNSET]: # It's after sunset (before midnight) # Because it's after sunset (and before midnight) sunrise should happen tomorrow - tomorrow = self.get_sunrise_sunset( - now + timedelta(days=1), as_timestamps=True - ) - _LOGGER.debug("tomorrow: " + str(tomorrow)) + tomorrow = self.get_sunrise_sunset(now + timedelta(days=1)) today[SUN_EVENT_SUNRISE] = tomorrow[SUN_EVENT_SUNRISE] if ( today[SUN_EVENT_MIDNIGHT] < today[SUN_EVENT_SUNRISE] @@ -303,14 +286,13 @@ class CircadianLighting: # Solar midnight is before sunrise so use tomorrow's time today[SUN_EVENT_MIDNIGHT] = tomorrow[SUN_EVENT_MIDNIGHT] - _LOGGER.debug(f"now_ts: {now_ts}, {today}") + # 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 generating a different parabola for sunrise-sunset. - # 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 + # sunrise -> sunset parabola if today[SUN_EVENT_SUNRISE] < now_ts < today[SUN_EVENT_SUNSET]: h = today[SUN_EVENT_NOON] k = 100 @@ -320,9 +302,8 @@ class CircadianLighting: if now_ts < today[SUN_EVENT_NOON] else today[SUN_EVENT_SUNSET] ) - y = 0 - # sunset_sunrise parabola + # sunset -> sunrise parabola elif today[SUN_EVENT_SUNSET] < now_ts < today[SUN_EVENT_SUNRISE]: h = today[SUN_EVENT_MIDNIGHT] k = -100 @@ -332,15 +313,10 @@ class CircadianLighting: if now_ts < today[SUN_EVENT_MIDNIGHT] else today[SUN_EVENT_SUNRISE] ) - y = 0 + y = 0 a = (y - k) / (h - x) ** 2 percentage = a * (now_ts - h) ** 2 + k - - _LOGGER.debug( - f"h: {h}, k: {k}, x: {x}, y: {y}, a: {a}, percentage: {percentage}" - ) - return percentage def calc_colortemp(self): @@ -360,6 +336,7 @@ class CircadianLighting: def calc_hs(self): return color_xy_to_hs(*self.calc_xy()) + @log() def _update(self): """Update Circadian Values.""" self._percent = self.calc_percent() @@ -368,4 +345,3 @@ class CircadianLighting: self._xy_color = self.calc_xy() self._hs_color = self.calc_hs() dispatcher_send(self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC) - _LOGGER.debug("Circadian Lighting Component Updated") From 29010105c146684c0daffa4e04c061d65073e62f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 23:46:36 +0200 Subject: [PATCH 50/69] change elif in if when returning --- .../circadian_lighting/switch.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 729046c4..a342b045 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -250,9 +250,9 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def _color_temperature(self): return ( - self._sleep_colortemp - if self.is_sleep() - else self._circadian_lighting._colortemp + self._circadian_lighting._colortemp + if not self.is_sleep() + else self._sleep_colortemp ) def calc_ct(self): @@ -270,14 +270,13 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def calc_brightness(self): if self._disable_brightness_adjust is True: return None - elif self.is_sleep(): + if self.is_sleep(): return self._sleep_brightness - elif self._circadian_lighting._percent > 0: + if self._circadian_lighting._percent > 0: return self._max_brightness - else: - delta_brightness = self._max_brightness - self._min_brightness - procent = (100 + self._circadian_lighting._percent) / 100 - return (delta_brightness * procent) + self._min_brightness + delta_brightness = self._max_brightness - self._min_brightness + procent = (100 + self._circadian_lighting._percent) / 100 + return (delta_brightness * procent) + self._min_brightness @log(logger=_LOGGER) def _update_switch(self, lights=None, transition=None, force=False): @@ -298,10 +297,9 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def _should_adjust(self): if self._state is not True: return False - elif self._is_disabled(): + if self._is_disabled(): return False - else: - return True + return True def _adjust_lights(self, lights, transition=None): if not self._should_adjust(): From 49a0e461d744d1a6b20701765c6f4a46d91e65e8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 23:57:16 +0200 Subject: [PATCH 51/69] introduce self._lights_types --- .../circadian_lighting/switch.py | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index a342b045..f390360b 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -179,7 +179,16 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._initial_transition = initial_transition self._once_only = once_only - self._lights = lights_ct + lights_rgb + lights_xy + lights_brightness + self._lights_types = {} + for light in lights_ct: + self._lights_types[light] = "ct" + for light in lights_rgb: + self._lights_types[light] = "rgb" + for light in lights_xy: + self._lights_types[light] = "xy" + for light in lights_brightness: + self._lights_types[light] = "brightness" + self._lights = list(self._lights_types.keys()) # Register callbacks dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self._update_switch) @@ -319,33 +328,18 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if transition is not None: service_data[ATTR_TRANSITION] = transition - # Set color of array of ct. - if light in self._lights_ct: - which = "CT" + light_type = self._lights_types[light] + if light_type == "ct": service_data[ATTR_COLOR_TEMP] = int(self.calc_ct()) - - # Set color of array of rgb. - elif light in self._lights_rgb: - which = "RGB" + elif light_type == "rgb": service_data[ATTR_RGB_COLOR] = tuple(map(int, self.calc_rgb())) - - # Set color of array of xy. - elif light in self._lights_xy: - which = "XY" + elif light_type == "xy": service_data[ATTR_XY_COLOR] = self.calc_xy() if service_data.get(ATTR_BRIGHTNESS, False): service_data[ATTR_WHITE_VALUE] = service_data[ATTR_BRIGHTNESS] - # Set color of array of brightness. - elif light in self._lights_brightness: - which = "Brightness" - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - key_value_strings = [ - f"{k}: {v}" for k, v in service_data.items() if k != ATTR_ENTITY_ID - ] - msg = ", ".join(key_value_strings) - _LOGGER.debug(f"{light} {which} Adjusted - {msg}") + _LOGGER.debug(f"{light} {light_type} Adjusted - {service_data}") @log(with_return=True, logger=_LOGGER) def light_state_changed(self, entity_id, from_state, to_state): From 1fd9d4764ce6717bd6e06a69b8ceb5850013e6d5 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Mon, 31 Aug 2020 23:58:57 +0200 Subject: [PATCH 52/69] be consistent with brackets --- custom_components/circadian_lighting/switch.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index f390360b..1999781a 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -55,15 +55,15 @@ CONF_LIGHTS_RGB = "lights_rgb" CONF_LIGHTS_XY = "lights_xy" CONF_LIGHTS_BRIGHT = "lights_brightness" CONF_DISABLE_BRIGHTNESS_ADJUST = "disable_brightness_adjust" -CONF_MIN_BRIGHT, DEFAULT_MIN_BRIGHT = ("min_brightness", 1) -CONF_MAX_BRIGHT, DEFAULT_MAX_BRIGHT = ("max_brightness", 100) +CONF_MIN_BRIGHT, DEFAULT_MIN_BRIGHT = "min_brightness", 1 +CONF_MAX_BRIGHT, DEFAULT_MAX_BRIGHT = "max_brightness", 100 CONF_SLEEP_ENTITY = "sleep_entity" CONF_SLEEP_STATE = "sleep_state" -CONF_SLEEP_CT, DEFAULT_SLEEP_CT = ("sleep_colortemp", 1000) -CONF_SLEEP_BRIGHT, DEFAULT_SLEEP_BRIGHT = ("sleep_brightness", 1) +CONF_SLEEP_CT, DEFAULT_SLEEP_CT = "sleep_colortemp", 1000 +CONF_SLEEP_BRIGHT, DEFAULT_SLEEP_BRIGHT = "sleep_brightness", 1 CONF_DISABLE_ENTITY = "disable_entity" CONF_DISABLE_STATE = "disable_state" -CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION = ("initial_transition", 1) +CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION = "initial_transition", 1 CONF_ONCE_ONLY = "once_only" PLATFORM_SCHEMA = vol.Schema( From d865cdbf9fe83c0605ef3f8c468a001ea6177800 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 00:01:34 +0200 Subject: [PATCH 53/69] remove unused attributes --- custom_components/circadian_lighting/switch.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 1999781a..9e4e99f7 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -163,10 +163,6 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._icon = ICON self._hs_color = None self._brightness = None - self._lights_ct = lights_ct - self._lights_rgb = lights_rgb - self._lights_xy = lights_xy - self._lights_brightness = lights_brightness self._disable_brightness_adjust = disable_brightness_adjust self._min_brightness = min_brightness self._max_brightness = max_brightness From 9dc04bc7c1d81f7ec42ab8837bdd9645140f1dad Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 00:11:36 +0200 Subject: [PATCH 54/69] remove code that wasn't needed --- .../circadian_lighting/__init__.py | 1 - .../circadian_lighting/sensor.py | 26 +++---------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index f5390072..6de46c6f 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -172,7 +172,6 @@ class CircadianLighting: self._latitude = latitude self._longitude = longitude self._elevation = elevation - self._interval = interval self._transition = transition self._timezone = self.get_timezone() self._percent = self.calc_percent() diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index 082250d3..76971eb5 100755 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -7,11 +7,7 @@ import logging from homeassistant.helpers.dispatcher import dispatcher_connect from homeassistant.helpers.entity import Entity -from custom_components.circadian_lighting import ( - CIRCADIAN_LIGHTING_UPDATE_TOPIC, - DOMAIN, - log, -) +from custom_components.circadian_lighting import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -44,16 +40,8 @@ class CircadianSensor(Entity): self._circadian_lighting = circadian_lighting self._name = "Circadian Values" self._entity_id = "sensor.circadian_values" - self._state = self._circadian_lighting._percent self._unit_of_measurement = "%" self._icon = ICON - self._hs_color = self._circadian_lighting._hs_color - self._colortemp = self._circadian_lighting._colortemp - self._rgb_color = self._circadian_lighting._rgb_color - self._xy_color = self._circadian_lighting._xy_color - - # Register callbacks - dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self.update_sensor) @property def entity_id(self): @@ -68,7 +56,7 @@ class CircadianSensor(Entity): @property def state(self): """Return the state of the sensor.""" - return self._state + return self._circadian_lighting._percent @property def unit_of_measurement(self): @@ -82,7 +70,7 @@ class CircadianSensor(Entity): @property def hs_color(self): - return self._hs_color + return self._circadian_lighting._hs_color @property def device_state_attributes(self): @@ -99,11 +87,3 @@ class CircadianSensor(Entity): This is the only method that should fetch new data for Home Assistant. """ self._circadian_lighting.update() - - @log(logger=_LOGGER) - def update_sensor(self): - self._state = self._circadian_lighting._percent - self._hs_color = self._circadian_lighting._hs_color - self._colortemp = self._circadian_lighting._colortemp - self._rgb_color = self._circadian_lighting._rgb_color - self._xy_color = self._circadian_lighting._xy_color From 98efae7e4040bb8798ca1b59da44be6885524bf7 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 00:23:49 +0200 Subject: [PATCH 55/69] be explicit in unpacking RGB --- custom_components/circadian_lighting/switch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 9e4e99f7..8519fe9f 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -328,7 +328,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if light_type == "ct": service_data[ATTR_COLOR_TEMP] = int(self.calc_ct()) elif light_type == "rgb": - service_data[ATTR_RGB_COLOR] = tuple(map(int, self.calc_rgb())) + r, g, b = self.calc_rgb() + service_data[ATTR_RGB_COLOR] = (int(r), int(g), int(b)) elif light_type == "xy": service_data[ATTR_XY_COLOR] = self.calc_xy() if service_data.get(ATTR_BRIGHTNESS, False): From 76b544682ef0a9997f571cfbbbbd679c5b023882 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 08:38:19 +0200 Subject: [PATCH 56/69] fix typos and use self._brightness --- custom_components/circadian_lighting/switch.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 8519fe9f..312d2c91 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -272,16 +272,16 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def calc_hs(self): return color_xy_to_hs(*self.calc_xy()) - def calc_brightness(self): - if self._disable_brightness_adjust is True: + def calc_brightness(self) -> float: + if self._disable_brightness_adjust: return None if self.is_sleep(): return self._sleep_brightness if self._circadian_lighting._percent > 0: return self._max_brightness delta_brightness = self._max_brightness - self._min_brightness - procent = (100 + self._circadian_lighting._percent) / 100 - return (delta_brightness * procent) + self._min_brightness + percent = (100 + self._circadian_lighting._percent) / 100 + return (delta_brightness * percent) + self._min_brightness @log(logger=_LOGGER) def _update_switch(self, lights=None, transition=None, force=False): @@ -318,9 +318,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): continue service_data = {ATTR_ENTITY_ID: light} - brightness = self._brightness - if brightness is not None: - service_data[ATTR_BRIGHTNESS] = int((brightness / 100) * 254) + if self._brightness is not None: + service_data[ATTR_BRIGHTNESS] = int((self._brightness / 100) * 254) if transition is not None: service_data[ATTR_TRANSITION] = transition From 6ffddbda61b5dd78efa3bdcb58ed788e5794e129 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 08:39:19 +0200 Subject: [PATCH 57/69] remove unused import and code --- custom_components/circadian_lighting/sensor.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index 76971eb5..4df3cc1e 100755 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -4,13 +4,10 @@ Circadian Lighting Sensor for Home-Assistant. import logging -from homeassistant.helpers.dispatcher import dispatcher_connect from homeassistant.helpers.entity import Entity from custom_components.circadian_lighting import DOMAIN -_LOGGER = logging.getLogger(__name__) - ICON = "mdi:theme-light-dark" From b70191d634004f23d6b53dabab3cacd803e5983b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 08:40:19 +0200 Subject: [PATCH 58/69] rename once_only -> only_once --- custom_components/circadian_lighting/switch.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 312d2c91..16a38e97 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -64,7 +64,7 @@ CONF_SLEEP_BRIGHT, DEFAULT_SLEEP_BRIGHT = "sleep_brightness", 1 CONF_DISABLE_ENTITY = "disable_entity" CONF_DISABLE_STATE = "disable_state" CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION = "initial_transition", 1 -CONF_ONCE_ONLY = "once_only" +CONF_ONLY_ONCE = "only_once" PLATFORM_SCHEMA = vol.Schema( { @@ -94,7 +94,7 @@ PLATFORM_SCHEMA = vol.Schema( vol.Optional( CONF_INITIAL_TRANSITION, default=DEFAULT_INITIAL_TRANSITION ): VALID_TRANSITION, - vol.Optional(CONF_ONCE_ONLY, default=False): cv.boolean, + vol.Optional(CONF_ONLY_ONCE, default=False): cv.boolean, } ) @@ -121,7 +121,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): disable_entity=config.get(CONF_DISABLE_ENTITY), disable_state=config.get(CONF_DISABLE_STATE), initial_transition=config.get(CONF_INITIAL_TRANSITION), - once_only=config.get(CONF_ONCE_ONLY), + only_once=config.get(CONF_ONLY_ONCE), ) add_devices([switch]) @@ -152,7 +152,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): disable_entity, disable_state, initial_transition, - once_only, + only_once, ): """Initialize the Circadian Lighting switch.""" self.hass = hass @@ -173,7 +173,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._disable_entity = disable_entity self._disable_state = disable_state self._initial_transition = initial_transition - self._once_only = once_only + self._only_once = only_once self._lights_types = {} for light in lights_ct: @@ -285,7 +285,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): @log(logger=_LOGGER) def _update_switch(self, lights=None, transition=None, force=False): - if self._once_only and not force: + if self._only_once and not force: return self._hs_color = self.calc_hs() self._brightness = self.calc_brightness() From 4c9d1f66598f4ec0d47565cc7aed84b207afe4da Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 18:39:16 +0200 Subject: [PATCH 59/69] use async functions and call trackers/listeners in the correct place --- .../circadian_lighting/__init__.py | 85 ++++++++----------- .../circadian_lighting/sensor.py | 31 ++++--- .../circadian_lighting/switch.py | 74 ++++++++-------- 3 files changed, 93 insertions(+), 97 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 6de46c6f..281d9afa 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -29,11 +29,10 @@ Technical notes: I had to make a lot of assumptions when writing this app import logging from datetime import timedelta -import inspect - -import voluptuous as vol import astral +import voluptuous as vol + import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ATTR_TRANSITION, VALID_TRANSITION from homeassistant.const import ( @@ -44,9 +43,13 @@ from homeassistant.const import ( SUN_EVENT_SUNSET, ) from homeassistant.helpers.discovery import load_platform -from homeassistant.helpers.dispatcher import dispatcher_send -from homeassistant.helpers.event import track_sunrise, track_sunset, track_time_change -from homeassistant.util import Throttle +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import ( + async_track_sunrise, + async_track_sunset, + async_track_time_change, + async_track_time_interval, +) from homeassistant.util.color import ( color_RGB_to_xy, color_temperature_to_rgb, @@ -89,7 +92,7 @@ CONFIG_SCHEMA = vol.Schema( vol.Optional(CONF_LATITUDE): cv.latitude, vol.Optional(CONF_LONGITUDE): cv.longitude, vol.Optional(CONF_ELEVATION): float, - vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.positive_int, + vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.time_period, vol.Optional( ATTR_TRANSITION, default=DEFAULT_TRANSITION ): VALID_TRANSITION, @@ -100,26 +103,6 @@ CONFIG_SCHEMA = vol.Schema( ) -def log(with_return=False, logger=_LOGGER): - def _log(func): - def wrapper(*args, **kwargs): - func_args = inspect.signature(func).bind(*args, **kwargs).arguments - key_value_pairs = ( - f"{k}={v!r}" for k, v in func_args.items() if k != "self" - ) - func_args_str = ", ".join(key_value_pairs) - out = f"{func.__qualname__}({func_args_str})" - result = func(*args, **kwargs) - if with_return: - out += f" -> {result}" - logger.debug(out) - return result - - return wrapper - - return _log - - def setup(hass, config): """Set up the Circadian Lighting component.""" conf = config[DOMAIN] @@ -180,23 +163,30 @@ class CircadianLighting: self._xy_color = self.calc_xy() self._hs_color = self.calc_hs() - self.update = Throttle(timedelta(seconds=interval))(self._update) + if self._manual_time["sunrise"] is not None: + async_track_time_change( + self.hass, + self.update, + hour=self._manual_time["sunrise"].hour, + minute=self._manual_time["sunrise"].minute, + second=self._manual_time["sunrise"].second, + ) + else: + async_track_sunrise(self.hass, self.update, self._sunrise_offset) - for which in ["sunrise", "sunrise"]: - time = self._manual_time[which] - if time is not None: - track_time_change( - self.hass, - self._update, - hour=time.hour, - minute=time.minute, - second=time.second, - ) + if self._manual_time["sunset"] is not None: + async_track_time_change( + self.hass, + self.update, + hour=self._manual_time["sunset"].hour, + minute=self._manual_time["sunset"].minute, + second=self._manual_time["sunset"].second, + ) + else: + async_track_sunset(self.hass, self.update, self._sunset_offset) - track_sunrise(self.hass, self._update, self._sunrise_offset) - track_sunset(self.hass, self._update, self._sunset_offset) + async_track_time_interval(self.hass, self.update, interval) - @log(with_return=True) def get_timezone(self): tf = TimezoneFinder() timezone_string = tf.timezone_at(lng=self._longitude, lat=self._latitude) @@ -263,8 +253,8 @@ class CircadianLighting: today = self.get_sunrise_sunset(now) if now_ts < today[SUN_EVENT_SUNRISE]: - # It's before sunrise (after midnight) - # Because it's before sunrise (and after midnight) sunset must have happend yesterday + # It's before sunrise (after midnight), because it's before + # sunrise (and after midnight) sunset must have happend yesterday. yesterday = self.get_sunrise_sunset(now - timedelta(days=1)) today[SUN_EVENT_SUNSET] = yesterday[SUN_EVENT_SUNSET] if ( @@ -274,8 +264,8 @@ class CircadianLighting: # Solar midnight is after sunset so use yesterdays's time today[SUN_EVENT_MIDNIGHT] = yesterday[SUN_EVENT_MIDNIGHT] elif now_ts > today[SUN_EVENT_SUNSET]: - # It's after sunset (before midnight) - # Because it's after sunset (and before midnight) sunrise should happen tomorrow + # It's after sunset (before midnight), because it's after sunset + # (and before midnight) sunrise should happen tomorrow. tomorrow = self.get_sunrise_sunset(now + timedelta(days=1)) today[SUN_EVENT_SUNRISE] = tomorrow[SUN_EVENT_SUNRISE] if ( @@ -335,12 +325,11 @@ class CircadianLighting: def calc_hs(self): return color_xy_to_hs(*self.calc_xy()) - @log() - def _update(self): + async def update(self, _=None): """Update Circadian Values.""" self._percent = self.calc_percent() self._colortemp = self.calc_colortemp() self._rgb_color = self.calc_rgb() self._xy_color = self.calc_xy() self._hs_color = self.calc_hs() - dispatcher_send(self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC) + async_dispatcher_send(self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC) diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index 4df3cc1e..b924fa3f 100755 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -2,11 +2,11 @@ Circadian Lighting Sensor for Home-Assistant. """ -import logging - +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from custom_components.circadian_lighting import DOMAIN +from . import CIRCADIAN_LIGHTING_UPDATE_TOPIC, DOMAIN ICON = "mdi:theme-light-dark" @@ -16,11 +16,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): circadian_lighting = hass.data.get(DOMAIN) if circadian_lighting is not None: sensor = CircadianSensor(hass, circadian_lighting) - add_devices([sensor]) + add_devices([sensor], True) def update(call=None): """Update component.""" - circadian_lighting._update() + circadian_lighting.update() service_name = "values_update" hass.services.register(DOMAIN, service_name, update) @@ -78,9 +78,20 @@ class CircadianSensor(Entity): "xy_color": self._circadian_lighting._xy_color, } - def update(self): - """Fetch new state data for the sensor. + @property + def should_poll(self) -> bool: + """Disable polling.""" + return False - This is the only method that should fetch new data for Home Assistant. - """ - self._circadian_lighting.update() + async def async_added_to_hass(self) -> None: + """Connect dispatcher to signal from CircadianLighting object.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self._update_callback + ) + ) + + @callback + def _update_callback(self) -> None: + """Triggers update of properties after receiving signal from CircadianLighting.""" + self.async_schedule_update_ha_state(force_refresh=False) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 16a38e97..584d0785 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -2,10 +2,12 @@ Circadian Lighting Switch for Home-Assistant. """ +import functools import logging -import homeassistant.helpers.config_validation as cv import voluptuous as vol + +import homeassistant.helpers.config_validation as cv from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -16,6 +18,7 @@ from homeassistant.components.light import ( ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.light import VALID_TRANSITION, is_on +from homeassistant.components.switch import SwitchEntity from homeassistant.const import ( ATTR_ENTITY_ID, CONF_NAME, @@ -23,8 +26,8 @@ from homeassistant.const import ( SERVICE_TURN_ON, STATE_ON, ) -from homeassistant.helpers.dispatcher import dispatcher_connect -from homeassistant.helpers.event import track_state_change +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.util import slugify from homeassistant.util.color import ( @@ -34,17 +37,7 @@ from homeassistant.util.color import ( color_xy_to_hs, ) -from custom_components.circadian_lighting import ( - CIRCADIAN_LIGHTING_UPDATE_TOPIC, - DOMAIN, - log, -) - -try: - from homeassistant.components.switch import SwitchEntity -except ImportError: - from homeassistant.components.switch import SwitchDevice as SwitchEntity - +from . import CIRCADIAN_LIGHTING_UPDATE_TOPIC, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -186,14 +179,6 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._lights_types[light] = "brightness" self._lights = list(self._lights_types.keys()) - # Register callbacks - dispatcher_connect(hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self._update_switch) - track_state_change(hass, self._lights, self.light_state_changed) - if self._sleep_entity is not None: - track_state_change(hass, self._sleep_entity, self.sleep_state_changed) - if self._disable_entity is not None: - track_state_change(hass, self._disable_entity, self.disable_state_changed) - @property def entity_id(self): """Return the entity ID of the switch.""" @@ -211,9 +196,33 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): async def async_added_to_hass(self): """Call when entity about to be added to hass.""" - # If not None, we got an initial value. - await super().async_added_to_hass() - if self._state is not None: + # Add callback + self.async_on_remove( + async_dispatcher_connect( + self.hass, CIRCADIAN_LIGHTING_UPDATE_TOPIC, self._update_switch + ) + ) + + # Add listeners + async_track_state_change(self.hass, self._lights, self.light_state_changed) + + if self._sleep_entity is not None: + async_track_state_change( + self.hass, self._sleep_entity, self.sleep_state_changed + ) + + if self._disable_entity is not None: + disable_state_changed = functools.partial( + self._update_switch, transition=self._initial_transition, force=True + ) + async_track_state_change( + self.hass, + self._disable_entity, + disable_state_changed, + from_state=self._disable_state, + ) + + if self._state is not None: # If not None, we got an initial value return state = await self.async_get_last_state() @@ -237,16 +246,13 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): """Turn on circadian lighting.""" self._state = True self._update_switch(transition=self._initial_transition, force=True) - self.schedule_update_ha_state() def turn_off(self, **kwargs): """Turn off circadian lighting.""" self._state = False - self.schedule_update_ha_state() self._hs_color = None self._brightness = None - @log(with_return=True, logger=_LOGGER) def is_sleep(self): return ( self._sleep_entity is not None @@ -283,7 +289,6 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): percent = (100 + self._circadian_lighting._percent) / 100 return (delta_brightness * percent) + self._min_brightness - @log(logger=_LOGGER) def _update_switch(self, lights=None, transition=None, force=False): if self._only_once and not force: return @@ -291,14 +296,12 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._brightness = self.calc_brightness() self._adjust_lights(lights or self._lights, transition) - @log(with_return=True, logger=_LOGGER) def _is_disabled(self): return ( self._disable_entity is not None and self.hass.states.get(self._disable_entity).state in self._disable_state ) - @log(with_return=True, logger=_LOGGER) def _should_adjust(self): if self._state is not True: return False @@ -306,7 +309,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return False return True - def _adjust_lights(self, lights, transition=None): + def _adjust_lights(self, lights, transition): if not self._should_adjust(): return @@ -337,17 +340,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) _LOGGER.debug(f"{light} {light_type} Adjusted - {service_data}") - @log(with_return=True, logger=_LOGGER) def light_state_changed(self, entity_id, from_state, to_state): if to_state.state == "on" and from_state.state != "on": self._update_switch([entity_id], self._initial_transition, force=True) - @log(with_return=True, logger=_LOGGER) def sleep_state_changed(self, entity_id, from_state, to_state): if to_state.state in self._sleep_state or from_state.state in self._sleep_state: self._update_switch(transition=self._initial_transition, force=True) - - @log(with_return=True, logger=_LOGGER) - def disable_state_changed(self, entity_id, from_state, to_state): - if from_state.state in self._disable_state: - self._update_switch(transition=self._initial_transition, force=True) From 9e03d5aeebea2694690e6ac5a1101a38737344fd Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 18:40:19 +0200 Subject: [PATCH 60/69] remove arg in get_sunrise_sunset and call update in __init__ --- .../circadian_lighting/__init__.py | 30 ++++++++++--------- .../circadian_lighting/sensor.py | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 281d9afa..3477a175 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -157,11 +157,13 @@ class CircadianLighting: self._elevation = elevation self._transition = transition self._timezone = self.get_timezone() - self._percent = self.calc_percent() - self._colortemp = self.calc_colortemp() - self._rgb_color = self.calc_rgb() - self._xy_color = self.calc_xy() - self._hs_color = self.calc_hs() + + self._percent = None + self._colortemp = None + self._rgb_color = None + self._xy_color = None + self._hs_color = None + self.update() if self._manual_time["sunrise"] is not None: async_track_time_change( @@ -201,7 +203,7 @@ class CircadianLighting: microsecond=other_date.microsecond, ) - def get_sunrise_sunset(self, date, as_timestamps=True): + def get_sunrise_sunset(self, date): if ( self._manual_time["sunrise"] is not None and self._manual_time["sunset"] is not None @@ -237,15 +239,15 @@ class CircadianLighting: sunset = sunset + self._sunset_offset datetimes = { - SUN_EVENT_SUNRISE: sunrise.astimezone(self._timezone), - SUN_EVENT_SUNSET: sunset.astimezone(self._timezone), - SUN_EVENT_NOON: solar_noon.astimezone(self._timezone), - SUN_EVENT_MIDNIGHT: solar_midnight.astimezone(self._timezone), + SUN_EVENT_SUNRISE: sunrise, + SUN_EVENT_SUNSET: sunset, + SUN_EVENT_NOON: solar_noon, + SUN_EVENT_MIDNIGHT: solar_midnight, + } + + return { + k: dt.astimezone(self._timezone).timestamp() for k, dt in datetimes.items() } - if as_timestamps: - return {k: dt.timestamp() for k, dt in datetimes.items()} - else: - return datetimes def calc_percent(self): now = dt_now(self._timezone) diff --git a/custom_components/circadian_lighting/sensor.py b/custom_components/circadian_lighting/sensor.py index b924fa3f..35947f92 100755 --- a/custom_components/circadian_lighting/sensor.py +++ b/custom_components/circadian_lighting/sensor.py @@ -93,5 +93,5 @@ class CircadianSensor(Entity): @callback def _update_callback(self) -> None: - """Triggers update of properties after receiving signal from CircadianLighting.""" + """Triggers update of properties.""" self.async_schedule_update_ha_state(force_refresh=False) From ce19976c5ee8b73a4bffb5cbbc0bb6df2ad7c9d3 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 18:49:16 +0200 Subject: [PATCH 61/69] fixes from previous commit --- .../circadian_lighting/__init__.py | 11 ++++---- .../circadian_lighting/switch.py | 25 ++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index 3477a175..ed40ae1c 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -158,12 +158,11 @@ class CircadianLighting: self._transition = transition self._timezone = self.get_timezone() - self._percent = None - self._colortemp = None - self._rgb_color = None - self._xy_color = None - self._hs_color = None - self.update() + self._percent = self.calc_percent() + self._colortemp = self.calc_colortemp() + self._rgb_color = self.calc_rgb() + self._xy_color = self.calc_xy() + self._hs_color = self.calc_hs() if self._manual_time["sunrise"] is not None: async_track_time_change( diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 584d0785..86631047 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -2,7 +2,6 @@ Circadian Lighting Switch for Home-Assistant. """ -import functools import logging import voluptuous as vol @@ -204,7 +203,9 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): ) # Add listeners - async_track_state_change(self.hass, self._lights, self.light_state_changed) + async_track_state_change( + self.hass, self._lights, self.light_state_changed, to_state="on" + ) if self._sleep_entity is not None: async_track_state_change( @@ -212,13 +213,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): ) if self._disable_entity is not None: - disable_state_changed = functools.partial( - self._update_switch, transition=self._initial_transition, force=True - ) async_track_state_change( self.hass, self._disable_entity, - disable_state_changed, + self.disable_state_changed, from_state=self._disable_state, ) @@ -245,7 +243,7 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def turn_on(self, **kwargs): """Turn on circadian lighting.""" self._state = True - self._update_switch(transition=self._initial_transition, force=True) + self._force_update_switch() def turn_off(self, **kwargs): """Turn off circadian lighting.""" @@ -296,6 +294,11 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._brightness = self.calc_brightness() self._adjust_lights(lights or self._lights, transition) + def _force_update_switch(self, lights=None): + return self._update_switch( + lights, transition=self._initial_transition, force=True + ) + def _is_disabled(self): return ( self._disable_entity is not None @@ -342,8 +345,12 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def light_state_changed(self, entity_id, from_state, to_state): if to_state.state == "on" and from_state.state != "on": - self._update_switch([entity_id], self._initial_transition, force=True) + self._force_update_switch(lights=[entity_id]) def sleep_state_changed(self, entity_id, from_state, to_state): if to_state.state in self._sleep_state or from_state.state in self._sleep_state: - self._update_switch(transition=self._initial_transition, force=True) + self._force_update_switch() + + def disable_state_changed(self, entity_id, from_state, to_state): + if from_state.state in self._disable_state: + self._force_update_switch() From f02be53d3fbf3aae32ee7107fee33ab1ae4c7bf2 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 18:55:12 +0200 Subject: [PATCH 62/69] make methods private --- .../circadian_lighting/switch.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 86631047..b7709ed8 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -204,19 +204,19 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): # Add listeners async_track_state_change( - self.hass, self._lights, self.light_state_changed, to_state="on" + self.hass, self._lights, self._light_state_changed, to_state="on" ) if self._sleep_entity is not None: async_track_state_change( - self.hass, self._sleep_entity, self.sleep_state_changed + self.hass, self._sleep_entity, self._sleep_state_changed ) if self._disable_entity is not None: async_track_state_change( self.hass, self._disable_entity, - self.disable_state_changed, + self._disable_state_changed, from_state=self._disable_state, ) @@ -264,19 +264,19 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): else self._sleep_colortemp ) - def calc_ct(self): + def _calc_ct(self): return color_temperature_kelvin_to_mired(self._color_temperature()) - def calc_rgb(self): + def _calc_rgb(self): return color_temperature_to_rgb(self._color_temperature()) - def calc_xy(self): - return color_RGB_to_xy(*self.calc_rgb()) + def _calc_xy(self): + return color_RGB_to_xy(*self._calc_rgb()) - def calc_hs(self): - return color_xy_to_hs(*self.calc_xy()) + def _calc_hs(self): + return color_xy_to_hs(*self._calc_xy()) - def calc_brightness(self) -> float: + def _calc_brightness(self) -> float: if self._disable_brightness_adjust: return None if self.is_sleep(): @@ -290,8 +290,8 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): def _update_switch(self, lights=None, transition=None, force=False): if self._only_once and not force: return - self._hs_color = self.calc_hs() - self._brightness = self.calc_brightness() + self._hs_color = self._calc_hs() + self._brightness = self._calc_brightness() self._adjust_lights(lights or self._lights, transition) def _force_update_switch(self, lights=None): @@ -331,26 +331,26 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): light_type = self._lights_types[light] if light_type == "ct": - service_data[ATTR_COLOR_TEMP] = int(self.calc_ct()) + service_data[ATTR_COLOR_TEMP] = int(self._calc_ct()) elif light_type == "rgb": - r, g, b = self.calc_rgb() + r, g, b = self._calc_rgb() service_data[ATTR_RGB_COLOR] = (int(r), int(g), int(b)) elif light_type == "xy": - service_data[ATTR_XY_COLOR] = self.calc_xy() + service_data[ATTR_XY_COLOR] = self._calc_xy() if service_data.get(ATTR_BRIGHTNESS, False): service_data[ATTR_WHITE_VALUE] = service_data[ATTR_BRIGHTNESS] self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) _LOGGER.debug(f"{light} {light_type} Adjusted - {service_data}") - def light_state_changed(self, entity_id, from_state, to_state): + def _light_state_changed(self, entity_id, from_state, to_state): if to_state.state == "on" and from_state.state != "on": self._force_update_switch(lights=[entity_id]) - def sleep_state_changed(self, entity_id, from_state, to_state): + def _sleep_state_changed(self, entity_id, from_state, to_state): if to_state.state in self._sleep_state or from_state.state in self._sleep_state: self._force_update_switch() - def disable_state_changed(self, entity_id, from_state, to_state): + def _disable_state_changed(self, entity_id, from_state, to_state): if from_state.state in self._disable_state: self._force_update_switch() From 25cbcf635b6479ebe01d8d34633527860fd0e161 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 18:56:10 +0200 Subject: [PATCH 63/69] remove ._manual_time dict --- .../circadian_lighting/__init__.py | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index ed40ae1c..ffc91bed 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -148,10 +148,8 @@ class CircadianLighting: self._max_colortemp = max_colortemp self._sunrise_offset = sunrise_offset self._sunset_offset = sunset_offset - self._manual_time = { - "sunrise": sunrise_time, - "sunset": sunset_time, - } + self._manual_sunset = sunset_time + self._manual_sunrise = sunrise_time self._latitude = latitude self._longitude = longitude self._elevation = elevation @@ -164,24 +162,24 @@ class CircadianLighting: self._xy_color = self.calc_xy() self._hs_color = self.calc_hs() - if self._manual_time["sunrise"] is not None: + if self._manual_sunrise is not None: async_track_time_change( self.hass, self.update, - hour=self._manual_time["sunrise"].hour, - minute=self._manual_time["sunrise"].minute, - second=self._manual_time["sunrise"].second, + hour=self._manual_sunrise.hour, + minute=self._manual_sunrise.minute, + second=self._manual_sunrise.second, ) else: async_track_sunrise(self.hass, self.update, self._sunrise_offset) - if self._manual_time["sunset"] is not None: + if self._manual_sunset is not None: async_track_time_change( self.hass, self.update, - hour=self._manual_time["sunset"].hour, - minute=self._manual_time["sunset"].minute, - second=self._manual_time["sunset"].second, + hour=self._manual_sunset.hour, + minute=self._manual_sunset.minute, + second=self._manual_sunset.second, ) else: async_track_sunset(self.hass, self.update, self._sunset_offset) @@ -194,7 +192,7 @@ class CircadianLighting: return get_time_zone(timezone_string) def _replace_time(self, date, key): - other_date = self._manual_time[key] + other_date = self._manual_sunrise if key == "sunrise" else self._manual_sunset return date.replace( hour=other_date.hour, minute=other_date.minute, @@ -203,10 +201,7 @@ class CircadianLighting: ) def get_sunrise_sunset(self, date): - if ( - self._manual_time["sunrise"] is not None - and self._manual_time["sunset"] is not None - ): + if self._manual_sunrise is not None and self._manual_sunset is not None: sunrise = self._replace_time(date, "sunrise") sunset = self._replace_time(date, "sunset") solar_noon = sunrise + (sunset - sunrise) / 2 @@ -219,12 +214,12 @@ class CircadianLighting: location.longitude = self._longitude location.elevation = self._elevation - if self._manual_time["sunrise"] is not None: + if self._manual_sunrise is not None: sunrise = self._replace_time(date, "sunrise") else: sunrise = location.sunrise(date) - if self._manual_time["sunset"] is not None: + if self._manual_sunset is not None: sunset = self._replace_time(date, "sunset") else: sunset = location.sunset(date) From 92d367e3bc9de5d65f00854b3b86db4dc030ad8c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 18:59:40 +0200 Subject: [PATCH 64/69] unify _sleep_state_changed and _disable_state_changed into _state_changed --- .../circadian_lighting/switch.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index b7709ed8..d15ddc78 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -206,17 +206,16 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): async_track_state_change( self.hass, self._lights, self._light_state_changed, to_state="on" ) - + track_kwargs = dict(hass=self.hass, action=self._state_changed) if self._sleep_entity is not None: - async_track_state_change( - self.hass, self._sleep_entity, self._sleep_state_changed - ) + sleep_kwargs = dict(track_kwargs, entity_ids=self._sleep_entity) + async_track_state_change(**sleep_kwargs, to_state=self._sleep_state) + async_track_state_change(**sleep_kwargs, from_state=self._sleep_state) if self._disable_entity is not None: async_track_state_change( - self.hass, - self._disable_entity, - self._disable_state_changed, + **track_kwargs, + entity_ids=self._disable_entity, from_state=self._disable_state, ) @@ -347,10 +346,5 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if to_state.state == "on" and from_state.state != "on": self._force_update_switch(lights=[entity_id]) - def _sleep_state_changed(self, entity_id, from_state, to_state): - if to_state.state in self._sleep_state or from_state.state in self._sleep_state: - self._force_update_switch() - - def _disable_state_changed(self, entity_id, from_state, to_state): - if from_state.state in self._disable_state: - self._force_update_switch() + def _state_changed(self, entity_id, from_state, to_state): + self._force_update_switch() From 368bc9fddc9cb4fcc06141001a10c3a5589c8da6 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 20:02:13 +0200 Subject: [PATCH 65/69] shorten self._lights_types construction --- custom_components/circadian_lighting/switch.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index d15ddc78..0b0b4b2d 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -3,6 +3,7 @@ Circadian Lighting Switch for Home-Assistant. """ import logging +from itertools import repeat import voluptuous as vol @@ -166,16 +167,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): self._disable_state = disable_state self._initial_transition = initial_transition self._only_once = only_once - - self._lights_types = {} - for light in lights_ct: - self._lights_types[light] = "ct" - for light in lights_rgb: - self._lights_types[light] = "rgb" - for light in lights_xy: - self._lights_types[light] = "xy" - for light in lights_brightness: - self._lights_types[light] = "brightness" + self._lights_types = dict(zip(lights_ct, repeat("ct"))) + self._lights_types.update(zip(lights_rgb, repeat("rgb"))) + self._lights_types.update(zip(lights_xy, repeat("xy"))) + self._lights_types.update(zip(lights_brightness, repeat("brightness"))) self._lights = list(self._lights_types.keys()) @property From becc846287a808cd9d7f093bb1f38035f5060e90 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 1 Sep 2020 20:02:23 +0200 Subject: [PATCH 66/69] component -> platform --- custom_components/circadian_lighting/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/circadian_lighting/__init__.py b/custom_components/circadian_lighting/__init__.py index ffc91bed..d43b8cb4 100755 --- a/custom_components/circadian_lighting/__init__.py +++ b/custom_components/circadian_lighting/__init__.py @@ -104,7 +104,7 @@ CONFIG_SCHEMA = vol.Schema( def setup(hass, config): - """Set up the Circadian Lighting component.""" + """Set up the Circadian Lighting platform.""" conf = config[DOMAIN] hass.data[DOMAIN] = CircadianLighting( hass, From ed9389a1e7666724a5b4d3746580a75719dc83b9 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Wed, 2 Sep 2020 09:38:21 +0200 Subject: [PATCH 67/69] make the switch async to ensure all light adjusting happens at the same time --- .../circadian_lighting/switch.py | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 0b0b4b2d..953374b0 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -2,6 +2,7 @@ Circadian Lighting Switch for Home-Assistant. """ +import asyncio import logging from itertools import repeat @@ -234,10 +235,10 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): """Return the attributes of the switch.""" return {"hs_color": self._hs_color, "brightness": self._brightness} - def turn_on(self, **kwargs): + async def turn_on(self, **kwargs): """Turn on circadian lighting.""" self._state = True - self._force_update_switch() + await self._force_update_switch() def turn_off(self, **kwargs): """Turn off circadian lighting.""" @@ -281,15 +282,15 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): percent = (100 + self._circadian_lighting._percent) / 100 return (delta_brightness * percent) + self._min_brightness - def _update_switch(self, lights=None, transition=None, force=False): + async def _update_switch(self, lights=None, transition=None, force=False): if self._only_once and not force: return self._hs_color = self._calc_hs() self._brightness = self._calc_brightness() - self._adjust_lights(lights or self._lights, transition) + await self._adjust_lights(lights or self._lights, transition) - def _force_update_switch(self, lights=None): - return self._update_switch( + async def _force_update_switch(self, lights=None): + return await self._update_switch( lights, transition=self._initial_transition, force=True ) @@ -306,13 +307,14 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): return False return True - def _adjust_lights(self, lights, transition): + async def _adjust_lights(self, lights, transition): if not self._should_adjust(): return if transition is None: transition = self._circadian_lighting._transition + tasks = [] for light in lights: if not is_on(self.hass, light): continue @@ -334,12 +336,15 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if service_data.get(ATTR_BRIGHTNESS, False): service_data[ATTR_WHITE_VALUE] = service_data[ATTR_BRIGHTNESS] - self.hass.services.call(LIGHT_DOMAIN, SERVICE_TURN_ON, service_data) - _LOGGER.debug(f"{light} {light_type} Adjusted - {service_data}") + tasks.append(self.hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_ON, service_data + )) + if tasks: + await asyncio.wait(tasks) - def _light_state_changed(self, entity_id, from_state, to_state): + async def _light_state_changed(self, entity_id, from_state, to_state): if to_state.state == "on" and from_state.state != "on": - self._force_update_switch(lights=[entity_id]) + await self._force_update_switch(lights=[entity_id]) - def _state_changed(self, entity_id, from_state, to_state): - self._force_update_switch() + async def _state_changed(self, entity_id, from_state, to_state): + await self._force_update_switch() From dee9dd10f8acd4a9b9f23ee186a45e2e7886cdf0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Thu, 3 Sep 2020 23:56:48 +0200 Subject: [PATCH 68/69] run black --- custom_components/circadian_lighting/switch.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 953374b0..879fdf04 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -336,9 +336,11 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): if service_data.get(ATTR_BRIGHTNESS, False): service_data[ATTR_WHITE_VALUE] = service_data[ATTR_BRIGHTNESS] - tasks.append(self.hass.services.async_call( - LIGHT_DOMAIN, SERVICE_TURN_ON, service_data - )) + tasks.append( + self.hass.services.async_call( + LIGHT_DOMAIN, SERVICE_TURN_ON, service_data + ) + ) if tasks: await asyncio.wait(tasks) From 0264aeaa09af34c05c4e1a918330bb91344c5f82 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Fri, 4 Sep 2020 16:18:45 +0200 Subject: [PATCH 69/69] add a more useful debug message for state changing --- .../circadian_lighting/switch.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/custom_components/circadian_lighting/switch.py b/custom_components/circadian_lighting/switch.py index 879fdf04..9c0650f9 100755 --- a/custom_components/circadian_lighting/switch.py +++ b/custom_components/circadian_lighting/switch.py @@ -124,6 +124,39 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False +def _difference_between_states(from_state, to_state): + start = "Lights adjusting because " + if from_state is None and to_state is None: + return start + "Both states None" + if from_state is None: + return start + f"from_state: None, to_state: {to_state}" + if to_state is None: + return start + f"from_state: {from_state}, to_state: None" + + changed_attrs = ", ".join( + [ + f"{key}: {val}" + for key, val in to_state.attributes.items() + if from_state.attributes.get(key) != val + ] + ) + if from_state.state == to_state.state: + return start + ( + f"{from_state.entity_id} is still {to_state.state} but" + f" these attributes changes: {changed_attrs}." + ) + elif changed_attrs != "": + return start + ( + f"{from_state.entity_id} changed from {from_state.state} to" + f" {to_state.state} and these attributes changes: {changed_attrs}." + ) + else: + return start + ( + f"{from_state.entity_id} changed from {from_state.state} to" + f" {to_state.state} and no attributes changed." + ) + + class CircadianSwitch(SwitchEntity, RestoreEntity): """Representation of a Circadian Lighting switch.""" @@ -346,7 +379,9 @@ class CircadianSwitch(SwitchEntity, RestoreEntity): async def _light_state_changed(self, entity_id, from_state, to_state): if to_state.state == "on" and from_state.state != "on": + _LOGGER.debug(_difference_between_states(from_state, to_state)) await self._force_update_switch(lights=[entity_id]) async def _state_changed(self, entity_id, from_state, to_state): + _LOGGER.debug(_difference_between_states(from_state, to_state)) await self._force_update_switch()