From 956c972e7214cb8a16d85ec21cc579f73cd9a323 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 30 Apr 2021 13:46:25 +0200 Subject: [PATCH] Add color_mode support to zwave_js light (#49588) --- homeassistant/components/zwave_js/light.py | 95 ++++++++++++++-------- tests/components/zwave_js/test_light.py | 23 ++++-- 2 files changed, 77 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/zwave_js/light.py b/homeassistant/components/zwave_js/light.py index 8dd5afea2a9..074332daaed 100644 --- a/homeassistant/components/zwave_js/light.py +++ b/homeassistant/components/zwave_js/light.py @@ -11,14 +11,14 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, + ATTR_RGBW_COLOR, ATTR_TRANSITION, - ATTR_WHITE_VALUE, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + COLOR_MODE_RGBW, DOMAIN as LIGHT_DOMAIN, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, SUPPORT_TRANSITION, - SUPPORT_WHITE_VALUE, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -88,14 +88,14 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): """Initialize the light.""" super().__init__(config_entry, client, info) self._supports_color = False - self._supports_white_value = False + self._supports_rgbw = False self._supports_color_temp = False self._hs_color: tuple[float, float] | None = None - self._white_value: int | None = None + self._rgbw_color: tuple[int, int, int, int] | None = None + self._color_mode: str | None = None self._color_temp: int | None = None self._min_mireds = 153 # 6500K as a safe default self._max_mireds = 370 # 2700K as a safe default - self._supported_features = SUPPORT_BRIGHTNESS self._warm_white = self.get_zwave_value( "targetColor", CommandClass.SWITCH_COLOR, @@ -106,6 +106,8 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): CommandClass.SWITCH_COLOR, value_property_key=ColorComponent.COLD_WHITE, ) + self._supported_color_modes = set() + self._supported_features = 0 # get additional (optional) values and set features self._target_value = self.get_zwave_value("targetValue") @@ -113,12 +115,14 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): if self._dimming_duration is not None: self._supported_features |= SUPPORT_TRANSITION self._calculate_color_values() - if self._supports_color: - self._supported_features |= SUPPORT_COLOR + if self._supports_rgbw: + self._supported_color_modes.add(COLOR_MODE_RGBW) + elif self._supports_color: + self._supported_color_modes.add(COLOR_MODE_HS) if self._supports_color_temp: - self._supported_features |= SUPPORT_COLOR_TEMP - if self._supports_white_value: - self._supported_features |= SUPPORT_WHITE_VALUE + self._supported_color_modes.add(COLOR_MODE_COLOR_TEMP) + if not self._supported_color_modes: + self._supported_color_modes.add(COLOR_MODE_BRIGHTNESS) @callback def on_value_update(self) -> None: @@ -135,6 +139,11 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): return round((self.info.primary_value.value / 99) * 255) return 0 + @property + def color_mode(self) -> str | None: + """Return the color mode of the light.""" + return self._color_mode + @property def is_on(self) -> bool: """Return true if device is on (brightness above 0).""" @@ -146,9 +155,9 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): return self._hs_color @property - def white_value(self) -> int | None: - """Return the white value of this light between 0..255.""" - return self._white_value + def rgbw_color(self) -> tuple[int, int, int, int] | None: + """Return the hs color.""" + return self._rgbw_color @property def color_temp(self) -> int | None: @@ -165,6 +174,11 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): """Return the warmest color_temp that this light supports.""" return self._max_mireds + @property + def supported_color_modes(self) -> set | None: + """Flag supported features.""" + return self._supported_color_modes + @property def supported_features(self) -> int: """Flag supported features.""" @@ -214,20 +228,20 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): } ) - # White value - white_value = kwargs.get(ATTR_WHITE_VALUE) - if white_value is not None and self._supports_white_value: - # white led brightness is controlled by white level - # rgb leds (if any) can be on at the same time - white_channel = {} - + # RGBW + rgbw = kwargs.get(ATTR_RGBW_COLOR) + if rgbw is not None and self._supports_rgbw: + rgbw_channels = { + ColorComponent.RED: rgbw[0], + ColorComponent.GREEN: rgbw[1], + ColorComponent.BLUE: rgbw[2], + } if self._warm_white: - white_channel[ColorComponent.WARM_WHITE] = white_value + rgbw_channels[ColorComponent.WARM_WHITE] = rgbw[3] if self._cold_white: - white_channel[ColorComponent.COLD_WHITE] = white_value - - await self._async_set_colors(white_channel) + rgbw_channels[ColorComponent.COLD_WHITE] = rgbw[3] + await self._async_set_colors(rgbw_channels) # set brightness await self._async_set_brightness( @@ -364,6 +378,9 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): else: multi_color = {} + # Default: Brightness (no color) + self._color_mode = COLOR_MODE_BRIGHTNESS + # RGB support if red_val and green_val and blue_val: # prefer values from the multicolor property @@ -373,6 +390,8 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): self._supports_color = True # convert to HS self._hs_color = color_util.color_RGB_to_hs(red, green, blue) + # Light supports color, set color mode to hs + self._color_mode = COLOR_MODE_HS # color temperature support if ww_val and cw_val: @@ -385,13 +404,21 @@ class ZwaveLight(ZWaveBaseEntity, LightEntity): self._max_mireds - ((cold_white / 255) * (self._max_mireds - self._min_mireds)) ) + # White channels turned on, set color mode to color_temp + self._color_mode = COLOR_MODE_COLOR_TEMP else: self._color_temp = None - # only one white channel (warm white) = white_level support - elif ww_val: - self._supports_white_value = True - self._white_value = multi_color.get("warmWhite", ww_val.value) - # only one white channel (cool white) = white_level support + # only one white channel (warm white) = rgbw support + elif red_val and green_val and blue_val and ww_val: + self._supports_rgbw = True + white = multi_color.get("warmWhite", ww_val.value) + self._rgbw_color = (red, green, blue, white) + # Light supports rgbw, set color mode to rgbw + self._color_mode = COLOR_MODE_RGBW + # only one white channel (cool white) = rgbw support elif cw_val: - self._supports_white_value = True - self._white_value = multi_color.get("coldWhite", cw_val.value) + self._supports_rgbw = True + white = multi_color.get("coldWhite", cw_val.value) + self._rgbw_color = (red, green, blue, white) + # Light supports rgbw, set color mode to rgbw + self._color_mode = COLOR_MODE_RGBW diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index cf45f2b9a79..3a99c4f7a23 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -5,11 +5,14 @@ from zwave_js_server.event import Event from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_COLOR_MODE, ATTR_COLOR_TEMP, ATTR_MAX_MIREDS, ATTR_MIN_MIREDS, ATTR_RGB_COLOR, - ATTR_WHITE_VALUE, + ATTR_RGBW_COLOR, + ATTR_SUPPORTED_COLOR_MODES, + SUPPORT_TRANSITION, ) from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_OFF, STATE_ON @@ -30,7 +33,8 @@ async def test_light(hass, client, bulb_6_multi_color, integration): assert state.state == STATE_OFF assert state.attributes[ATTR_MIN_MIREDS] == 153 assert state.attributes[ATTR_MAX_MIREDS] == 370 - assert state.attributes[ATTR_SUPPORTED_FEATURES] == 51 + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TRANSITION + assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["color_temp", "hs"] # Test turning on await hass.services.async_call( @@ -86,8 +90,10 @@ async def test_light(hass, client, bulb_6_multi_color, integration): state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY) assert state.state == STATE_ON + assert state.attributes[ATTR_COLOR_MODE] == "color_temp" assert state.attributes[ATTR_BRIGHTNESS] == 255 assert state.attributes[ATTR_COLOR_TEMP] == 370 + assert ATTR_RGB_COLOR not in state.attributes # Test turning on with same brightness await hass.services.async_call( @@ -231,8 +237,10 @@ async def test_light(hass, client, bulb_6_multi_color, integration): state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY) assert state.state == STATE_ON + assert state.attributes[ATTR_COLOR_MODE] == "hs" assert state.attributes[ATTR_BRIGHTNESS] == 255 assert state.attributes[ATTR_RGB_COLOR] == (255, 76, 255) + assert ATTR_COLOR_TEMP not in state.attributes client.async_send_command.reset_mock() @@ -352,9 +360,10 @@ async def test_light(hass, client, bulb_6_multi_color, integration): state = hass.states.get(BULB_6_MULTI_COLOR_LIGHT_ENTITY) assert state.state == STATE_ON + assert state.attributes[ATTR_COLOR_MODE] == "color_temp" assert state.attributes[ATTR_BRIGHTNESS] == 255 assert state.attributes[ATTR_COLOR_TEMP] == 170 - assert state.attributes[ATTR_RGB_COLOR] == (255, 255, 255) + assert ATTR_RGB_COLOR not in state.attributes # Test turning on with same color temp await hass.services.async_call( @@ -415,20 +424,20 @@ async def test_optional_light(hass, client, aeon_smart_switch_6, integration): assert state.state == STATE_ON -async def test_white_value_light(hass, client, zen_31, integration): +async def test_rgbw_light(hass, client, zen_31, integration): """Test the light entity.""" zen_31 state = hass.states.get(ZEN_31_ENTITY) assert state assert state.state == STATE_ON - assert state.attributes[ATTR_SUPPORTED_FEATURES] == 177 + assert state.attributes[ATTR_SUPPORTED_FEATURES] == SUPPORT_TRANSITION # Test turning on await hass.services.async_call( "light", "turn_on", - {"entity_id": ZEN_31_ENTITY, ATTR_WHITE_VALUE: 128}, + {"entity_id": ZEN_31_ENTITY, ATTR_RGBW_COLOR: (0, 0, 0, 128)}, blocking=True, ) @@ -451,7 +460,7 @@ async def test_white_value_light(hass, client, zen_31, integration): }, "value": {"blue": 70, "green": 159, "red": 255, "warmWhite": 141}, } - assert args["value"] == {"warmWhite": 128} + assert args["value"] == {"blue": 0, "green": 0, "red": 0, "warmWhite": 128} args = client.async_send_command.call_args_list[1][0][0] assert args["command"] == "node.set_value"