adaptive-lighting/custom_components/circadian_lighting/switch.py
Clayton Nummer be01be243d Define "initial transition" and use it when CL switch is turned on
A 1 sec transition was used when a light is turned on or when sleep state changes. The short transition should also be used when the CL switch is turned on. Also, because this is used in multiple spots, better to define it in one spot (this will also make it easier to make it configurable, if desired)
2019-08-04 14:16:59 -04:00

336 lines
No EOL
14 KiB
Python

"""
Circadian Lighting Switch for Home-Assistant.
"""
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
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,
ATTR_WHITE_VALUE, ATTR_XY_COLOR, DOMAIN as LIGHT_DOMAIN)
from homeassistant.components.switch import SwitchDevice
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'
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'
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'
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
})
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)
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)
add_devices([cs])
def update(call=None):
"""Update lights."""
cs.update_switch()
return True
else:
return False
class CircadianSwitch(SwitchDevice, 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):
"""Initialize the Circadian Lighting switch."""
self.hass = hass
self._cl = cl
self._name = name
self._entity_id = "switch." + slugify("{} {}".format('circadian_lighting', name))
self._state = None
self._icon = ICON
self._hs_color = 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
self._sleep_entity = sleep_entity
self._sleep_state = sleep_state
self._sleep_colortemp = sleep_colortemp
self._sleep_brightness = sleep_brightness
self._disable_entity = disable_entity
self._disable_state = disable_state
self._attributes = {}
self._attributes['hs_color'] = self._hs_color
self._attributes['brightness'] = None
self._lights = []
if lights_ct != None:
self._lights += lights_ct
if lights_rgb != None:
self._lights += lights_rgb
if lights_xy != None:
self._lights += lights_xy
if lights_brightness != None:
self._lights += lights_brightness
"""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)
@property
def entity_id(self):
"""Return the entity ID of the switch."""
return self._entity_id
@property
def name(self):
"""Return the name of the device if any."""
return self._name
@property
def is_on(self):
"""Return true if circadian lighting is on."""
return self._state
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:
return
state = await self.async_get_last_state()
self._state = state and state.state == STATE_ON
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return self._icon
@property
def hs_color(self):
return self._hs_color
@property
def device_state_attributes(self):
"""Return the attributes of the switch."""
return self._attributes
def turn_on(self, **kwargs):
"""Turn on circadian lighting."""
self._state = True
# Make initial update
self.update_switch(DEFAULT_INITIAL_TRANSITION)
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._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
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'])
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'])
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_brightness(self):
if self._disable_brightness_adjust is True:
return None
else:
if self.is_sleep():
_LOGGER.debug(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
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()
_LOGGER.debug(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")
return False
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:
_LOGGER.debug(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:
transition = self._cl.data['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
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(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):
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(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):
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(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):
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))
def light_state_changed(self, entity_id, from_state, to_state):
_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], DEFAULT_INITIAL_TRANSITION)
def sleep_state_changed(self, entity_id, from_state, to_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(DEFAULT_INITIAL_TRANSITION)