From d91fe792c56d872e9054fd608978fd51a99142dd Mon Sep 17 00:00:00 2001 From: happyleaves Date: Sat, 14 Nov 2015 12:56:18 -0500 Subject: [PATCH 1/2] limitlessled improvements --- .../components/light/limitlessled.py | 348 +++++++++++------- requirements_all.txt | 2 +- 2 files changed, 226 insertions(+), 124 deletions(-) diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index ad5f8487a2a..8700dd85bf2 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -8,171 +8,273 @@ https://home-assistant.io/components/light.limitlessled/ """ import logging -from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_EFFECT, + ATTR_COLOR_TEMP, ATTR_TRANSITION, + ATTR_FLASH, FLASH_LONG, EFFECT_COLORLOOP, EFFECT_WHITE) +from limitlessled import Color +from limitlessled.bridge import Bridge +from limitlessled.group.rgbw import RgbwGroup +from limitlessled.group.white import WhiteGroup +from limitlessled.pipeline import Pipeline +from limitlessled.presets import COLORLOOP + + _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['ledcontroller==1.1.0'] - -COLOR_TABLE = { - 'white': [0xFF, 0xFF, 0xFF], - 'violet': [0xEE, 0x82, 0xEE], - 'royal_blue': [0x41, 0x69, 0xE1], - 'baby_blue': [0x87, 0xCE, 0xFA], - 'aqua': [0x00, 0xFF, 0xFF], - 'royal_mint': [0x7F, 0xFF, 0xD4], - 'seafoam_green': [0x2E, 0x8B, 0x57], - 'green': [0x00, 0x80, 0x00], - 'lime_green': [0x32, 0xCD, 0x32], - 'yellow': [0xFF, 0xFF, 0x00], - 'yellow_orange': [0xDA, 0xA5, 0x20], - 'orange': [0xFF, 0xA5, 0x00], - 'red': [0xFF, 0x00, 0x00], - 'pink': [0xFF, 0xC0, 0xCB], - 'fusia': [0xFF, 0x00, 0xFF], - 'lilac': [0xDA, 0x70, 0xD6], - 'lavendar': [0xE6, 0xE6, 0xFA], -} +REQUIREMENTS = ['limitlessled==1.0.0'] +RGB_BOUNDARY = 40 +DEFAULT_TRANSITION = 0 +DEFAULT_PORT = 8899 +DEFAULT_VERSION = 5 +DEFAULT_LED_TYPE = 'rgbw' +WHITE = [255, 255, 255] -def _distance_squared(rgb1, rgb2): - """ Return sum of squared distances of each color part. """ - return sum((val1-val2)**2 for val1, val2 in zip(rgb1, rgb2)) - - -def _rgb_to_led_color(rgb_color): - """ Convert an RGB color to the closest color string and color. """ - return sorted((_distance_squared(rgb_color, color), name) - for name, color in COLOR_TABLE.items())[0][1] +def legacy_setup(config, add_devices_callback): + """ Perform setup using legacy format. """ + bridges = config.get('bridges', [config]) + lights = [] + for bridge_conf in bridges: + bridge = Bridge(bridge_conf.get('host')) + for i in range(1, 5): + name_key = 'group_%d_name' % i + if name_key in bridge_conf: + group_type = bridge_conf.get('group_%d_type' % i, + DEFAULT_LED_TYPE) + group = bridge.add_group(i, + bridge_conf.get(name_key), + group_type) + lights.append(LimitlessLEDGroup.factory(group)) + add_devices_callback(lights) def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Gets the LimitlessLED lights. """ - import ledcontroller - # Handle old configuration format: - bridges = config.get('bridges', [config]) - - for bridge_id, bridge in enumerate(bridges): - bridge['id'] = bridge_id - - pool = ledcontroller.LedControllerPool([x['host'] for x in bridges]) + # Two legacy configuration formats are supported to + # maintain backwards compatibility. + legacy_setup(config, add_devices_callback) + # Use the expanded configuration format. + if 'bridges' not in config: + return lights = [] - for bridge in bridges: - for i in range(1, 5): - name_key = 'group_%d_name' % i - if name_key in bridge: - group_type = bridge.get('group_%d_type' % i, 'rgbw') - lights.append(LimitlessLED.factory(pool, bridge['id'], i, - bridge[name_key], - group_type)) - + for bridge_conf in config.get('bridges'): + if 'groups' not in bridge_conf: + continue + bridge = Bridge(bridge_conf.get('host'), + port=bridge_conf.get('port', DEFAULT_PORT), + version=bridge_conf.get('version', DEFAULT_VERSION)) + for group_conf in bridge_conf.get('groups'): + group = bridge.add_group(group_conf.get('number'), + group_conf.get('name'), + group_conf.get('type', DEFAULT_LED_TYPE)) + lights.append(LimitlessLEDGroup.factory(group)) add_devices_callback(lights) -class LimitlessLED(Light): - """ Represents a LimitlessLED light """ +def state(new_state): + """ State decorator. + + Specify True (turn on) or False (turn off). + """ + def decorator(function): + """ Decorator function. """ + # pylint: disable=no-member + def wrapper(self, **kwargs): + """ Wrap a group state change. """ + pipeline = Pipeline() + transition_time = DEFAULT_TRANSITION + # Stop any repeating pipeline. + if self.repeating: + self.repeating = False + self.group.stop() + # Not on? Turn on. + if not self.is_on: + pipeline.on() + # Set transition time. + if ATTR_TRANSITION in kwargs: + transition_time = kwargs[ATTR_TRANSITION] + # Do group type-specific work. + function(self, transition_time, pipeline, **kwargs) + # Update state. + self.on_state = new_state + self.group.enqueue(pipeline) + self.update_ha_state() + return wrapper + return decorator + + +class LimitlessLEDGroup(Light): + """ LimitessLED group. """ + def __init__(self, group): + """ Initialize a group. """ + self.group = group + self.repeating = False + self.on_state = False + self._brightness = None @staticmethod - def factory(pool, controller_id, group, name, group_type): - ''' Construct a Limitless LED of the appropriate type ''' - if group_type == 'white': - return WhiteLimitlessLED(pool, controller_id, group, name) - elif group_type == 'rgbw': - return RGBWLimitlessLED(pool, controller_id, group, name) - - # pylint: disable=too-many-arguments - def __init__(self, pool, controller_id, group, name, group_type): - self.pool = pool - self.controller_id = controller_id - self.group = group - - self.pool.execute(self.controller_id, "set_group_type", self.group, - group_type) - - # LimitlessLEDs don't report state, we have track it ourselves. - self.pool.execute(self.controller_id, "off", self.group) - - self._name = name or DEVICE_DEFAULT_NAME - self._state = False + def factory(group): + """ Produce LimitlessLEDGroup objects. """ + if isinstance(group, WhiteGroup): + return LimitlessLEDWhiteGroup(group) + elif isinstance(group, RgbwGroup): + return LimitlessLEDRGBWGroup(group) @property def should_poll(self): - """ No polling needed. """ + """ No polling needed. + + LimitlessLED state cannot be fetched. + """ return False @property def name(self): - """ Returns the name of the device if any. """ - return self._name + """ Returns the name of the group. """ + return self.group.name @property def is_on(self): """ True if device is on. """ - return self._state - - def turn_off(self, **kwargs): - """ Turn the device off. """ - self._state = False - self.pool.execute(self.controller_id, "off", self.group) - self.update_ha_state() - - -class RGBWLimitlessLED(LimitlessLED): - """ Represents a RGBW LimitlessLED light """ - - def __init__(self, pool, controller_id, group, name): - super().__init__(pool, controller_id, group, name, 'rgbw') - - self._brightness = 100 - self._led_color = 'white' + return self.on_state @property def brightness(self): + """ Brightness property. """ return self._brightness + @state(False) + def turn_off(self, transition_time, pipeline, **kwargs): + """ Turn off a group. """ + pipeline.transition(transition_time, brightness=0.0).off() + + +class LimitlessLEDWhiteGroup(LimitlessLEDGroup): + """ LimitlessLED White group. """ + def __init__(self, group): + """ Initialize White group. """ + super().__init__(group) + # Initialize group with known values. + self.group.on = True + self.group.temperature = 1.0 + self.group.brightness = 0.0 + self._brightness = _to_hass_brightness(1.0) + self._temperature = _to_hass_temperature(self.group.temperature) + self.group.on = False + + @property + def color_temp(self): + """ Temperature property. """ + return self._temperature + + @state(True) + def turn_on(self, transition_time, pipeline, **kwargs): + """ Turn on (or adjust property of) a group. """ + # Check arguments. + if ATTR_BRIGHTNESS in kwargs: + self._brightness = kwargs[ATTR_BRIGHTNESS] + if ATTR_COLOR_TEMP in kwargs: + self._temperature = kwargs[ATTR_COLOR_TEMP] + # Set up transition. + pipeline.transition(transition_time, + brightness=_from_hass_brightness( + self._brightness), + temperature=_from_hass_temperature( + self._temperature)) + + +class LimitlessLEDRGBWGroup(LimitlessLEDGroup): + """ LimitlessLED RGBW group. """ + def __init__(self, group): + """ Initialize RGBW group. """ + super().__init__(group) + # Initialize group with known values. + self.group.on = True + self.group.white() + self._color = WHITE + self.group.brightness = 0.0 + self._brightness = _to_hass_brightness(1.0) + self.group.on = False + @property def rgb_color(self): - return COLOR_TABLE[self._led_color] - - def turn_on(self, **kwargs): - """ Turn the device on. """ - self._state = True + """ Color property. """ + return self._color + @state(True) + def turn_on(self, transition_time, pipeline, **kwargs): + """ Turn on (or adjust property of) a group. """ + # Check arguments. if ATTR_BRIGHTNESS in kwargs: self._brightness = kwargs[ATTR_BRIGHTNESS] - if ATTR_RGB_COLOR in kwargs: - self._led_color = _rgb_to_led_color(kwargs[ATTR_RGB_COLOR]) - - effect = kwargs.get(ATTR_EFFECT) - - if effect == EFFECT_COLORLOOP: - self.pool.execute(self.controller_id, "disco", self.group) - elif effect == EFFECT_WHITE: - self.pool.execute(self.controller_id, "white", self.group) - else: - self.pool.execute(self.controller_id, "set_color", - self._led_color, self.group) - - # Brightness can be set independently of color - self.pool.execute(self.controller_id, "set_brightness", - self._brightness / 255.0, self.group) - - self.update_ha_state() + self._color = kwargs[ATTR_RGB_COLOR] + # White is a special case. + if min(self._color) > 256 - RGB_BOUNDARY: + pipeline.white() + self._color = WHITE + # Set up transition. + pipeline.transition(transition_time, + brightness=_from_hass_brightness( + self._brightness), + color=_from_hass_color(self._color)) + # Flash. + if ATTR_FLASH in kwargs: + duration = 0 + if kwargs[ATTR_FLASH] == FLASH_LONG: + duration = 1 + pipeline.flash(duration=duration) + # Add effects. + if ATTR_EFFECT in kwargs: + if kwargs[ATTR_EFFECT] == EFFECT_COLORLOOP: + self.repeating = True + pipeline.append(COLORLOOP) + if kwargs[ATTR_EFFECT] == EFFECT_WHITE: + pipeline.white() + self._color = WHITE -class WhiteLimitlessLED(LimitlessLED): - """ Represents a White LimitlessLED light """ +def _from_hass_temperature(temperature): + """ Convert Home Assistant color temperature + units to percentage. + """ + return (temperature - 154) / 346 - def __init__(self, pool, controller_id, group, name): - super().__init__(pool, controller_id, group, name, 'white') - def turn_on(self, **kwargs): - """ Turn the device on. """ - self._state = True - self.pool.execute(self.controller_id, "on", self.group) - self.update_ha_state() +def _to_hass_temperature(temperature): + """ Convert percentage to Home Assistant + color temperature units. + """ + return int(temperature * 346) + 154 + + +def _from_hass_brightness(brightness): + """ Convert Home Assistant brightness units + to percentage. + """ + return brightness / 255 + + +def _to_hass_brightness(brightness): + """ Convert percentage to Home Assistant + brightness units. + """ + return int(brightness * 255) + + +def _from_hass_color(color): + """ Convert Home Assistant RGB list + to Color tuple. + """ + return Color(*tuple(color)) + + +def _to_hass_color(color): + """ Convert from Color tuple to + Home Assistant RGB list. + """ + return list([int(c) for c in color]) diff --git a/requirements_all.txt b/requirements_all.txt index a9f24cdb65f..e563895d94b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -36,7 +36,7 @@ blinkstick==1.1.7 phue==0.8 # homeassistant.components.light.limitlessled -ledcontroller==1.1.0 +limitlessled==1.0.0 # homeassistant.components.light.tellstick # homeassistant.components.sensor.tellstick From e6fdcc94e622f377574c53bc16954d1bfe5813a0 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Sat, 28 Nov 2015 12:21:00 -0500 Subject: [PATCH 2/2] refactor legacy; move imports --- .../components/light/limitlessled.py | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index 8700dd85bf2..4952a3144ba 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -14,13 +14,6 @@ from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, ATTR_FLASH, FLASH_LONG, EFFECT_COLORLOOP, EFFECT_WHITE) -from limitlessled import Color -from limitlessled.bridge import Bridge -from limitlessled.group.rgbw import RgbwGroup -from limitlessled.group.white import WhiteGroup -from limitlessled.pipeline import Pipeline -from limitlessled.presets import COLORLOOP - _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['limitlessled==1.0.0'] @@ -32,38 +25,43 @@ DEFAULT_LED_TYPE = 'rgbw' WHITE = [255, 255, 255] -def legacy_setup(config, add_devices_callback): - """ Perform setup using legacy format. """ +def rewrite_legacy(config): + """ Rewrite legacy configuration to new format. """ bridges = config.get('bridges', [config]) - lights = [] + new_bridges = [] for bridge_conf in bridges: - bridge = Bridge(bridge_conf.get('host')) - for i in range(1, 5): - name_key = 'group_%d_name' % i - if name_key in bridge_conf: - group_type = bridge_conf.get('group_%d_type' % i, - DEFAULT_LED_TYPE) - group = bridge.add_group(i, - bridge_conf.get(name_key), - group_type) - lights.append(LimitlessLEDGroup.factory(group)) - add_devices_callback(lights) + groups = [] + if 'groups' in bridge_conf: + groups = bridge_conf['groups'] + else: + _LOGGER.warn("Legacy configuration format detected") + for i in range(1, 5): + name_key = 'group_%d_name' % i + if name_key in bridge_conf: + 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('host'), + 'groups': groups + }) + return {'bridges': new_bridges} def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Gets the LimitlessLED lights. """ + from limitlessled.bridge import Bridge # Two legacy configuration formats are supported to # maintain backwards compatibility. - legacy_setup(config, add_devices_callback) + config = rewrite_legacy(config) # Use the expanded configuration format. - if 'bridges' not in config: - return lights = [] for bridge_conf in config.get('bridges'): - if 'groups' not in bridge_conf: - continue bridge = Bridge(bridge_conf.get('host'), port=bridge_conf.get('port', DEFAULT_PORT), version=bridge_conf.get('version', DEFAULT_VERSION)) @@ -82,9 +80,10 @@ def state(new_state): """ def decorator(function): """ Decorator function. """ - # pylint: disable=no-member + # pylint: disable=no-member,protected-access def wrapper(self, **kwargs): """ Wrap a group state change. """ + from limitlessled.pipeline import Pipeline pipeline = Pipeline() transition_time = DEFAULT_TRANSITION # Stop any repeating pipeline. @@ -100,7 +99,7 @@ def state(new_state): # Do group type-specific work. function(self, transition_time, pipeline, **kwargs) # Update state. - self.on_state = new_state + self._is_on = new_state self.group.enqueue(pipeline) self.update_ha_state() return wrapper @@ -113,12 +112,14 @@ class LimitlessLEDGroup(Light): """ Initialize a group. """ self.group = group self.repeating = False - self.on_state = False + self._is_on = False self._brightness = None @staticmethod def factory(group): """ Produce LimitlessLEDGroup objects. """ + from limitlessled.group.rgbw import RgbwGroup + from limitlessled.group.white import WhiteGroup if isinstance(group, WhiteGroup): return LimitlessLEDWhiteGroup(group) elif isinstance(group, RgbwGroup): @@ -140,7 +141,7 @@ class LimitlessLEDGroup(Light): @property def is_on(self): """ True if device is on. """ - return self.on_state + return self._is_on @property def brightness(self): @@ -208,6 +209,7 @@ class LimitlessLEDRGBWGroup(LimitlessLEDGroup): @state(True) def turn_on(self, transition_time, pipeline, **kwargs): """ Turn on (or adjust property of) a group. """ + from limitlessled.presets import COLORLOOP # Check arguments. if ATTR_BRIGHTNESS in kwargs: self._brightness = kwargs[ATTR_BRIGHTNESS] @@ -270,6 +272,7 @@ def _from_hass_color(color): """ Convert Home Assistant RGB list to Color tuple. """ + from limitlessled import Color return Color(*tuple(color))