Add color modes to KNX light (#49883)
* color_modes support * return brightness for color lights even when no brightness address is set * apply brightness for color lights * remove unneeded constantspull/49955/head
parent
0a6f981b4c
commit
cfabb06a7a
|
@ -2,7 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, cast
|
||||
|
||||
from xknx.devices import Light as XknxLight
|
||||
from xknx.telegram.address import parse_device_group_address
|
||||
|
@ -10,12 +10,13 @@ from xknx.telegram.address import parse_device_group_address
|
|||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_WHITE_VALUE,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
COLOR_MODE_BRIGHTNESS,
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
COLOR_MODE_ONOFF,
|
||||
COLOR_MODE_RGB,
|
||||
COLOR_MODE_RGBW,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
@ -28,10 +29,6 @@ from .const import DOMAIN, KNX_ADDRESS
|
|||
from .knx_entity import KnxEntity
|
||||
from .schema import LightSchema
|
||||
|
||||
DEFAULT_COLOR = (0.0, 0.0)
|
||||
DEFAULT_BRIGHTNESS = 255
|
||||
DEFAULT_WHITE_VALUE = 255
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
|
@ -146,39 +143,39 @@ class KNXLight(KnxEntity, LightEntity):
|
|||
f"{self._device.blue.brightness.group_address}"
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if light is on."""
|
||||
return bool(self._device.state)
|
||||
|
||||
@property
|
||||
def brightness(self) -> int | None:
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
if self._device.supports_brightness:
|
||||
return self._device.current_brightness
|
||||
hsv_color = self._hsv_color
|
||||
if self._device.supports_color and hsv_color:
|
||||
return round(hsv_color[-1] / 100 * 255)
|
||||
if (rgb := self.rgb_color) is not None:
|
||||
return max(rgb)
|
||||
return None
|
||||
|
||||
@property
|
||||
def hs_color(self) -> tuple[float, float] | None:
|
||||
"""Return the HS color value."""
|
||||
rgb: tuple[int, int, int] | None = None
|
||||
if self._device.supports_rgbw or self._device.supports_color:
|
||||
def rgb_color(self) -> tuple[int, int, int] | None:
|
||||
"""Return the rgb color value [int, int, int]."""
|
||||
if (rgbw := self.rgbw_color) is not None:
|
||||
# used in brightness calculation when no address is given
|
||||
return color_util.color_rgbw_to_rgb(*rgbw)
|
||||
if self._device.supports_color:
|
||||
rgb, _ = self._device.current_color
|
||||
return color_util.color_RGB_to_hs(*rgb) if rgb else None
|
||||
return rgb
|
||||
return None
|
||||
|
||||
@property
|
||||
def _hsv_color(self) -> tuple[float, float, float] | None:
|
||||
"""Return the HSV color value."""
|
||||
rgb: tuple[int, int, int] | None = None
|
||||
if self._device.supports_rgbw or self._device.supports_color:
|
||||
rgb, _ = self._device.current_color
|
||||
return color_util.color_RGB_to_hsv(*rgb) if rgb else None
|
||||
|
||||
@property
|
||||
def white_value(self) -> int | None:
|
||||
"""Return the white value."""
|
||||
white: int | None = None
|
||||
def rgbw_color(self) -> tuple[int, int, int, int] | None:
|
||||
"""Return the rgbw color value [int, int, int, int]."""
|
||||
if self._device.supports_rgbw:
|
||||
_, white = self._device.current_color
|
||||
return white
|
||||
rgb, white = self._device.current_color
|
||||
if rgb is not None and white is not None:
|
||||
return (*rgb, white)
|
||||
return None
|
||||
|
||||
@property
|
||||
def color_temp(self) -> int | None:
|
||||
|
@ -210,83 +207,80 @@ class KNXLight(KnxEntity, LightEntity):
|
|||
return self._max_mireds
|
||||
|
||||
@property
|
||||
def effect_list(self) -> list[str] | None:
|
||||
"""Return the list of supported effects."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def effect(self) -> str | None:
|
||||
"""Return the current effect."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if light is on."""
|
||||
return bool(self._device.state)
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
flags = 0
|
||||
if self._device.supports_brightness:
|
||||
flags |= SUPPORT_BRIGHTNESS
|
||||
if self._device.supports_color:
|
||||
flags |= SUPPORT_COLOR | SUPPORT_BRIGHTNESS
|
||||
def color_mode(self) -> str | None:
|
||||
"""Return the color mode of the light."""
|
||||
if self._device.supports_rgbw:
|
||||
flags |= SUPPORT_COLOR | SUPPORT_WHITE_VALUE
|
||||
return COLOR_MODE_RGBW
|
||||
if self._device.supports_color:
|
||||
return COLOR_MODE_RGB
|
||||
if (
|
||||
self._device.supports_color_temperature
|
||||
or self._device.supports_tunable_white
|
||||
):
|
||||
flags |= SUPPORT_COLOR_TEMP
|
||||
return flags
|
||||
return COLOR_MODE_COLOR_TEMP
|
||||
if self._device.supports_brightness:
|
||||
return COLOR_MODE_BRIGHTNESS
|
||||
return COLOR_MODE_ONOFF
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> set | None:
|
||||
"""Flag supported color modes."""
|
||||
return {self.color_mode}
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the light on."""
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness)
|
||||
hs_color = kwargs.get(ATTR_HS_COLOR, self.hs_color)
|
||||
white_value = kwargs.get(ATTR_WHITE_VALUE, self.white_value)
|
||||
mireds = kwargs.get(ATTR_COLOR_TEMP, self.color_temp)
|
||||
# ignore arguments if not supported to fall back to set_on()
|
||||
brightness = (
|
||||
kwargs.get(ATTR_BRIGHTNESS)
|
||||
if self._device.supports_brightness
|
||||
or self.color_mode in (COLOR_MODE_RGB, COLOR_MODE_RGBW)
|
||||
else None
|
||||
)
|
||||
mireds = (
|
||||
kwargs.get(ATTR_COLOR_TEMP)
|
||||
if self.color_mode == COLOR_MODE_COLOR_TEMP
|
||||
else None
|
||||
)
|
||||
rgb = kwargs.get(ATTR_RGB_COLOR) if self.color_mode == COLOR_MODE_RGB else None
|
||||
rgbw = (
|
||||
kwargs.get(ATTR_RGBW_COLOR) if self.color_mode == COLOR_MODE_RGBW else None
|
||||
)
|
||||
|
||||
update_brightness = ATTR_BRIGHTNESS in kwargs
|
||||
update_color = ATTR_HS_COLOR in kwargs
|
||||
update_white_value = ATTR_WHITE_VALUE in kwargs
|
||||
update_color_temp = ATTR_COLOR_TEMP in kwargs
|
||||
|
||||
# avoid conflicting changes and weird effects
|
||||
if not (
|
||||
self.is_on
|
||||
or update_brightness
|
||||
or update_color
|
||||
or update_white_value
|
||||
or update_color_temp
|
||||
if (
|
||||
not self.is_on
|
||||
and brightness is None
|
||||
and mireds is None
|
||||
and rgb is None
|
||||
and rgbw is None
|
||||
):
|
||||
await self._device.set_on()
|
||||
return
|
||||
|
||||
if self._device.supports_brightness and (
|
||||
update_brightness and not update_color
|
||||
):
|
||||
# if we don't need to update the color, try updating brightness
|
||||
# directly if supported; don't do it if color also has to be
|
||||
# changed, as RGB color implicitly sets the brightness as well
|
||||
await self._device.set_brightness(brightness)
|
||||
elif (self._device.supports_rgbw or self._device.supports_color) and (
|
||||
update_brightness or update_color or update_white_value
|
||||
):
|
||||
# change RGB color, white value (if supported), and brightness
|
||||
# if brightness or hs_color was not yet set use the default value
|
||||
# to calculate RGB from as a fallback
|
||||
if brightness is None:
|
||||
brightness = DEFAULT_BRIGHTNESS
|
||||
if hs_color is None:
|
||||
hs_color = DEFAULT_COLOR
|
||||
if white_value is None and self._device.supports_rgbw:
|
||||
white_value = DEFAULT_WHITE_VALUE
|
||||
hsv_color = hs_color + (brightness * 100 / 255,)
|
||||
rgb = color_util.color_hsv_to_RGB(*hsv_color)
|
||||
await self._device.set_color(rgb, white_value)
|
||||
async def set_color(
|
||||
rgb: tuple[int, int, int], white: int | None, brightness: int | None
|
||||
) -> None:
|
||||
"""Set color of light. Normalize colors for brightness when not writable."""
|
||||
if brightness:
|
||||
if self._device.brightness.writable:
|
||||
await self._device.set_color(rgb, white)
|
||||
await self._device.set_brightness(brightness)
|
||||
return
|
||||
rgb = cast(
|
||||
tuple[int, int, int],
|
||||
tuple(color * brightness // 255 for color in rgb),
|
||||
)
|
||||
white = white * brightness // 255 if white is not None else None
|
||||
await self._device.set_color(rgb, white)
|
||||
|
||||
if update_color_temp:
|
||||
# return after RGB(W) color has changed as it implicitly sets the brightness
|
||||
if rgbw is not None:
|
||||
await set_color(rgbw[:3], rgbw[3], brightness)
|
||||
return
|
||||
if rgb is not None:
|
||||
await set_color(rgb, None, brightness)
|
||||
return
|
||||
|
||||
if mireds is not None:
|
||||
kelvin = int(color_util.color_temperature_mired_to_kelvin(mireds))
|
||||
kelvin = min(self._max_kelvin, max(self._min_kelvin, kelvin))
|
||||
|
||||
|
@ -300,6 +294,9 @@ class KNXLight(KnxEntity, LightEntity):
|
|||
)
|
||||
await self._device.set_tunable_white(relative_ct)
|
||||
|
||||
if brightness is not None:
|
||||
await self._device.set_brightness(brightness)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the light off."""
|
||||
await self._device.set_off()
|
||||
|
|
Loading…
Reference in New Issue