diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 7c9cb72db26..2aed8d54a8c 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -14,16 +14,27 @@ from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \ from homeassistant.components import zwave from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \ - color_temperature_mired_to_kelvin, color_temperature_to_rgb + color_temperature_mired_to_kelvin, color_temperature_to_rgb, \ + color_rgb_to_rgbw, color_rgbw_to_rgb _LOGGER = logging.getLogger(__name__) +AEOTEC = 0x86 +AEOTEC_ZW098_LED_BULB = 0x62 +AEOTEC_ZW098_LED_BULB_LIGHT = (AEOTEC, AEOTEC_ZW098_LED_BULB) + COLOR_CHANNEL_WARM_WHITE = 0x01 COLOR_CHANNEL_COLD_WHITE = 0x02 COLOR_CHANNEL_RED = 0x04 COLOR_CHANNEL_GREEN = 0x08 COLOR_CHANNEL_BLUE = 0x10 +WORKAROUND_ZW098 = 'zw098' + +DEVICE_MAPPINGS = { + AEOTEC_ZW098_LED_BULB_LIGHT: WORKAROUND_ZW098 +} + # Generate midpoint color temperatures for bulbs that have limited # support for white light colors TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN @@ -161,6 +172,7 @@ class ZwaveColorLight(ZwaveDimmer): self._color_channels = None self._rgb = None self._ct = None + self._zw098 = None # Here we attempt to find a zwave color value with the same instance # id as the dimmer value. Currently zwave nodes that change colors @@ -182,6 +194,17 @@ class ZwaveColorLight(ZwaveDimmer): if self._value_color_channels is None: raise ValueError("Color Channels not found.") + # Make sure that we have values for the key before converting to int + if (value.node.manufacturer_id.strip() and + value.node.product_id.strip()): + specific_sensor_key = (int(value.node.manufacturer_id, 16), + int(value.node.product_id, 16)) + + if specific_sensor_key in DEVICE_MAPPINGS: + if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZW098: + _LOGGER.debug("AEOTEC ZW098 workaround enabled") + self._zw098 = 1 + super().__init__(value) def update_properties(self): @@ -218,11 +241,10 @@ class ZwaveColorLight(ZwaveDimmer): else: cold_white = 0 - # Color temperature. With two white channels, only two color - # temperatures are supported for the bulb. The channel values + # Color temperature. With the AEOTEC ZW098 bulb, only two color + # temperatures are supported. The warm and cold channel values # indicate brightness for warm/cold color temperature. - if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and - self._color_channels & COLOR_CHANNEL_COLD_WHITE): + if self._zw098: if warm_white > 0: self._ct = TEMP_WARM_HASS self._rgb = ct_to_rgb(self._ct) @@ -233,17 +255,11 @@ class ZwaveColorLight(ZwaveDimmer): # RGB color is being used. Just report midpoint. self._ct = TEMP_MID_HASS - # If only warm white is reported 0-255 is color temperature. elif self._color_channels & COLOR_CHANNEL_WARM_WHITE: - self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * ( - warm_white / 255) - self._rgb = ct_to_rgb(self._ct) + self._rgb = list(color_rgbw_to_rgb(*self._rgb, w=warm_white)) - # If only cold white is reported 0-255 is negative color temperature. elif self._color_channels & COLOR_CHANNEL_COLD_WHITE: - self._ct = HASS_COLOR_MIN + (HASS_COLOR_MAX - HASS_COLOR_MIN) * ( - (255 - cold_white) / 255) - self._rgb = ct_to_rgb(self._ct) + self._rgb = list(color_rgbw_to_rgb(*self._rgb, w=cold_white)) # If no rgb channels supported, report None. if not (self._color_channels & COLOR_CHANNEL_RED or @@ -266,10 +282,10 @@ class ZwaveColorLight(ZwaveDimmer): rgbw = None if ATTR_COLOR_TEMP in kwargs: - # With two white channels, only two color temperatures are - # supported for the bulb. - if (self._color_channels & COLOR_CHANNEL_WARM_WHITE and - self._color_channels & COLOR_CHANNEL_COLD_WHITE): + # Color temperature. With the AEOTEC ZW098 bulb, only two color + # temperatures are supported. The warm and cold channel values + # indicate brightness for warm/cold color temperature. + if self._zw098: if kwargs[ATTR_COLOR_TEMP] > TEMP_MID_HASS: self._ct = TEMP_WARM_HASS rgbw = b'#000000FF00' @@ -277,29 +293,20 @@ class ZwaveColorLight(ZwaveDimmer): self._ct = TEMP_COLD_HASS rgbw = b'#00000000FF' - # If only warm white is reported 0-255 is color temperature - elif self._color_channels & COLOR_CHANNEL_WARM_WHITE: - rgbw = b'#000000' - temp = ( - (kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) / - (HASS_COLOR_MAX - HASS_COLOR_MIN) * 255) - rgbw += format(int(temp)).encode('utf-8') - - # If only cold white is reported 0-255 is negative color temp - elif self._color_channels & COLOR_CHANNEL_COLD_WHITE: - rgbw = b'#000000' - temp = ( - 255 - (kwargs[ATTR_COLOR_TEMP] - HASS_COLOR_MIN) / - (HASS_COLOR_MAX - HASS_COLOR_MIN) * 255) - rgbw += format(int(temp)).encode('utf-8') - elif ATTR_RGB_COLOR in kwargs: self._rgb = kwargs[ATTR_RGB_COLOR] - - rgbw = b'#' - for colorval in self._rgb: - rgbw += format(colorval, '02x').encode('utf-8') - rgbw += b'0000' + if (not self._zw098 and ( + self._color_channels & COLOR_CHANNEL_WARM_WHITE or + self._color_channels & COLOR_CHANNEL_COLD_WHITE)): + rgbw = b'#' + for colorval in color_rgb_to_rgbw(*self._rgb): + rgbw += format(colorval, '02x').encode('utf-8') + rgbw += b'00' + else: + rgbw = b'#' + for colorval in self._rgb: + rgbw += format(colorval, '02x').encode('utf-8') + rgbw += b'0000' if rgbw is None: _LOGGER.warning("rgbw string was not generated for turn_on") diff --git a/homeassistant/util/color.py b/homeassistant/util/color.py index 1f702a50193..646fe7941c3 100644 --- a/homeassistant/util/color.py +++ b/homeassistant/util/color.py @@ -112,6 +112,39 @@ def color_xy_brightness_to_RGB(vX, vY, brightness): return (r, g, b) +def _match_max_scale(input_colors, output_colors): + """Match the maximum value of the output to the input.""" + max_in = max(input_colors) + max_out = max(output_colors) + if max_out == 0: + factor = 0 + else: + factor = max_in / max_out + return tuple(int(round(i * factor)) for i in output_colors) + + +def color_rgb_to_rgbw(r, g, b): + """Convert an rgb color to an rgbw representation.""" + # Calculate the white channel as the minimum of input rgb channels. + # Subtract the white portion from the remaining rgb channels. + w = min(r, g, b) + rgbw = (r - w, g - w, b - w, w) + + # Match the output maximum value to the input. This ensures the full + # channel range is used. + return _match_max_scale((r, g, b), rgbw) + + +def color_rgbw_to_rgb(r, g, b, w): + """Convert an rgbw color to an rgb representation.""" + # Add the white channel back into the rgb channels. + rgb = (r + w, g + w, b + w) + + # Match the output maximum value to the input. This ensures the the + # output doesn't overflow. + return _match_max_scale((r, g, b, w), rgb) + + def rgb_hex_to_rgb_list(hex_string): """Return an RGB color value list from a hex color string.""" return [int(hex_string[i:i + len(hex_string) // 3], 16) diff --git a/tests/util/test_color.py b/tests/util/test_color.py index 884b59ec10e..50bee79283e 100644 --- a/tests/util/test_color.py +++ b/tests/util/test_color.py @@ -75,6 +75,58 @@ class TestColorUtil(unittest.TestCase): self.assertEqual((255, 255, 255), color_util.color_name_to_rgb('not a color')) + def test_color_rgb_to_rgbw(self): + """Test color_rgb_to_rgbw.""" + self.assertEqual((0, 0, 0, 0), + color_util.color_rgb_to_rgbw(0, 0, 0)) + + self.assertEqual((0, 0, 0, 255), + color_util.color_rgb_to_rgbw(255, 255, 255)) + + self.assertEqual((255, 0, 0, 0), + color_util.color_rgb_to_rgbw(255, 0, 0)) + + self.assertEqual((0, 255, 0, 0), + color_util.color_rgb_to_rgbw(0, 255, 0)) + + self.assertEqual((0, 0, 255, 0), + color_util.color_rgb_to_rgbw(0, 0, 255)) + + self.assertEqual((255, 127, 0, 0), + color_util.color_rgb_to_rgbw(255, 127, 0)) + + self.assertEqual((255, 0, 0, 253), + color_util.color_rgb_to_rgbw(255, 127, 127)) + + self.assertEqual((0, 0, 0, 127), + color_util.color_rgb_to_rgbw(127, 127, 127)) + + def test_color_rgbw_to_rgb(self): + """Test color_rgbw_to_rgb.""" + self.assertEqual((0, 0, 0), + color_util.color_rgbw_to_rgb(0, 0, 0, 0)) + + self.assertEqual((255, 255, 255), + color_util.color_rgbw_to_rgb(0, 0, 0, 255)) + + self.assertEqual((255, 0, 0), + color_util.color_rgbw_to_rgb(255, 0, 0, 0)) + + self.assertEqual((0, 255, 0), + color_util.color_rgbw_to_rgb(0, 255, 0, 0)) + + self.assertEqual((0, 0, 255), + color_util.color_rgbw_to_rgb(0, 0, 255, 0)) + + self.assertEqual((255, 127, 0), + color_util.color_rgbw_to_rgb(255, 127, 0, 0)) + + self.assertEqual((255, 127, 127), + color_util.color_rgbw_to_rgb(255, 0, 0, 253)) + + self.assertEqual((127, 127, 127), + color_util.color_rgbw_to_rgb(0, 0, 0, 127)) + class ColorTemperatureMiredToKelvinTests(unittest.TestCase): """Test color_temperature_mired_to_kelvin."""