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 constants
pull/49955/head
Matthias Alphart 2021-05-03 11:28:02 +02:00 committed by GitHub
parent 0a6f981b4c
commit cfabb06a7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 93 additions and 96 deletions

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Iterable 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.devices import Light as XknxLight
from xknx.telegram.address import parse_device_group_address 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 ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP, ATTR_COLOR_TEMP,
ATTR_HS_COLOR, ATTR_RGB_COLOR,
ATTR_WHITE_VALUE, ATTR_RGBW_COLOR,
SUPPORT_BRIGHTNESS, COLOR_MODE_BRIGHTNESS,
SUPPORT_COLOR, COLOR_MODE_COLOR_TEMP,
SUPPORT_COLOR_TEMP, COLOR_MODE_ONOFF,
SUPPORT_WHITE_VALUE, COLOR_MODE_RGB,
COLOR_MODE_RGBW,
LightEntity, LightEntity,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
@ -28,10 +29,6 @@ from .const import DOMAIN, KNX_ADDRESS
from .knx_entity import KnxEntity from .knx_entity import KnxEntity
from .schema import LightSchema from .schema import LightSchema
DEFAULT_COLOR = (0.0, 0.0)
DEFAULT_BRIGHTNESS = 255
DEFAULT_WHITE_VALUE = 255
async def async_setup_platform( async def async_setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
@ -146,39 +143,39 @@ class KNXLight(KnxEntity, LightEntity):
f"{self._device.blue.brightness.group_address}" 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 @property
def brightness(self) -> int | None: def brightness(self) -> int | None:
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
if self._device.supports_brightness: if self._device.supports_brightness:
return self._device.current_brightness return self._device.current_brightness
hsv_color = self._hsv_color if (rgb := self.rgb_color) is not None:
if self._device.supports_color and hsv_color: return max(rgb)
return round(hsv_color[-1] / 100 * 255)
return None return None
@property @property
def hs_color(self) -> tuple[float, float] | None: def rgb_color(self) -> tuple[int, int, int] | None:
"""Return the HS color value.""" """Return the rgb color value [int, int, int]."""
rgb: tuple[int, int, int] | None = None if (rgbw := self.rgbw_color) is not None:
if self._device.supports_rgbw or self._device.supports_color: # 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 rgb, _ = self._device.current_color
return color_util.color_RGB_to_hs(*rgb) if rgb else None return rgb
return None
@property @property
def _hsv_color(self) -> tuple[float, float, float] | None: def rgbw_color(self) -> tuple[int, int, int, int] | None:
"""Return the HSV color value.""" """Return the rgbw color value [int, int, int, int]."""
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
if self._device.supports_rgbw: if self._device.supports_rgbw:
_, white = self._device.current_color rgb, white = self._device.current_color
return white if rgb is not None and white is not None:
return (*rgb, white)
return None
@property @property
def color_temp(self) -> int | None: def color_temp(self) -> int | None:
@ -210,83 +207,80 @@ class KNXLight(KnxEntity, LightEntity):
return self._max_mireds return self._max_mireds
@property @property
def effect_list(self) -> list[str] | None: def color_mode(self) -> str | None:
"""Return the list of supported effects.""" """Return the color mode of the light."""
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
if self._device.supports_rgbw: 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 ( if (
self._device.supports_color_temperature self._device.supports_color_temperature
or self._device.supports_tunable_white or self._device.supports_tunable_white
): ):
flags |= SUPPORT_COLOR_TEMP return COLOR_MODE_COLOR_TEMP
return flags 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: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on.""" """Turn the light on."""
brightness = kwargs.get(ATTR_BRIGHTNESS, self.brightness) # ignore arguments if not supported to fall back to set_on()
hs_color = kwargs.get(ATTR_HS_COLOR, self.hs_color) brightness = (
white_value = kwargs.get(ATTR_WHITE_VALUE, self.white_value) kwargs.get(ATTR_BRIGHTNESS)
mireds = kwargs.get(ATTR_COLOR_TEMP, self.color_temp) 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 if (
update_color = ATTR_HS_COLOR in kwargs not self.is_on
update_white_value = ATTR_WHITE_VALUE in kwargs and brightness is None
update_color_temp = ATTR_COLOR_TEMP in kwargs and mireds is None
and rgb is None
# avoid conflicting changes and weird effects and rgbw is None
if not (
self.is_on
or update_brightness
or update_color
or update_white_value
or update_color_temp
): ):
await self._device.set_on() await self._device.set_on()
return
if self._device.supports_brightness and ( async def set_color(
update_brightness and not update_color rgb: tuple[int, int, int], white: int | None, brightness: int | None
): ) -> None:
# if we don't need to update the color, try updating brightness """Set color of light. Normalize colors for brightness when not writable."""
# directly if supported; don't do it if color also has to be if brightness:
# changed, as RGB color implicitly sets the brightness as well if self._device.brightness.writable:
await self._device.set_brightness(brightness) await self._device.set_color(rgb, white)
elif (self._device.supports_rgbw or self._device.supports_color) and ( await self._device.set_brightness(brightness)
update_brightness or update_color or update_white_value return
): rgb = cast(
# change RGB color, white value (if supported), and brightness tuple[int, int, int],
# if brightness or hs_color was not yet set use the default value tuple(color * brightness // 255 for color in rgb),
# to calculate RGB from as a fallback )
if brightness is None: white = white * brightness // 255 if white is not None else None
brightness = DEFAULT_BRIGHTNESS await self._device.set_color(rgb, white)
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)
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 = int(color_util.color_temperature_mired_to_kelvin(mireds))
kelvin = min(self._max_kelvin, max(self._min_kelvin, kelvin)) 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) 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: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off.""" """Turn the light off."""
await self._device.set_off() await self._device.set_off()