run black, pyupgrade, and isort

This commit is contained in:
Bas Nijholt 2020-08-25 18:08:33 +02:00
commit 9b8a4ad51f
3 changed files with 470 additions and 241 deletions

View file

@ -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")

View file

@ -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")
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")

View file

@ -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: