mirror of
https://github.com/home-assistant/core.git
synced 2025-07-08 13:57:10 +00:00
Support KNX lights with multiple color modes (#130842)
This commit is contained in:
parent
c154ac26eb
commit
2f1c1d66cb
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
|
from propcache import cached_property
|
||||||
from xknx import XKNX
|
from xknx import XKNX
|
||||||
from xknx.devices.light import ColorTemperatureType, Light as XknxLight, XYYColor
|
from xknx.devices.light import ColorTemperatureType, Light as XknxLight, XYYColor
|
||||||
|
|
||||||
@ -389,39 +390,47 @@ class _KnxLight(LightEntity):
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def color_mode(self) -> ColorMode:
|
def supported_color_modes(self) -> set[ColorMode]:
|
||||||
"""Return the color mode of the light."""
|
"""Get supported color modes."""
|
||||||
if self._device.supports_xyy_color:
|
color_mode = set()
|
||||||
return ColorMode.XY
|
|
||||||
if self._device.supports_hs_color:
|
|
||||||
return ColorMode.HS
|
|
||||||
if self._device.supports_rgbw:
|
|
||||||
return ColorMode.RGBW
|
|
||||||
if self._device.supports_color:
|
|
||||||
return ColorMode.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
|
||||||
):
|
):
|
||||||
return ColorMode.COLOR_TEMP
|
color_mode.add(ColorMode.COLOR_TEMP)
|
||||||
|
if self._device.supports_xyy_color:
|
||||||
|
color_mode.add(ColorMode.XY)
|
||||||
|
if self._device.supports_rgbw:
|
||||||
|
color_mode.add(ColorMode.RGBW)
|
||||||
|
elif self._device.supports_color:
|
||||||
|
# one of RGB or RGBW so individual color configurations work properly
|
||||||
|
color_mode.add(ColorMode.RGB)
|
||||||
|
if self._device.supports_hs_color:
|
||||||
|
color_mode.add(ColorMode.HS)
|
||||||
|
if not color_mode:
|
||||||
|
# brightness or on/off must be the only supported mode
|
||||||
if self._device.supports_brightness:
|
if self._device.supports_brightness:
|
||||||
return ColorMode.BRIGHTNESS
|
color_mode.add(ColorMode.BRIGHTNESS)
|
||||||
return ColorMode.ONOFF
|
else:
|
||||||
|
color_mode.add(ColorMode.ONOFF)
|
||||||
@property
|
return color_mode
|
||||||
def supported_color_modes(self) -> set[ColorMode]:
|
|
||||||
"""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)
|
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||||
color_temp = kwargs.get(ATTR_COLOR_TEMP_KELVIN)
|
# LightEntity color translation will ensure that only attributes of supported
|
||||||
rgb = kwargs.get(ATTR_RGB_COLOR)
|
# color modes are passed to this method - so we can't set unsupported mode here
|
||||||
rgbw = kwargs.get(ATTR_RGBW_COLOR)
|
if color_temp := kwargs.get(ATTR_COLOR_TEMP_KELVIN):
|
||||||
hs_color = kwargs.get(ATTR_HS_COLOR)
|
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||||
xy_color = kwargs.get(ATTR_XY_COLOR)
|
if rgb := kwargs.get(ATTR_RGB_COLOR):
|
||||||
|
self._attr_color_mode = ColorMode.RGB
|
||||||
|
if rgbw := kwargs.get(ATTR_RGBW_COLOR):
|
||||||
|
self._attr_color_mode = ColorMode.RGBW
|
||||||
|
if hs_color := kwargs.get(ATTR_HS_COLOR):
|
||||||
|
self._attr_color_mode = ColorMode.HS
|
||||||
|
if xy_color := kwargs.get(ATTR_XY_COLOR):
|
||||||
|
self._attr_color_mode = ColorMode.XY
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not self.is_on
|
not self.is_on
|
||||||
@ -500,17 +509,17 @@ class _KnxLight(LightEntity):
|
|||||||
await self._device.set_brightness(brightness)
|
await self._device.set_brightness(brightness)
|
||||||
return
|
return
|
||||||
# brightness without color in kwargs; set via color
|
# brightness without color in kwargs; set via color
|
||||||
if self.color_mode == ColorMode.XY:
|
if self._attr_color_mode == ColorMode.XY:
|
||||||
await self._device.set_xyy_color(XYYColor(brightness=brightness))
|
await self._device.set_xyy_color(XYYColor(brightness=brightness))
|
||||||
return
|
return
|
||||||
# default to white if color not known for RGB(W)
|
# default to white if color not known for RGB(W)
|
||||||
if self.color_mode == ColorMode.RGBW:
|
if self._attr_color_mode == ColorMode.RGBW:
|
||||||
_rgbw = self.rgbw_color
|
_rgbw = self.rgbw_color
|
||||||
if not _rgbw or not any(_rgbw):
|
if not _rgbw or not any(_rgbw):
|
||||||
_rgbw = (0, 0, 0, 255)
|
_rgbw = (0, 0, 0, 255)
|
||||||
await set_color(_rgbw[:3], _rgbw[3], brightness)
|
await set_color(_rgbw[:3], _rgbw[3], brightness)
|
||||||
return
|
return
|
||||||
if self.color_mode == ColorMode.RGB:
|
if self._attr_color_mode == ColorMode.RGB:
|
||||||
_rgb = self.rgb_color
|
_rgb = self.rgb_color
|
||||||
if not _rgb or not any(_rgb):
|
if not _rgb or not any(_rgb):
|
||||||
_rgb = (255, 255, 255)
|
_rgb = (255, 255, 255)
|
||||||
@ -533,6 +542,7 @@ class KnxYamlLight(_KnxLight, KnxYamlEntity):
|
|||||||
knx_module=knx_module,
|
knx_module=knx_module,
|
||||||
device=_create_yaml_light(knx_module.xknx, config),
|
device=_create_yaml_light(knx_module.xknx, config),
|
||||||
)
|
)
|
||||||
|
self._attr_color_mode = next(iter(self.supported_color_modes))
|
||||||
self._attr_max_color_temp_kelvin: int = config[LightSchema.CONF_MAX_KELVIN]
|
self._attr_max_color_temp_kelvin: int = config[LightSchema.CONF_MAX_KELVIN]
|
||||||
self._attr_min_color_temp_kelvin: int = config[LightSchema.CONF_MIN_KELVIN]
|
self._attr_min_color_temp_kelvin: int = config[LightSchema.CONF_MIN_KELVIN]
|
||||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||||
@ -566,5 +576,6 @@ class KnxUiLight(_KnxLight, KnxUiEntity):
|
|||||||
self._device = _create_ui_light(
|
self._device = _create_ui_light(
|
||||||
knx_module.xknx, config[DOMAIN], config[CONF_ENTITY][CONF_NAME]
|
knx_module.xknx, config[DOMAIN], config[CONF_ENTITY][CONF_NAME]
|
||||||
)
|
)
|
||||||
|
self._attr_color_mode = next(iter(self.supported_color_modes))
|
||||||
self._attr_max_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MAX]
|
self._attr_max_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MAX]
|
||||||
self._attr_min_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MIN]
|
self._attr_min_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MIN]
|
||||||
|
@ -41,7 +41,11 @@ async def test_light_simple(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
knx.assert_state("light.test", STATE_OFF)
|
knx.assert_state(
|
||||||
|
"light.test",
|
||||||
|
STATE_OFF,
|
||||||
|
supported_color_modes=[ColorMode.ONOFF],
|
||||||
|
)
|
||||||
# turn on light
|
# turn on light
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"light",
|
"light",
|
||||||
@ -110,6 +114,7 @@ async def test_light_brightness(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=80,
|
brightness=80,
|
||||||
|
supported_color_modes=[ColorMode.BRIGHTNESS],
|
||||||
color_mode=ColorMode.BRIGHTNESS,
|
color_mode=ColorMode.BRIGHTNESS,
|
||||||
)
|
)
|
||||||
# receive brightness changes from KNX
|
# receive brightness changes from KNX
|
||||||
@ -165,6 +170,7 @@ async def test_light_color_temp_absolute(hass: HomeAssistant, knx: KNXTestKit) -
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255,
|
brightness=255,
|
||||||
|
supported_color_modes=[ColorMode.COLOR_TEMP],
|
||||||
color_mode=ColorMode.COLOR_TEMP,
|
color_mode=ColorMode.COLOR_TEMP,
|
||||||
color_temp=370,
|
color_temp=370,
|
||||||
color_temp_kelvin=2700,
|
color_temp_kelvin=2700,
|
||||||
@ -227,6 +233,7 @@ async def test_light_color_temp_relative(hass: HomeAssistant, knx: KNXTestKit) -
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255,
|
brightness=255,
|
||||||
|
supported_color_modes=[ColorMode.COLOR_TEMP],
|
||||||
color_mode=ColorMode.COLOR_TEMP,
|
color_mode=ColorMode.COLOR_TEMP,
|
||||||
color_temp=250,
|
color_temp=250,
|
||||||
color_temp_kelvin=4000,
|
color_temp_kelvin=4000,
|
||||||
@ -300,6 +307,7 @@ async def test_light_hs_color(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255,
|
brightness=255,
|
||||||
|
supported_color_modes=[ColorMode.HS],
|
||||||
color_mode=ColorMode.HS,
|
color_mode=ColorMode.HS,
|
||||||
hs_color=(360, 100),
|
hs_color=(360, 100),
|
||||||
)
|
)
|
||||||
@ -375,6 +383,7 @@ async def test_light_xyy_color(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=204,
|
brightness=204,
|
||||||
|
supported_color_modes=[ColorMode.XY],
|
||||||
color_mode=ColorMode.XY,
|
color_mode=ColorMode.XY,
|
||||||
xy_color=(0.8, 0.8),
|
xy_color=(0.8, 0.8),
|
||||||
)
|
)
|
||||||
@ -457,6 +466,7 @@ async def test_light_xyy_color_with_brightness(
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255, # brightness form xyy_color ignored when extra brightness GA is used
|
brightness=255, # brightness form xyy_color ignored when extra brightness GA is used
|
||||||
|
supported_color_modes=[ColorMode.XY],
|
||||||
color_mode=ColorMode.XY,
|
color_mode=ColorMode.XY,
|
||||||
xy_color=(0.8, 0.8),
|
xy_color=(0.8, 0.8),
|
||||||
)
|
)
|
||||||
@ -543,6 +553,7 @@ async def test_light_rgb_individual(hass: HomeAssistant, knx: KNXTestKit) -> Non
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255,
|
brightness=255,
|
||||||
|
supported_color_modes=[ColorMode.RGB],
|
||||||
color_mode=ColorMode.RGB,
|
color_mode=ColorMode.RGB,
|
||||||
rgb_color=(255, 255, 255),
|
rgb_color=(255, 255, 255),
|
||||||
)
|
)
|
||||||
@ -699,6 +710,7 @@ async def test_light_rgbw_individual(
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255,
|
brightness=255,
|
||||||
|
supported_color_modes=[ColorMode.RGBW],
|
||||||
color_mode=ColorMode.RGBW,
|
color_mode=ColorMode.RGBW,
|
||||||
rgbw_color=(0, 0, 0, 255),
|
rgbw_color=(0, 0, 0, 255),
|
||||||
)
|
)
|
||||||
@ -853,6 +865,7 @@ async def test_light_rgb(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255,
|
brightness=255,
|
||||||
|
supported_color_modes=[ColorMode.RGB],
|
||||||
color_mode=ColorMode.RGB,
|
color_mode=ColorMode.RGB,
|
||||||
rgb_color=(255, 255, 255),
|
rgb_color=(255, 255, 255),
|
||||||
)
|
)
|
||||||
@ -961,6 +974,7 @@ async def test_light_rgbw(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255,
|
brightness=255,
|
||||||
|
supported_color_modes=[ColorMode.RGBW],
|
||||||
color_mode=ColorMode.RGBW,
|
color_mode=ColorMode.RGBW,
|
||||||
rgbw_color=(255, 101, 102, 103),
|
rgbw_color=(255, 101, 102, 103),
|
||||||
)
|
)
|
||||||
@ -1078,6 +1092,7 @@ async def test_light_rgbw_brightness(hass: HomeAssistant, knx: KNXTestKit) -> No
|
|||||||
"light.test",
|
"light.test",
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
brightness=255,
|
brightness=255,
|
||||||
|
supported_color_modes=[ColorMode.RGBW],
|
||||||
color_mode=ColorMode.RGBW,
|
color_mode=ColorMode.RGBW,
|
||||||
rgbw_color=(255, 101, 102, 103),
|
rgbw_color=(255, 101, 102, 103),
|
||||||
)
|
)
|
||||||
@ -1174,8 +1189,12 @@ async def test_light_ui_create(
|
|||||||
# created entity sends read-request to KNX bus
|
# created entity sends read-request to KNX bus
|
||||||
await knx.assert_read("2/2/2")
|
await knx.assert_read("2/2/2")
|
||||||
await knx.receive_response("2/2/2", True)
|
await knx.receive_response("2/2/2", True)
|
||||||
state = hass.states.get("light.test")
|
knx.assert_state(
|
||||||
assert state.state is STATE_ON
|
"light.test",
|
||||||
|
STATE_ON,
|
||||||
|
supported_color_modes=[ColorMode.ONOFF],
|
||||||
|
color_mode=ColorMode.ONOFF,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -1216,9 +1235,103 @@ async def test_light_ui_color_temp(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await knx.assert_write("3/3/3", raw_ct)
|
await knx.assert_write("3/3/3", raw_ct)
|
||||||
state = hass.states.get("light.test")
|
knx.assert_state(
|
||||||
assert state.state is STATE_ON
|
"light.test",
|
||||||
assert state.attributes[ATTR_COLOR_TEMP_KELVIN] == pytest.approx(4200, abs=1)
|
STATE_ON,
|
||||||
|
supported_color_modes=[ColorMode.COLOR_TEMP],
|
||||||
|
color_mode=ColorMode.COLOR_TEMP,
|
||||||
|
color_temp_kelvin=pytest.approx(4200, abs=1),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_light_ui_multi_mode(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
knx: KNXTestKit,
|
||||||
|
create_ui_entity: KnxEntityGenerator,
|
||||||
|
) -> None:
|
||||||
|
"""Test creating a light with multiple color modes."""
|
||||||
|
await knx.setup_integration({})
|
||||||
|
await create_ui_entity(
|
||||||
|
platform=Platform.LIGHT,
|
||||||
|
entity_data={"name": "test"},
|
||||||
|
knx_data={
|
||||||
|
"color_temp_min": 2700,
|
||||||
|
"color_temp_max": 6000,
|
||||||
|
"_light_color_mode_schema": "default",
|
||||||
|
"ga_switch": {
|
||||||
|
"write": "1/1/1",
|
||||||
|
"passive": [],
|
||||||
|
"state": "2/2/2",
|
||||||
|
},
|
||||||
|
"sync_state": True,
|
||||||
|
"ga_brightness": {
|
||||||
|
"write": "0/6/0",
|
||||||
|
"state": "0/6/1",
|
||||||
|
"passive": [],
|
||||||
|
},
|
||||||
|
"ga_color_temp": {
|
||||||
|
"write": "0/6/2",
|
||||||
|
"dpt": "7.600",
|
||||||
|
"state": "0/6/3",
|
||||||
|
"passive": [],
|
||||||
|
},
|
||||||
|
"ga_color": {
|
||||||
|
"write": "0/6/4",
|
||||||
|
"dpt": "251.600",
|
||||||
|
"state": "0/6/5",
|
||||||
|
"passive": [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await knx.assert_read("2/2/2", True)
|
||||||
|
await knx.assert_read("0/6/1", (0xFF,))
|
||||||
|
await knx.assert_read("0/6/5", (0xFF, 0x65, 0x66, 0x67, 0x00, 0x0F))
|
||||||
|
await knx.assert_read("0/6/3", (0x12, 0x34))
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": "light.test",
|
||||||
|
ATTR_COLOR_NAME: "hotpink",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await knx.assert_write("0/6/4", (255, 0, 128, 178, 0, 15))
|
||||||
|
knx.assert_state(
|
||||||
|
"light.test",
|
||||||
|
STATE_ON,
|
||||||
|
brightness=255,
|
||||||
|
color_temp_kelvin=None,
|
||||||
|
rgbw_color=(255, 0, 128, 178),
|
||||||
|
supported_color_modes=[
|
||||||
|
ColorMode.COLOR_TEMP,
|
||||||
|
ColorMode.RGBW,
|
||||||
|
],
|
||||||
|
color_mode=ColorMode.RGBW,
|
||||||
|
)
|
||||||
|
await hass.services.async_call(
|
||||||
|
"light",
|
||||||
|
"turn_on",
|
||||||
|
{
|
||||||
|
"entity_id": "light.test",
|
||||||
|
ATTR_COLOR_TEMP_KELVIN: 4200,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await knx.assert_write("0/6/2", (0x10, 0x68))
|
||||||
|
knx.assert_state(
|
||||||
|
"light.test",
|
||||||
|
STATE_ON,
|
||||||
|
brightness=255,
|
||||||
|
color_temp_kelvin=4200,
|
||||||
|
rgbw_color=None,
|
||||||
|
supported_color_modes=[
|
||||||
|
ColorMode.COLOR_TEMP,
|
||||||
|
ColorMode.RGBW,
|
||||||
|
],
|
||||||
|
color_mode=ColorMode.COLOR_TEMP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_light_ui_load(
|
async def test_light_ui_load(
|
||||||
@ -1234,8 +1347,12 @@ async def test_light_ui_load(
|
|||||||
# unrelated switch in config store
|
# unrelated switch in config store
|
||||||
await knx.assert_read("1/0/45", response=True, ignore_order=True)
|
await knx.assert_read("1/0/45", response=True, ignore_order=True)
|
||||||
|
|
||||||
state = hass.states.get("light.test")
|
knx.assert_state(
|
||||||
assert state.state is STATE_ON
|
"light.test",
|
||||||
|
STATE_ON,
|
||||||
|
supported_color_modes=[ColorMode.ONOFF],
|
||||||
|
color_mode=ColorMode.ONOFF,
|
||||||
|
)
|
||||||
|
|
||||||
entity = entity_registry.async_get("light.test")
|
entity = entity_registry.async_get("light.test")
|
||||||
assert entity.entity_category is EntityCategory.CONFIG
|
assert entity.entity_category is EntityCategory.CONFIG
|
||||||
|
Loading…
x
Reference in New Issue
Block a user