From a6a34c76f777cbf5b8328b57f58bfa6dffa9bb68 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 9 Jun 2021 13:31:31 +0200 Subject: [PATCH] Add color mode support to WLED (#51648) * Add color mode support to WLED * Update homeassistant/components/wled/light.py Co-authored-by: Erik Montnemery * Update homeassistant/components/wled/light.py Co-authored-by: Erik Montnemery * Update homeassistant/components/wled/light.py Co-authored-by: Erik Montnemery * Update homeassistant/components/wled/light.py Co-authored-by: Erik Montnemery * black * property, property Co-authored-by: Erik Montnemery --- homeassistant/components/wled/light.py | 99 +++++++++----------------- tests/components/wled/test_light.py | 53 ++------------ 2 files changed, 36 insertions(+), 116 deletions(-) diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index d9240e75efb..c40c61f98f4 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -2,23 +2,21 @@ from __future__ import annotations from functools import partial -from typing import Any +from typing import Any, Tuple, cast import voluptuous as vol from homeassistant.components.light import ( ATTR_BRIGHTNESS, - ATTR_COLOR_TEMP, ATTR_EFFECT, - ATTR_HS_COLOR, + ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, ATTR_TRANSITION, - ATTR_WHITE_VALUE, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_RGB, + COLOR_MODE_RGBW, SUPPORT_EFFECT, SUPPORT_TRANSITION, - SUPPORT_WHITE_VALUE, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -28,7 +26,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_registry import ( async_get_registry as async_get_entity_registry, ) -import homeassistant.util.color as color_util from . import WLEDDataUpdateCoordinator, WLEDEntity, wled_exception_handler from .const import ( @@ -96,14 +93,16 @@ async def async_setup_entry( class WLEDMasterLight(WLEDEntity, LightEntity): """Defines a WLED master light.""" - _attr_supported_features = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + _attr_color_mode = COLOR_MODE_BRIGHTNESS _attr_icon = "mdi:led-strip-variant" + _attr_supported_features = SUPPORT_TRANSITION def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None: """Initialize WLED master light.""" super().__init__(coordinator=coordinator) self._attr_name = f"{coordinator.data.info.name} Master" self._attr_unique_id = coordinator.data.info.mac_address + self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS} @property def brightness(self) -> int | None: @@ -165,12 +164,14 @@ class WLEDMasterLight(WLEDEntity, LightEntity): class WLEDSegmentLight(WLEDEntity, LightEntity): """Defines a WLED light based on a segment.""" + _attr_supported_features = SUPPORT_EFFECT | SUPPORT_TRANSITION _attr_icon = "mdi:led-strip-variant" def __init__(self, coordinator: WLEDDataUpdateCoordinator, segment: int) -> None: """Initialize WLED segment light.""" super().__init__(coordinator=coordinator) self._rgbw = coordinator.data.info.leds.rgbw + self._wv = coordinator.data.info.leds.wv self._segment = segment # If this is the one and only segment, use a simpler name @@ -182,6 +183,12 @@ class WLEDSegmentLight(WLEDEntity, LightEntity): f"{self.coordinator.data.info.mac_address}_{self._segment}" ) + self._attr_color_mode = COLOR_MODE_RGB + self._attr_supported_color_modes = {COLOR_MODE_RGB} + if self._rgbw and self._wv: + self._attr_color_mode = COLOR_MODE_RGBW + self._attr_supported_color_modes = {COLOR_MODE_RGBW} + @property def available(self) -> bool: """Return True if entity is available.""" @@ -214,10 +221,17 @@ class WLEDSegmentLight(WLEDEntity, LightEntity): } @property - def hs_color(self) -> tuple[float, float]: - """Return the hue and saturation color value [float, float].""" - color = self.coordinator.data.state.segments[self._segment].color_primary - return color_util.color_RGB_to_hs(*color[:3]) + def rgb_color(self) -> tuple[int, int, int] | None: + """Return the color value.""" + return self.coordinator.data.state.segments[self._segment].color_primary[:3] + + @property + def rgbw_color(self) -> tuple[int, int, int, int] | None: + """Return the color value.""" + return cast( + Tuple[int, int, int, int], + self.coordinator.data.state.segments[self._segment].color_primary, + ) @property def effect(self) -> str | None: @@ -238,28 +252,6 @@ class WLEDSegmentLight(WLEDEntity, LightEntity): return state.segments[self._segment].brightness - @property - def white_value(self) -> int | None: - """Return the white value of this light between 0..255.""" - color = self.coordinator.data.state.segments[self._segment].color_primary - return color[-1] if self._rgbw else None - - @property - def supported_features(self) -> int: - """Flag supported features.""" - flags = ( - SUPPORT_BRIGHTNESS - | SUPPORT_COLOR - | SUPPORT_COLOR_TEMP - | SUPPORT_EFFECT - | SUPPORT_TRANSITION - ) - - if self._rgbw: - flags |= SUPPORT_WHITE_VALUE - - return flags - @property def effect_list(self) -> list[str]: """Return the list of supported effects.""" @@ -301,17 +293,11 @@ class WLEDSegmentLight(WLEDEntity, LightEntity): ATTR_SEGMENT_ID: self._segment, } - if ATTR_COLOR_TEMP in kwargs: - mireds = color_util.color_temperature_kelvin_to_mired( - kwargs[ATTR_COLOR_TEMP] - ) - data[ATTR_COLOR_PRIMARY] = tuple( - map(int, color_util.color_temperature_to_rgb(mireds)) - ) + if ATTR_RGB_COLOR in kwargs: + data[ATTR_COLOR_PRIMARY] = kwargs[ATTR_RGB_COLOR] - if ATTR_HS_COLOR in kwargs: - hue, sat = kwargs[ATTR_HS_COLOR] - data[ATTR_COLOR_PRIMARY] = color_util.color_hsv_to_RGB(hue, sat, 100) + if ATTR_RGBW_COLOR in kwargs: + data[ATTR_COLOR_PRIMARY] = kwargs[ATTR_RGBW_COLOR] if ATTR_TRANSITION in kwargs: # WLED uses 100ms per unit, so 10 = 1 second. @@ -323,27 +309,6 @@ class WLEDSegmentLight(WLEDEntity, LightEntity): if ATTR_EFFECT in kwargs: data[ATTR_EFFECT] = kwargs[ATTR_EFFECT] - # Support for RGBW strips, adds white value - if self._rgbw and any( - x in (ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_WHITE_VALUE) for x in kwargs - ): - # WLED cannot just accept a white value, it needs the color. - # We use the last know color in case just the white value changes. - if all(x not in (ATTR_COLOR_TEMP, ATTR_HS_COLOR) for x in kwargs): - hue, sat = self.hs_color - data[ATTR_COLOR_PRIMARY] = color_util.color_hsv_to_RGB(hue, sat, 100) - - # On a RGBW strip, when the color is pure white, disable the RGB LEDs in - # WLED by setting RGB to 0,0,0 - if data[ATTR_COLOR_PRIMARY] == (255, 255, 255): - data[ATTR_COLOR_PRIMARY] = (0, 0, 0) - - # Add requested or last known white value - if ATTR_WHITE_VALUE in kwargs: - data[ATTR_COLOR_PRIMARY] += (kwargs[ATTR_WHITE_VALUE],) - else: - data[ATTR_COLOR_PRIMARY] += (self.white_value,) - # When only 1 segment is present, switch along the master, and use # the master for power/brightness control. if len(self.coordinator.data.state.segments) == 1: diff --git a/tests/components/wled/test_light.py b/tests/components/wled/test_light.py index 0077cea0202..268c527a763 100644 --- a/tests/components/wled/test_light.py +++ b/tests/components/wled/test_light.py @@ -6,12 +6,11 @@ from wled import Device as WLEDDevice, WLEDConnectionError from homeassistant.components.light import ( ATTR_BRIGHTNESS, - ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, ATTR_RGB_COLOR, + ATTR_RGBW_COLOR, ATTR_TRANSITION, - ATTR_WHITE_VALUE, DOMAIN as LIGHT_DOMAIN, ) from homeassistant.components.wled import SCAN_INTERVAL @@ -144,20 +143,6 @@ async def test_segment_change_state( transition=50, ) - with patch("wled.WLED.segment") as light_mock: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_COLOR_TEMP: 400}, - blocking=True, - ) - await hass.async_block_till_done() - light_mock.assert_called_once_with( - color_primary=(255, 159, 70), - on=True, - segment_id=0, - ) - async def test_master_change_state( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog @@ -394,36 +379,7 @@ async def test_rgbw_light( state = hass.states.get("light.wled_rgbw_light") assert state.state == STATE_ON - assert state.attributes.get(ATTR_HS_COLOR) == (0.0, 100.0) - assert state.attributes.get(ATTR_WHITE_VALUE) == 139 - - with patch("wled.WLED.segment") as light_mock: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "light.wled_rgbw_light", ATTR_COLOR_TEMP: 400}, - blocking=True, - ) - await hass.async_block_till_done() - light_mock.assert_called_once_with( - on=True, - segment_id=0, - color_primary=(255, 159, 70, 139), - ) - - with patch("wled.WLED.segment") as light_mock: - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "light.wled_rgbw_light", ATTR_WHITE_VALUE: 100}, - blocking=True, - ) - await hass.async_block_till_done() - light_mock.assert_called_once_with( - color_primary=(255, 0, 0, 100), - on=True, - segment_id=0, - ) + assert state.attributes.get(ATTR_RGBW_COLOR) == (255, 0, 0, 139) with patch("wled.WLED.segment") as light_mock: await hass.services.async_call( @@ -431,14 +387,13 @@ async def test_rgbw_light( SERVICE_TURN_ON, { ATTR_ENTITY_ID: "light.wled_rgbw_light", - ATTR_RGB_COLOR: (255, 255, 255), - ATTR_WHITE_VALUE: 100, + ATTR_RGBW_COLOR: (255, 255, 255, 255), }, blocking=True, ) await hass.async_block_till_done() light_mock.assert_called_once_with( - color_primary=(0, 0, 0, 100), + color_primary=(255, 255, 255, 255), on=True, segment_id=0, )