Add color mode support to WLED (#51648)

* Add color mode support to WLED

* Update homeassistant/components/wled/light.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/wled/light.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/wled/light.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/wled/light.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* black

* property, property

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Franck Nijhof 2021-06-09 13:31:31 +02:00 committed by GitHub
parent d021e593d3
commit a6a34c76f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 116 deletions

View File

@ -2,23 +2,21 @@
from __future__ import annotations from __future__ import annotations
from functools import partial from functools import partial
from typing import Any from typing import Any, Tuple, cast
import voluptuous as vol import voluptuous as vol
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_EFFECT, ATTR_EFFECT,
ATTR_HS_COLOR, ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_TRANSITION, ATTR_TRANSITION,
ATTR_WHITE_VALUE, COLOR_MODE_BRIGHTNESS,
SUPPORT_BRIGHTNESS, COLOR_MODE_RGB,
SUPPORT_COLOR, COLOR_MODE_RGBW,
SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT, SUPPORT_EFFECT,
SUPPORT_TRANSITION, SUPPORT_TRANSITION,
SUPPORT_WHITE_VALUE,
LightEntity, LightEntity,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -28,7 +26,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_registry import ( from homeassistant.helpers.entity_registry import (
async_get_registry as async_get_entity_registry, async_get_registry as async_get_entity_registry,
) )
import homeassistant.util.color as color_util
from . import WLEDDataUpdateCoordinator, WLEDEntity, wled_exception_handler from . import WLEDDataUpdateCoordinator, WLEDEntity, wled_exception_handler
from .const import ( from .const import (
@ -96,14 +93,16 @@ async def async_setup_entry(
class WLEDMasterLight(WLEDEntity, LightEntity): class WLEDMasterLight(WLEDEntity, LightEntity):
"""Defines a WLED master light.""" """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_icon = "mdi:led-strip-variant"
_attr_supported_features = SUPPORT_TRANSITION
def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None: def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None:
"""Initialize WLED master light.""" """Initialize WLED master light."""
super().__init__(coordinator=coordinator) super().__init__(coordinator=coordinator)
self._attr_name = f"{coordinator.data.info.name} Master" self._attr_name = f"{coordinator.data.info.name} Master"
self._attr_unique_id = coordinator.data.info.mac_address self._attr_unique_id = coordinator.data.info.mac_address
self._attr_supported_color_modes = {COLOR_MODE_BRIGHTNESS}
@property @property
def brightness(self) -> int | None: def brightness(self) -> int | None:
@ -165,12 +164,14 @@ class WLEDMasterLight(WLEDEntity, LightEntity):
class WLEDSegmentLight(WLEDEntity, LightEntity): class WLEDSegmentLight(WLEDEntity, LightEntity):
"""Defines a WLED light based on a segment.""" """Defines a WLED light based on a segment."""
_attr_supported_features = SUPPORT_EFFECT | SUPPORT_TRANSITION
_attr_icon = "mdi:led-strip-variant" _attr_icon = "mdi:led-strip-variant"
def __init__(self, coordinator: WLEDDataUpdateCoordinator, segment: int) -> None: def __init__(self, coordinator: WLEDDataUpdateCoordinator, segment: int) -> None:
"""Initialize WLED segment light.""" """Initialize WLED segment light."""
super().__init__(coordinator=coordinator) super().__init__(coordinator=coordinator)
self._rgbw = coordinator.data.info.leds.rgbw self._rgbw = coordinator.data.info.leds.rgbw
self._wv = coordinator.data.info.leds.wv
self._segment = segment self._segment = segment
# If this is the one and only segment, use a simpler name # 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}" 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 @property
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if entity is available."""
@ -214,10 +221,17 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
} }
@property @property
def hs_color(self) -> tuple[float, float]: def rgb_color(self) -> tuple[int, int, int] | None:
"""Return the hue and saturation color value [float, float].""" """Return the color value."""
color = self.coordinator.data.state.segments[self._segment].color_primary return self.coordinator.data.state.segments[self._segment].color_primary[:3]
return color_util.color_RGB_to_hs(*color[: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 @property
def effect(self) -> str | None: def effect(self) -> str | None:
@ -238,28 +252,6 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
return state.segments[self._segment].brightness 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 @property
def effect_list(self) -> list[str]: def effect_list(self) -> list[str]:
"""Return the list of supported effects.""" """Return the list of supported effects."""
@ -301,17 +293,11 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
ATTR_SEGMENT_ID: self._segment, ATTR_SEGMENT_ID: self._segment,
} }
if ATTR_COLOR_TEMP in kwargs: if ATTR_RGB_COLOR in kwargs:
mireds = color_util.color_temperature_kelvin_to_mired( data[ATTR_COLOR_PRIMARY] = kwargs[ATTR_RGB_COLOR]
kwargs[ATTR_COLOR_TEMP]
)
data[ATTR_COLOR_PRIMARY] = tuple(
map(int, color_util.color_temperature_to_rgb(mireds))
)
if ATTR_HS_COLOR in kwargs: if ATTR_RGBW_COLOR in kwargs:
hue, sat = kwargs[ATTR_HS_COLOR] data[ATTR_COLOR_PRIMARY] = kwargs[ATTR_RGBW_COLOR]
data[ATTR_COLOR_PRIMARY] = color_util.color_hsv_to_RGB(hue, sat, 100)
if ATTR_TRANSITION in kwargs: if ATTR_TRANSITION in kwargs:
# WLED uses 100ms per unit, so 10 = 1 second. # WLED uses 100ms per unit, so 10 = 1 second.
@ -323,27 +309,6 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
if ATTR_EFFECT in kwargs: if ATTR_EFFECT in kwargs:
data[ATTR_EFFECT] = kwargs[ATTR_EFFECT] 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 # When only 1 segment is present, switch along the master, and use
# the master for power/brightness control. # the master for power/brightness control.
if len(self.coordinator.data.state.segments) == 1: if len(self.coordinator.data.state.segments) == 1:

View File

@ -6,12 +6,11 @@ from wled import Device as WLEDDevice, WLEDConnectionError
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_EFFECT, ATTR_EFFECT,
ATTR_HS_COLOR, ATTR_HS_COLOR,
ATTR_RGB_COLOR, ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_TRANSITION, ATTR_TRANSITION,
ATTR_WHITE_VALUE,
DOMAIN as LIGHT_DOMAIN, DOMAIN as LIGHT_DOMAIN,
) )
from homeassistant.components.wled import SCAN_INTERVAL from homeassistant.components.wled import SCAN_INTERVAL
@ -144,20 +143,6 @@ async def test_segment_change_state(
transition=50, 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( async def test_master_change_state(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
@ -394,36 +379,7 @@ async def test_rgbw_light(
state = hass.states.get("light.wled_rgbw_light") state = hass.states.get("light.wled_rgbw_light")
assert state.state == STATE_ON assert state.state == STATE_ON
assert state.attributes.get(ATTR_HS_COLOR) == (0.0, 100.0) assert state.attributes.get(ATTR_RGBW_COLOR) == (255, 0, 0, 139)
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,
)
with patch("wled.WLED.segment") as light_mock: with patch("wled.WLED.segment") as light_mock:
await hass.services.async_call( await hass.services.async_call(
@ -431,14 +387,13 @@ async def test_rgbw_light(
SERVICE_TURN_ON, SERVICE_TURN_ON,
{ {
ATTR_ENTITY_ID: "light.wled_rgbw_light", ATTR_ENTITY_ID: "light.wled_rgbw_light",
ATTR_RGB_COLOR: (255, 255, 255), ATTR_RGBW_COLOR: (255, 255, 255, 255),
ATTR_WHITE_VALUE: 100,
}, },
blocking=True, blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
light_mock.assert_called_once_with( light_mock.assert_called_once_with(
color_primary=(0, 0, 0, 100), color_primary=(255, 255, 255, 255),
on=True, on=True,
segment_id=0, segment_id=0,
) )