core/homeassistant/components/limitlessled/light.py

395 lines
12 KiB
Python
Raw Normal View History

"""Support for LimitlessLED bulbs."""
2015-05-18 23:27:09 +00:00
import logging
2016-09-30 02:06:28 +00:00
import voluptuous as vol
2019-07-31 19:25:30 +00:00
from homeassistant.const import CONF_NAME, CONF_HOST, CONF_PORT, CONF_TYPE, STATE_ON
2016-02-19 05:27:50 +00:00
from homeassistant.components.light import (
2019-07-31 19:25:30 +00:00
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_FLASH,
ATTR_HS_COLOR,
ATTR_TRANSITION,
EFFECT_COLORLOOP,
EFFECT_WHITE,
FLASH_LONG,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT,
SUPPORT_FLASH,
SUPPORT_COLOR,
SUPPORT_TRANSITION,
Light,
PLATFORM_SCHEMA,
)
2016-09-30 02:06:28 +00:00
import homeassistant.helpers.config_validation as cv
2019-07-31 19:25:30 +00:00
from homeassistant.util.color import color_temperature_mired_to_kelvin, color_hs_to_RGB
from homeassistant.helpers.restore_state import RestoreEntity
2016-09-30 02:06:28 +00:00
_LOGGER = logging.getLogger(__name__)
2019-07-31 19:25:30 +00:00
CONF_BRIDGES = "bridges"
CONF_GROUPS = "groups"
CONF_NUMBER = "number"
CONF_VERSION = "version"
CONF_FADE = "fade"
2016-09-30 02:06:28 +00:00
2019-07-31 19:25:30 +00:00
DEFAULT_LED_TYPE = "rgbw"
DEFAULT_PORT = 5987
2016-09-30 02:06:28 +00:00
DEFAULT_TRANSITION = 0
DEFAULT_VERSION = 6
DEFAULT_FADE = False
2016-09-30 02:06:28 +00:00
2019-07-31 19:25:30 +00:00
LED_TYPE = ["rgbw", "rgbww", "white", "bridge-led", "dimmer"]
2016-09-30 02:06:28 +00:00
2019-07-31 19:25:30 +00:00
EFFECT_NIGHT = "night"
2018-02-21 07:08:45 +00:00
MIN_SATURATION = 10
2016-09-30 02:06:28 +00:00
WHITE = [0, 0]
2019-07-31 19:25:30 +00:00
SUPPORT_LIMITLESSLED_WHITE = (
SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT | SUPPORT_TRANSITION
)
SUPPORT_LIMITLESSLED_DIMMER = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
SUPPORT_LIMITLESSLED_RGB = (
SUPPORT_BRIGHTNESS
| SUPPORT_EFFECT
| SUPPORT_FLASH
| SUPPORT_COLOR
| SUPPORT_TRANSITION
)
SUPPORT_LIMITLESSLED_RGBWW = (
SUPPORT_BRIGHTNESS
| SUPPORT_COLOR_TEMP
| SUPPORT_EFFECT
| SUPPORT_FLASH
| SUPPORT_COLOR
| SUPPORT_TRANSITION
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_BRIDGES): vol.All(
cv.ensure_list,
[
2016-09-30 02:06:28 +00:00
{
2019-07-31 19:25:30 +00:00
vol.Required(CONF_HOST): cv.string,
vol.Optional(
CONF_VERSION, default=DEFAULT_VERSION
): cv.positive_int,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(CONF_GROUPS): vol.All(
cv.ensure_list,
[
{
vol.Required(CONF_NAME): cv.string,
vol.Optional(
CONF_TYPE, default=DEFAULT_LED_TYPE
): vol.In(LED_TYPE),
vol.Required(CONF_NUMBER): cv.positive_int,
vol.Optional(
CONF_FADE, default=DEFAULT_FADE
): cv.boolean,
}
],
),
2016-09-30 02:06:28 +00:00
}
2019-07-31 19:25:30 +00:00
],
)
}
)
2016-09-30 02:06:28 +00:00
2015-05-18 23:27:09 +00:00
2015-11-28 17:21:00 +00:00
def rewrite_legacy(config):
2016-03-07 21:08:21 +00:00
"""Rewrite legacy configuration to new format."""
bridges = config.get(CONF_BRIDGES, [config])
2015-11-28 17:21:00 +00:00
new_bridges = []
2015-11-14 17:56:18 +00:00
for bridge_conf in bridges:
2015-11-28 17:21:00 +00:00
groups = []
2019-07-31 19:25:30 +00:00
if "groups" in bridge_conf:
groups = bridge_conf["groups"]
2015-11-28 17:21:00 +00:00
else:
2015-11-29 23:29:37 +00:00
_LOGGER.warning("Legacy configuration format detected")
2015-11-28 17:21:00 +00:00
for i in range(1, 5):
2019-07-31 19:25:30 +00:00
name_key = "group_%d_name" % i
2015-11-28 17:21:00 +00:00
if name_key in bridge_conf:
2019-07-31 19:25:30 +00:00
groups.append(
{
"number": i,
"type": bridge_conf.get(
"group_%d_type" % i, DEFAULT_LED_TYPE
),
"name": bridge_conf.get(name_key),
}
)
new_bridges.append(
{
"host": bridge_conf.get(CONF_HOST),
"version": bridge_conf.get(CONF_VERSION),
"port": bridge_conf.get(CONF_PORT),
"groups": groups,
}
)
return {"bridges": new_bridges}
2015-05-18 23:27:09 +00:00
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the LimitlessLED lights."""
2015-11-28 17:21:00 +00:00
from limitlessled.bridge import Bridge
2015-11-14 17:56:18 +00:00
# Two legacy configuration formats are supported to maintain backwards
# compatibility.
2015-11-28 17:21:00 +00:00
config = rewrite_legacy(config)
2015-05-19 01:09:34 +00:00
2015-11-14 17:56:18 +00:00
# Use the expanded configuration format.
lights = []
for bridge_conf in config.get(CONF_BRIDGES):
2019-07-31 19:25:30 +00:00
bridge = Bridge(
bridge_conf.get(CONF_HOST),
port=bridge_conf.get(CONF_PORT, DEFAULT_PORT),
version=bridge_conf.get(CONF_VERSION, DEFAULT_VERSION),
)
for group_conf in bridge_conf.get(CONF_GROUPS):
group = bridge.add_group(
group_conf.get(CONF_NUMBER),
group_conf.get(CONF_NAME),
2019-07-31 19:25:30 +00:00
group_conf.get(CONF_TYPE, DEFAULT_LED_TYPE),
)
lights.append(LimitlessLEDGroup(group, {"fade": group_conf[CONF_FADE]}))
add_entities(lights)
2015-05-18 23:27:09 +00:00
2015-11-14 17:56:18 +00:00
def state(new_state):
2016-03-07 21:08:21 +00:00
"""State decorator.
2015-11-14 17:56:18 +00:00
Specify True (turn on) or False (turn off).
"""
2019-07-31 19:25:30 +00:00
2015-11-14 17:56:18 +00:00
def decorator(function):
"""Set up the decorator function."""
# pylint: disable=protected-access
2015-11-14 17:56:18 +00:00
def wrapper(self, **kwargs):
2016-03-07 21:08:21 +00:00
"""Wrap a group state change."""
2015-11-28 17:21:00 +00:00
from limitlessled.pipeline import Pipeline
2019-07-31 19:25:30 +00:00
2015-11-14 17:56:18 +00:00
pipeline = Pipeline()
transition_time = DEFAULT_TRANSITION
if self._effect == EFFECT_COLORLOOP:
2015-11-14 17:56:18 +00:00
self.group.stop()
self._effect = None
2015-11-14 17:56:18 +00:00
# Set transition time.
if ATTR_TRANSITION in kwargs:
transition_time = int(kwargs[ATTR_TRANSITION])
2015-11-14 17:56:18 +00:00
# Do group type-specific work.
function(self, transition_time, pipeline, **kwargs)
# Update state.
2015-11-28 17:21:00 +00:00
self._is_on = new_state
2015-11-14 17:56:18 +00:00
self.group.enqueue(pipeline)
self.schedule_update_ha_state()
2019-07-31 19:25:30 +00:00
2015-11-14 17:56:18 +00:00
return wrapper
2019-07-31 19:25:30 +00:00
2015-11-14 17:56:18 +00:00
return decorator
class LimitlessLEDGroup(Light, RestoreEntity):
2016-03-07 21:08:21 +00:00
"""Representation of a LimitessLED group."""
def __init__(self, group, config):
2016-03-07 21:08:21 +00:00
"""Initialize a group."""
2015-11-28 17:21:00 +00:00
from limitlessled.group.rgbw import RgbwGroup
from limitlessled.group.white import WhiteGroup
from limitlessled.group.dimmer import DimmerGroup
from limitlessled.group.rgbww import RgbwwGroup
2019-07-31 19:25:30 +00:00
2015-11-14 17:56:18 +00:00
if isinstance(group, WhiteGroup):
self._supported = SUPPORT_LIMITLESSLED_WHITE
2018-02-21 07:08:45 +00:00
self._effect_list = [EFFECT_NIGHT]
elif isinstance(group, DimmerGroup):
self._supported = SUPPORT_LIMITLESSLED_DIMMER
2018-02-21 07:08:45 +00:00
self._effect_list = []
2015-11-14 17:56:18 +00:00
elif isinstance(group, RgbwGroup):
self._supported = SUPPORT_LIMITLESSLED_RGB
2018-02-21 07:08:45 +00:00
self._effect_list = [EFFECT_COLORLOOP, EFFECT_NIGHT, EFFECT_WHITE]
elif isinstance(group, RgbwwGroup):
self._supported = SUPPORT_LIMITLESSLED_RGBWW
2018-02-21 07:08:45 +00:00
self._effect_list = [EFFECT_COLORLOOP, EFFECT_NIGHT, EFFECT_WHITE]
self.group = group
self.config = config
self._is_on = False
self._brightness = None
self._temperature = None
self._color = None
self._effect = None
async def async_added_to_hass(self):
"""Handle entity about to be added to hass event."""
await super().async_added_to_hass()
last_state = await self.async_get_last_state()
if last_state:
2019-07-31 19:25:30 +00:00
self._is_on = last_state.state == STATE_ON
self._brightness = last_state.attributes.get("brightness")
self._temperature = last_state.attributes.get("color_temp")
self._color = last_state.attributes.get("hs_color")
@property
def should_poll(self):
2016-03-07 21:08:21 +00:00
"""No polling needed."""
return False
@property
def assumed_state(self):
"""Return True because unable to access real state of the entity."""
return True
@property
def name(self):
2016-03-07 21:08:21 +00:00
"""Return the name of the group."""
2015-11-14 17:56:18 +00:00
return self.group.name
@property
def is_on(self):
2016-03-07 21:08:21 +00:00
"""Return true if device is on."""
2015-11-28 17:21:00 +00:00
return self._is_on
2015-05-18 23:27:09 +00:00
@property
2015-06-13 23:42:09 +00:00
def brightness(self):
2016-03-07 21:08:21 +00:00
"""Return the brightness property."""
if self._effect == EFFECT_NIGHT:
return 1
2015-06-13 23:42:09 +00:00
return self._brightness
2015-05-18 23:27:09 +00:00
@property
def min_mireds(self):
"""Return the coldest color_temp that this light supports."""
return 154
@property
def max_mireds(self):
"""Return the warmest color_temp that this light supports."""
return 370
@property
2015-11-14 17:56:18 +00:00
def color_temp(self):
2016-03-07 21:08:21 +00:00
"""Return the temperature property."""
if self.hs_color is not None:
return None
2015-11-14 17:56:18 +00:00
return self._temperature
@property
def hs_color(self):
2016-03-07 21:08:21 +00:00
"""Return the color property."""
if self._effect == EFFECT_NIGHT:
return None
if self._color is None or self._color[1] == 0:
return None
2015-11-14 17:56:18 +00:00
return self._color
2015-05-18 23:27:09 +00:00
@property
def supported_features(self):
"""Flag supported features."""
return self._supported
@property
def effect(self):
"""Return the current effect for this light."""
return self._effect
2018-02-21 07:08:45 +00:00
@property
def effect_list(self):
"""Return the list of supported effects for this light."""
return self._effect_list
# pylint: disable=arguments-differ
@state(False)
def turn_off(self, transition_time, pipeline, **kwargs):
"""Turn off a group."""
if self.config[CONF_FADE]:
pipeline.transition(transition_time, brightness=0.0)
pipeline.off()
# pylint: disable=arguments-differ
2015-11-14 17:56:18 +00:00
@state(True)
def turn_on(self, transition_time, pipeline, **kwargs):
2016-03-07 21:08:21 +00:00
"""Turn on (or adjust property of) a group."""
2018-02-21 07:08:45 +00:00
# The night effect does not need a turned on light
if kwargs.get(ATTR_EFFECT) == EFFECT_NIGHT:
if EFFECT_NIGHT in self._effect_list:
pipeline.night_light()
self._effect = EFFECT_NIGHT
2018-02-21 07:08:45 +00:00
return
pipeline.on()
# Set up transition.
args = {}
if self.config[CONF_FADE] and not self.is_on and self._brightness:
2019-07-31 19:25:30 +00:00
args["brightness"] = self.limitlessled_brightness()
2015-05-18 23:27:09 +00:00
if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS]
2019-07-31 19:25:30 +00:00
args["brightness"] = self.limitlessled_brightness()
if ATTR_HS_COLOR in kwargs and self._supported & SUPPORT_COLOR:
self._color = kwargs[ATTR_HS_COLOR]
# White is a special case.
if self._color[1] < MIN_SATURATION:
pipeline.white()
self._color = WHITE
else:
2019-07-31 19:25:30 +00:00
args["color"] = self.limitlessled_color()
if ATTR_COLOR_TEMP in kwargs:
if self._supported & SUPPORT_COLOR:
pipeline.white()
self._color = WHITE
if self._supported & SUPPORT_COLOR_TEMP:
self._temperature = kwargs[ATTR_COLOR_TEMP]
2019-07-31 19:25:30 +00:00
args["temperature"] = self.limitlessled_temperature()
if args:
pipeline.transition(transition_time, **args)
# Flash.
if ATTR_FLASH in kwargs and self._supported & SUPPORT_FLASH:
duration = 0
if kwargs[ATTR_FLASH] == FLASH_LONG:
duration = 1
pipeline.flash(duration=duration)
# Add effects.
2018-02-21 07:08:45 +00:00
if ATTR_EFFECT in kwargs and self._effect_list:
if kwargs[ATTR_EFFECT] == EFFECT_COLORLOOP:
from limitlessled.presets import COLORLOOP
2019-07-31 19:25:30 +00:00
self._effect = EFFECT_COLORLOOP
pipeline.append(COLORLOOP)
2015-11-14 17:56:18 +00:00
if kwargs[ATTR_EFFECT] == EFFECT_WHITE:
pipeline.white()
self._color = WHITE
def limitlessled_temperature(self):
"""Convert Home Assistant color temperature units to percentage."""
max_kelvin = color_temperature_mired_to_kelvin(self.min_mireds)
min_kelvin = color_temperature_mired_to_kelvin(self.max_mireds)
width = max_kelvin - min_kelvin
kelvin = color_temperature_mired_to_kelvin(self._temperature)
temperature = (kelvin - min_kelvin) / width
return max(0, min(1, temperature))
2015-11-14 17:56:18 +00:00
def limitlessled_brightness(self):
"""Convert Home Assistant brightness units to percentage."""
return self._brightness / 255
2015-11-14 17:56:18 +00:00
def limitlessled_color(self):
"""Convert Home Assistant HS list to RGB Color tuple."""
from limitlessled import Color
2019-07-31 19:25:30 +00:00
2018-03-25 07:47:10 +00:00
return Color(*color_hs_to_RGB(*tuple(self._color)))