Skip to content

Commit

Permalink
Add color mode support to WLED (home-assistant#51648)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
2 people authored and thomasgermain committed Jun 10, 2021
1 parent 5e5870e commit feb23f3
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 116 deletions.
99 changes: 32 additions & 67 deletions homeassistant/components/wled/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,7 +26,6 @@
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 (
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -165,12 +164,14 @@ async def async_preset(
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
Expand All @@ -182,6 +183,12 @@ def __init__(self, coordinator: WLEDDataUpdateCoordinator, segment: int) -> None
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."""
Expand Down Expand Up @@ -214,10 +221,17 @@ def extra_state_attributes(self) -> dict[str, Any] | None:
}

@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:
Expand All @@ -238,28 +252,6 @@ def brightness(self) -> int | None:

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."""
Expand Down Expand Up @@ -301,17 +293,11 @@ async def async_turn_on(self, **kwargs: Any) -> None:
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.
Expand All @@ -323,27 +309,6 @@ async def async_turn_on(self, **kwargs: Any) -> None:
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:
Expand Down
53 changes: 4 additions & 49 deletions tests/components/wled/test_light.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@

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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -394,51 +379,21 @@ 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(
LIGHT_DOMAIN,
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,
)
Expand Down

0 comments on commit feb23f3

Please sign in to comment.