From 9b8a4ad51f991730b675eb4d40f934d0bea100f8 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Tue, 25 Aug 2020 18:08:33 +0200 Subject: [PATCH] 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: