You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
15 KiB
362 lines
15 KiB
"""
|
|
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,
|
|
VALID_TRANSITION, ATTR_WHITE_VALUE, ATTR_XY_COLOR, DOMAIN as LIGHT_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'
|
|
|
|
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'
|
|
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
|
|
})
|
|
|
|
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)
|
|
add_devices([cs])
|
|
|
|
def update(call=None):
|
|
"""Update lights."""
|
|
cs.update_switch()
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
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):
|
|
"""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._initial_transition = initial_transition
|
|
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)
|
|
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."""
|
|
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(self._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):
|
|
try:
|
|
_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:
|
|
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))
|
|
if from_state.state == self._disable_state:
|
|
self.update_switch(self._initial_transition)
|
|
except:
|
|
pass
|