core/homeassistant/components/limitlessled/light.py

394 lines
12 KiB
Python
Raw Normal View History

"""Support for LimitlessLED bulbs."""
2015-05-18 23:27:09 +00:00
import logging
from limitlessled import Color
from limitlessled.bridge import Bridge
from limitlessled.group.dimmer import DimmerGroup
from limitlessled.group.rgbw import RgbwGroup
from limitlessled.group.rgbww import RgbwwGroup
from limitlessled.group.white import WhiteGroup
from limitlessled.pipeline import Pipeline
from limitlessled.presets import COLORLOOP
2016-09-30 02:06:28 +00:00
import voluptuous as vol
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,
PLATFORM_SCHEMA,
2019-07-31 19:25:30 +00:00
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
2019-07-31 19:25:30 +00:00
SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT,
SUPPORT_FLASH,
SUPPORT_TRANSITION,
Light,
)
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE, STATE_ON
2016-09-30 02:06:28 +00:00
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.util.color import color_hs_to_RGB, color_temperature_mired_to_kelvin
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-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."""
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."""
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:
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."""
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)))