mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
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:
parent
d021e593d3
commit
a6a34c76f7
@ -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:
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user