Merge pull request #590 from balloob/light-rgb

Light: base color now in RGB instead of XY
pull/600/head
Paulus Schoutsen 2015-11-08 20:06:49 -08:00
commit 1be2be0886
8 changed files with 115 additions and 98 deletions

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "3395b15f48bdcbfe59f7a5f58665b252" VERSION = "889bede717abab2548d50cc40a395b06"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit b04e28506110869003b7da11c91f78b4a8cca75a Subproject commit 6ddc18ba5dc4723b72bf1800665b41ebacc7e30e

View File

@ -109,8 +109,9 @@ DISCOVERY_PLATFORMS = {
PROP_TO_ATTR = { PROP_TO_ATTR = {
'brightness': ATTR_BRIGHTNESS, 'brightness': ATTR_BRIGHTNESS,
'color_xy': ATTR_XY_COLOR,
'color_temp': ATTR_COLOR_TEMP, 'color_temp': ATTR_COLOR_TEMP,
'rgb_color': ATTR_RGB_COLOR,
'xy_color': ATTR_XY_COLOR,
} }
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -263,28 +264,17 @@ def setup(hass, config):
if len(rgb_color) == 3: if len(rgb_color) == 3:
params[ATTR_RGB_COLOR] = [int(val) for val in rgb_color] params[ATTR_RGB_COLOR] = [int(val) for val in rgb_color]
params[ATTR_XY_COLOR] = \
color_util.color_RGB_to_xy(int(rgb_color[0]),
int(rgb_color[1]),
int(rgb_color[2]))
except (TypeError, ValueError): except (TypeError, ValueError):
# TypeError if rgb_color is not iterable # TypeError if rgb_color is not iterable
# ValueError if not all values can be converted to int # ValueError if not all values can be converted to int
pass pass
if ATTR_FLASH in dat: if dat.get(ATTR_FLASH) in (FLASH_SHORT, FLASH_LONG):
if dat[ATTR_FLASH] == FLASH_SHORT: params[ATTR_FLASH] = dat[ATTR_FLASH]
params[ATTR_FLASH] = FLASH_SHORT
elif dat[ATTR_FLASH] == FLASH_LONG: if dat.get(ATTR_EFFECT) in (EFFECT_COLORLOOP, EFFECT_WHITE):
params[ATTR_FLASH] = FLASH_LONG params[ATTR_EFFECT] = dat[ATTR_EFFECT]
if ATTR_EFFECT in dat:
if dat[ATTR_EFFECT] == EFFECT_COLORLOOP:
params[ATTR_EFFECT] = EFFECT_COLORLOOP
if dat[ATTR_EFFECT] == EFFECT_WHITE:
params[ATTR_EFFECT] = EFFECT_WHITE
for light in target_lights: for light in target_lights:
light.turn_on(**params) light.turn_on(**params)
@ -315,10 +305,15 @@ class Light(ToggleEntity):
return None return None
@property @property
def color_xy(self): def xy_color(self):
""" XY color value [float, float]. """ """ XY color value [float, float]. """
return None return None
@property
def rgb_color(self):
""" RGB color value [int, int, int] """
return None
@property @property
def color_temp(self): def color_temp(self):
""" CT color value in mirads. """ """ CT color value in mirads. """
@ -340,6 +335,12 @@ class Light(ToggleEntity):
if value: if value:
data[attr] = value data[attr] = value
if ATTR_RGB_COLOR not in data and ATTR_XY_COLOR in data and \
ATTR_BRIGHTNESS in data:
data[ATTR_RGB_COLOR] = color_util.color_xy_brightness_to_RGB(
data[ATTR_XY_COLOR][0], data[ATTR_XY_COLOR][1],
data[ATTR_BRIGHTNESS])
device_attr = self.device_state_attributes device_attr = self.device_state_attributes
if device_attr is not None: if device_attr is not None:

View File

@ -8,33 +8,33 @@ Demo platform that implements lights.
import random import random
from homeassistant.components.light import ( from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP) Light, ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP)
LIGHT_COLORS = [ LIGHT_COLORS = [
[0.368, 0.180], [237, 224, 33],
[0.460, 0.470], [255, 63, 111],
] ]
LIGHT_TEMPS = [160, 500] LIGHT_TEMPS = [240, 380]
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return demo lights. """ """ Find and return demo lights. """
add_devices_callback([ add_devices_callback([
DemoLight("Bed Light", False), DemoLight("Bed Light", False),
DemoLight("Ceiling Lights", True, LIGHT_TEMPS[1], LIGHT_COLORS[0]), DemoLight("Ceiling Lights", True, LIGHT_COLORS[0], LIGHT_TEMPS[1]),
DemoLight("Kitchen Lights", True, LIGHT_TEMPS[0], LIGHT_COLORS[1]) DemoLight("Kitchen Lights", True, LIGHT_COLORS[1], LIGHT_TEMPS[0])
]) ])
class DemoLight(Light): class DemoLight(Light):
""" Provides a demo switch. """ """ Provides a demo switch. """
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, name, state, xy=None, ct=None, brightness=180): def __init__(self, name, state, rgb=None, ct=None, brightness=180):
self._name = name self._name = name
self._state = state self._state = state
self._xy = xy or random.choice(LIGHT_COLORS) self._rgb = rgb or random.choice(LIGHT_COLORS)
self._ct = ct or random.choice(LIGHT_TEMPS) self._ct = ct or random.choice(LIGHT_TEMPS)
self._brightness = brightness self._brightness = brightness
@ -54,9 +54,9 @@ class DemoLight(Light):
return self._brightness return self._brightness
@property @property
def color_xy(self): def rgb_color(self):
""" XY color value. """ """ rgb color value. """
return self._xy return self._rgb
@property @property
def color_temp(self): def color_temp(self):
@ -72,8 +72,8 @@ class DemoLight(Light):
""" Turn the device on. """ """ Turn the device on. """
self._state = True self._state = True
if ATTR_XY_COLOR in kwargs: if ATTR_RGB_COLOR in kwargs:
self._xy = kwargs[ATTR_XY_COLOR] self._rgb = kwargs[ATTR_RGB_COLOR]
if ATTR_COLOR_TEMP in kwargs: if ATTR_COLOR_TEMP in kwargs:
self._ct = kwargs[ATTR_COLOR_TEMP] self._ct = kwargs[ATTR_COLOR_TEMP]

View File

@ -5,18 +5,21 @@ Support for Hue lights.
https://home-assistant.io/components/light.hue.html https://home-assistant.io/components/light.hue.html
""" """
import json
import logging import logging
import os
import socket import socket
from datetime import timedelta from datetime import timedelta
from urllib.parse import urlparse from urllib.parse import urlparse
from homeassistant.loader import get_component from homeassistant.loader import get_component
import homeassistant.util as util import homeassistant.util as util
import homeassistant.util.color as color_util
from homeassistant.const import CONF_HOST, DEVICE_DEFAULT_NAME from homeassistant.const import CONF_HOST, DEVICE_DEFAULT_NAME
from homeassistant.components.light import ( from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP, Light, ATTR_BRIGHTNESS, ATTR_XY_COLOR, ATTR_COLOR_TEMP,
ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT, ATTR_TRANSITION, ATTR_FLASH, FLASH_LONG, FLASH_SHORT,
ATTR_EFFECT, EFFECT_COLORLOOP) ATTR_EFFECT, EFFECT_COLORLOOP, ATTR_RGB_COLOR)
REQUIREMENTS = ['phue==0.8'] REQUIREMENTS = ['phue==0.8']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
@ -30,21 +33,37 @@ _CONFIGURING = {}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def _find_host_from_config(hass):
""" Attempt to detect host based on existing configuration. """
path = hass.config.path(PHUE_CONFIG_FILE)
if not os.path.isfile(path):
return None
try:
with open(path) as inp:
return next(json.loads(''.join(inp)).keys().__iter__())
except (ValueError, AttributeError, StopIteration):
# ValueError if can't parse as JSON
# AttributeError if JSON value is not a dict
# StopIteration if no keys
return None
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Gets the Hue lights. """ """ Gets the Hue lights. """
try:
# pylint: disable=unused-variable
import phue # noqa
except ImportError:
_LOGGER.exception("Error while importing dependency phue.")
return
if discovery_info is not None: if discovery_info is not None:
host = urlparse(discovery_info[1]).hostname host = urlparse(discovery_info[1]).hostname
else: else:
host = config.get(CONF_HOST, None) host = config.get(CONF_HOST, None)
if host is None:
host = _find_host_from_config(hass)
if host is None:
_LOGGER.error('No host found in configuration')
return False
# Only act if we are not already configuring this host # Only act if we are not already configuring this host
if host in _CONFIGURING: if host in _CONFIGURING:
return return
@ -165,7 +184,7 @@ class HueLight(Light):
return self.info['state']['bri'] return self.info['state']['bri']
@property @property
def color_xy(self): def xy_color(self):
""" XY color value. """ """ XY color value. """
return self.info['state'].get('xy') return self.info['state'].get('xy')
@ -195,6 +214,9 @@ class HueLight(Light):
if ATTR_XY_COLOR in kwargs: if ATTR_XY_COLOR in kwargs:
command['xy'] = kwargs[ATTR_XY_COLOR] command['xy'] = kwargs[ATTR_XY_COLOR]
elif ATTR_RGB_COLOR in kwargs:
command['xy'] = color_util.color_RGB_to_xy(
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
if ATTR_COLOR_TEMP in kwargs: if ATTR_COLOR_TEMP in kwargs:
command['ct'] = kwargs[ATTR_COLOR_TEMP] command['ct'] = kwargs[ATTR_COLOR_TEMP]

View File

@ -18,13 +18,43 @@ import logging
from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.const import DEVICE_DEFAULT_NAME
from homeassistant.components.light import (Light, ATTR_BRIGHTNESS, from homeassistant.components.light import (Light, ATTR_BRIGHTNESS,
ATTR_XY_COLOR, ATTR_EFFECT, ATTR_RGB_COLOR, ATTR_EFFECT,
EFFECT_COLORLOOP, EFFECT_WHITE) EFFECT_COLORLOOP, EFFECT_WHITE)
from homeassistant.util.color import color_RGB_to_xy
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['ledcontroller==1.1.0'] 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),
}
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 LedController color string. """
return sorted((_distance_squared(rgb_color, color), name)
for name, color in COLOR_TABLE.items())[0][1]
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Gets the LimitlessLED lights. """ """ Gets the LimitlessLED lights. """
@ -106,49 +136,15 @@ class RGBWLimitlessLED(LimitlessLED):
super().__init__(pool, controller_id, group, name, 'rgbw') super().__init__(pool, controller_id, group, name, 'rgbw')
self._brightness = 100 self._brightness = 100
self._xy_color = color_RGB_to_xy(255, 255, 255) self._rgb_color = COLOR_TABLE['white']
# Build a color table that maps an RGB color to a color string
# recognized by LedController's set_color method
self._color_table = [(color_RGB_to_xy(*x[0]), x[1]) for x in [
((0xFF, 0xFF, 0xFF), 'white'),
((0xEE, 0x82, 0xEE), 'violet'),
((0x41, 0x69, 0xE1), 'royal_blue'),
((0x87, 0xCE, 0xFA), 'baby_blue'),
((0x00, 0xFF, 0xFF), 'aqua'),
((0x7F, 0xFF, 0xD4), 'royal_mint'),
((0x2E, 0x8B, 0x57), 'seafoam_green'),
((0x00, 0x80, 0x00), 'green'),
((0x32, 0xCD, 0x32), 'lime_green'),
((0xFF, 0xFF, 0x00), 'yellow'),
((0xDA, 0xA5, 0x20), 'yellow_orange'),
((0xFF, 0xA5, 0x00), 'orange'),
((0xFF, 0x00, 0x00), 'red'),
((0xFF, 0xC0, 0xCB), 'pink'),
((0xFF, 0x00, 0xFF), 'fusia'),
((0xDA, 0x70, 0xD6), 'lilac'),
((0xE6, 0xE6, 0xFA), 'lavendar'),
]]
@property @property
def brightness(self): def brightness(self):
return self._brightness return self._brightness
@property @property
def color_xy(self): def rgb_color(self):
return self._xy_color return self._rgb_color
def _xy_to_led_color(self, xy_color):
""" Convert an XY color to the closest LedController color string. """
def abs_dist_squared(p_0, p_1):
""" Returns the absolute value of the squared distance """
return abs((p_0[0] - p_1[0])**2 + (p_0[1] - p_1[1])**2)
candidates = [(abs_dist_squared(xy_color, x[0]), x[1]) for x in
self._color_table]
# First candidate in the sorted list is closest to desired color:
return sorted(candidates)[0][1]
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
""" Turn the device on. """ """ Turn the device on. """
@ -157,8 +153,8 @@ class RGBWLimitlessLED(LimitlessLED):
if ATTR_BRIGHTNESS in kwargs: if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS] self._brightness = kwargs[ATTR_BRIGHTNESS]
if ATTR_XY_COLOR in kwargs: if ATTR_RGB_COLOR in kwargs:
self._xy_color = kwargs[ATTR_XY_COLOR] self._rgb_color = _rgb_to_led_color(kwargs[ATTR_RGB_COLOR])
effect = kwargs.get(ATTR_EFFECT) effect = kwargs.get(ATTR_EFFECT)
@ -168,7 +164,7 @@ class RGBWLimitlessLED(LimitlessLED):
self.pool.execute(self.controller_id, "white", self.group) self.pool.execute(self.controller_id, "white", self.group)
else: else:
self.pool.execute(self.controller_id, "set_color", self.pool.execute(self.controller_id, "set_color",
self._xy_to_led_color(self._xy_color), self._rgb_color,
self.group) self.group)
# Brightness can be set independently of color # Brightness can be set independently of color

View File

@ -152,13 +152,7 @@ class TestLight(unittest.TestCase):
data) data)
method, data = dev2.last_call('turn_on') method, data = dev2.last_call('turn_on')
self.assertEquals( self.assertEquals(data[light.ATTR_RGB_COLOR], [255, 255, 255])
data[light.ATTR_XY_COLOR],
color_util.color_RGB_to_xy(255, 255, 255))
self.assertEquals(
data[light.ATTR_RGB_COLOR],
[255, 255, 255])
method, data = dev3.last_call('turn_on') method, data = dev3.last_call('turn_on')
self.assertEqual({light.ATTR_XY_COLOR: [.4, .6]}, data) self.assertEqual({light.ATTR_XY_COLOR: [.4, .6]}, data)