1 """Support for KNX/IP lights."""
3 from __future__
import annotations
5 from typing
import Any, cast
7 from propcache
import cached_property
9 from xknx.devices.light
import ColorTemperatureType, Light
as XknxLight, XYYColor
11 from homeassistant
import config_entries
14 ATTR_COLOR_TEMP_KELVIN,
26 async_get_current_platform,
31 from .
import KNXModule
32 from .const
import CONF_SYNC_STATE, DOMAIN, KNX_ADDRESS, KNX_MODULE_KEY, ColorTempModes
33 from .entity
import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
34 from .schema
import LightSchema
35 from .storage.const
import (
40 CONF_GA_BLUE_BRIGHTNESS,
45 CONF_GA_GREEN_BRIGHTNESS,
49 CONF_GA_RED_BRIGHTNESS,
54 CONF_GA_WHITE_BRIGHTNESS,
58 from .storage.entity_store_schema
import LightColorMode
64 async_add_entities: AddEntitiesCallback,
66 """Set up light(s) for KNX platform."""
67 knx_module = hass.data[KNX_MODULE_KEY]
69 knx_module.config_store.add_platform(
70 platform=Platform.LIGHT,
72 knx_module=knx_module,
73 entity_platform=platform,
74 entity_class=KnxUiLight,
78 entities: list[KnxYamlEntity | KnxUiEntity] = []
79 if yaml_platform_config := knx_module.config_yaml.get(Platform.LIGHT):
82 for entity_config
in yaml_platform_config
84 if ui_config := knx_module.config_store.data[
"entities"].
get(Platform.LIGHT):
87 for unique_id, config
in ui_config.items()
94 """Return a KNX Light device to be used within XKNX."""
96 def individual_color_addresses(color: str, feature: str) -> Any |
None:
97 """Load individual color address list from configuration structure."""
99 LightSchema.CONF_INDIVIDUAL_COLORS
not in config
100 or color
not in config[LightSchema.CONF_INDIVIDUAL_COLORS]
103 return config[LightSchema.CONF_INDIVIDUAL_COLORS][color].
get(feature)
105 group_address_tunable_white =
None
106 group_address_tunable_white_state =
None
107 group_address_color_temp =
None
108 group_address_color_temp_state =
None
109 color_temperature_type = ColorTemperatureType.UINT_2_BYTE
110 if config[LightSchema.CONF_COLOR_TEMP_MODE] == ColorTempModes.RELATIVE:
111 group_address_tunable_white = config.get(LightSchema.CONF_COLOR_TEMP_ADDRESS)
112 group_address_tunable_white_state = config.get(
113 LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS
117 group_address_color_temp = config.get(LightSchema.CONF_COLOR_TEMP_ADDRESS)
118 group_address_color_temp_state = config.get(
119 LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS
121 if config[LightSchema.CONF_COLOR_TEMP_MODE] == ColorTempModes.ABSOLUTE_FLOAT:
122 color_temperature_type = ColorTemperatureType.FLOAT_2_BYTE
126 name=config[CONF_NAME],
127 group_address_switch=config.get(KNX_ADDRESS),
128 group_address_switch_state=config.get(LightSchema.CONF_STATE_ADDRESS),
129 group_address_brightness=config.get(LightSchema.CONF_BRIGHTNESS_ADDRESS),
130 group_address_brightness_state=config.get(
131 LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS
133 group_address_color=config.get(LightSchema.CONF_COLOR_ADDRESS),
134 group_address_color_state=config.get(LightSchema.CONF_COLOR_STATE_ADDRESS),
135 group_address_rgbw=config.get(LightSchema.CONF_RGBW_ADDRESS),
136 group_address_rgbw_state=config.get(LightSchema.CONF_RGBW_STATE_ADDRESS),
137 group_address_hue=config.get(LightSchema.CONF_HUE_ADDRESS),
138 group_address_hue_state=config.get(LightSchema.CONF_HUE_STATE_ADDRESS),
139 group_address_saturation=config.get(LightSchema.CONF_SATURATION_ADDRESS),
140 group_address_saturation_state=config.get(
141 LightSchema.CONF_SATURATION_STATE_ADDRESS
143 group_address_xyy_color=config.get(LightSchema.CONF_XYY_ADDRESS),
144 group_address_xyy_color_state=config.get(LightSchema.CONF_XYY_STATE_ADDRESS),
145 group_address_tunable_white=group_address_tunable_white,
146 group_address_tunable_white_state=group_address_tunable_white_state,
147 group_address_color_temperature=group_address_color_temp,
148 group_address_color_temperature_state=group_address_color_temp_state,
149 group_address_switch_red=individual_color_addresses(
150 LightSchema.CONF_RED, KNX_ADDRESS
152 group_address_switch_red_state=individual_color_addresses(
153 LightSchema.CONF_RED, LightSchema.CONF_STATE_ADDRESS
155 group_address_brightness_red=individual_color_addresses(
156 LightSchema.CONF_RED, LightSchema.CONF_BRIGHTNESS_ADDRESS
158 group_address_brightness_red_state=individual_color_addresses(
159 LightSchema.CONF_RED, LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS
161 group_address_switch_green=individual_color_addresses(
162 LightSchema.CONF_GREEN, KNX_ADDRESS
164 group_address_switch_green_state=individual_color_addresses(
165 LightSchema.CONF_GREEN, LightSchema.CONF_STATE_ADDRESS
167 group_address_brightness_green=individual_color_addresses(
168 LightSchema.CONF_GREEN, LightSchema.CONF_BRIGHTNESS_ADDRESS
170 group_address_brightness_green_state=individual_color_addresses(
171 LightSchema.CONF_GREEN, LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS
173 group_address_switch_blue=individual_color_addresses(
174 LightSchema.CONF_BLUE, KNX_ADDRESS
176 group_address_switch_blue_state=individual_color_addresses(
177 LightSchema.CONF_BLUE, LightSchema.CONF_STATE_ADDRESS
179 group_address_brightness_blue=individual_color_addresses(
180 LightSchema.CONF_BLUE, LightSchema.CONF_BRIGHTNESS_ADDRESS
182 group_address_brightness_blue_state=individual_color_addresses(
183 LightSchema.CONF_BLUE, LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS
185 group_address_switch_white=individual_color_addresses(
186 LightSchema.CONF_WHITE, KNX_ADDRESS
188 group_address_switch_white_state=individual_color_addresses(
189 LightSchema.CONF_WHITE, LightSchema.CONF_STATE_ADDRESS
191 group_address_brightness_white=individual_color_addresses(
192 LightSchema.CONF_WHITE, LightSchema.CONF_BRIGHTNESS_ADDRESS
194 group_address_brightness_white_state=individual_color_addresses(
195 LightSchema.CONF_WHITE, LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS
197 color_temperature_type=color_temperature_type,
198 min_kelvin=config[LightSchema.CONF_MIN_KELVIN],
199 max_kelvin=config[LightSchema.CONF_MAX_KELVIN],
204 """Return a KNX Light device to be used within XKNX."""
206 def get_write(key: str) -> str |
None:
207 """Get the write group address."""
208 return knx_config[key][CONF_GA_WRITE]
if key
in knx_config
else None
210 def get_state(key: str) -> list[Any] |
None:
211 """Get the state group address."""
213 [knx_config[key][CONF_GA_STATE], *knx_config[key][CONF_GA_PASSIVE]]
218 def get_dpt(key: str) -> str |
None:
220 return knx_config[key].
get(CONF_DPT)
if key
in knx_config
else None
222 group_address_tunable_white =
None
223 group_address_tunable_white_state =
None
224 group_address_color_temp =
None
225 group_address_color_temp_state =
None
226 color_temperature_type = ColorTemperatureType.UINT_2_BYTE
227 if ga_color_temp := knx_config.get(CONF_GA_COLOR_TEMP):
228 if ga_color_temp[CONF_DPT] == ColorTempModes.RELATIVE.value:
229 group_address_tunable_white = ga_color_temp[CONF_GA_WRITE]
230 group_address_tunable_white_state = [
231 ga_color_temp[CONF_GA_STATE],
232 *ga_color_temp[CONF_GA_PASSIVE],
236 group_address_color_temp = ga_color_temp[CONF_GA_WRITE]
237 group_address_color_temp_state = [
238 ga_color_temp[CONF_GA_STATE],
239 *ga_color_temp[CONF_GA_PASSIVE],
241 if ga_color_temp[CONF_DPT] == ColorTempModes.ABSOLUTE_FLOAT.value:
242 color_temperature_type = ColorTemperatureType.FLOAT_2_BYTE
244 _color_dpt = get_dpt(CONF_GA_COLOR)
248 group_address_switch=get_write(CONF_GA_SWITCH),
249 group_address_switch_state=
get_state(CONF_GA_SWITCH),
250 group_address_brightness=get_write(CONF_GA_BRIGHTNESS),
251 group_address_brightness_state=
get_state(CONF_GA_BRIGHTNESS),
252 group_address_color=get_write(CONF_GA_COLOR)
253 if _color_dpt == LightColorMode.RGB
255 group_address_color_state=
get_state(CONF_GA_COLOR)
256 if _color_dpt == LightColorMode.RGB
258 group_address_rgbw=get_write(CONF_GA_COLOR)
259 if _color_dpt == LightColorMode.RGBW
261 group_address_rgbw_state=
get_state(CONF_GA_COLOR)
262 if _color_dpt == LightColorMode.RGBW
264 group_address_hue=get_write(CONF_GA_HUE),
265 group_address_hue_state=
get_state(CONF_GA_HUE),
266 group_address_saturation=get_write(CONF_GA_SATURATION),
267 group_address_saturation_state=
get_state(CONF_GA_SATURATION),
268 group_address_xyy_color=get_write(CONF_GA_COLOR)
269 if _color_dpt == LightColorMode.XYY
271 group_address_xyy_color_state=get_write(CONF_GA_COLOR)
272 if _color_dpt == LightColorMode.XYY
274 group_address_tunable_white=group_address_tunable_white,
275 group_address_tunable_white_state=group_address_tunable_white_state,
276 group_address_color_temperature=group_address_color_temp,
277 group_address_color_temperature_state=group_address_color_temp_state,
278 group_address_switch_red=get_write(CONF_GA_RED_SWITCH),
279 group_address_switch_red_state=
get_state(CONF_GA_RED_SWITCH),
280 group_address_brightness_red=get_write(CONF_GA_RED_BRIGHTNESS),
281 group_address_brightness_red_state=
get_state(CONF_GA_RED_BRIGHTNESS),
282 group_address_switch_green=get_write(CONF_GA_GREEN_SWITCH),
283 group_address_switch_green_state=
get_state(CONF_GA_GREEN_SWITCH),
284 group_address_brightness_green=get_write(CONF_GA_GREEN_BRIGHTNESS),
285 group_address_brightness_green_state=
get_state(CONF_GA_GREEN_BRIGHTNESS),
286 group_address_switch_blue=get_write(CONF_GA_BLUE_SWITCH),
287 group_address_switch_blue_state=
get_state(CONF_GA_BLUE_SWITCH),
288 group_address_brightness_blue=get_write(CONF_GA_BLUE_BRIGHTNESS),
289 group_address_brightness_blue_state=
get_state(CONF_GA_BLUE_BRIGHTNESS),
290 group_address_switch_white=get_write(CONF_GA_WHITE_SWITCH),
291 group_address_switch_white_state=
get_state(CONF_GA_WHITE_SWITCH),
292 group_address_brightness_white=get_write(CONF_GA_WHITE_BRIGHTNESS),
293 group_address_brightness_white_state=
get_state(CONF_GA_WHITE_BRIGHTNESS),
294 color_temperature_type=color_temperature_type,
295 min_kelvin=knx_config[CONF_COLOR_TEMP_MIN],
296 max_kelvin=knx_config[CONF_COLOR_TEMP_MAX],
297 sync_state=knx_config[CONF_SYNC_STATE],
302 """Representation of a KNX light."""
304 _attr_max_color_temp_kelvin: int
305 _attr_min_color_temp_kelvin: int
310 """Return true if light is on."""
311 return bool(self._device.state)
315 """Return the brightness of this light between 0..255."""
316 if self._device.supports_brightness:
317 return self._device.current_brightness
318 if self._device.current_xyy_color
is not None:
319 return self._device.current_xyy_color.brightness
320 if self._device.supports_color
or self._device.supports_rgbw:
321 rgb, white = self._device.current_color
326 return max(*rgb, white)
331 """Return the rgb color value [int, int, int]."""
332 if self._device.supports_color:
333 rgb, _ = self._device.current_color
335 if not self._device.supports_brightness:
338 tuple[int, int, int], color_util.match_max_scale((255,), rgb)
345 """Return the rgbw color value [int, int, int, int]."""
346 if self._device.supports_rgbw:
347 rgb, white = self._device.current_color
348 if rgb
is not None and white
is not None:
349 if not self._device.supports_brightness:
352 tuple[int, int, int, int],
353 color_util.match_max_scale((255,), (*rgb, white)),
360 """Return the hue and saturation color value [float, float]."""
363 return self._device.current_hs_color
367 """Return the xy color value [float, float]."""
368 if self._device.current_xyy_color
is not None:
369 return self._device.current_xyy_color.color
374 """Return the color temperature in Kelvin."""
375 if self._device.supports_color_temperature:
376 if kelvin := self._device.current_color_temperature:
378 if self._device.supports_tunable_white:
379 relative_ct = self._device.current_tunable_white
380 if relative_ct
is not None:
382 self._attr_min_color_temp_kelvin
386 self._attr_max_color_temp_kelvin
387 - self._attr_min_color_temp_kelvin
395 """Get supported color modes."""
398 self._device.supports_color_temperature
399 or self._device.supports_tunable_white
401 color_mode.add(ColorMode.COLOR_TEMP)
402 if self._device.supports_xyy_color:
403 color_mode.add(ColorMode.XY)
404 if self._device.supports_rgbw:
405 color_mode.add(ColorMode.RGBW)
406 elif self._device.supports_color:
408 color_mode.add(ColorMode.RGB)
409 if self._device.supports_hs_color:
410 color_mode.add(ColorMode.HS)
413 if self._device.supports_brightness:
414 color_mode.add(ColorMode.BRIGHTNESS)
416 color_mode.add(ColorMode.ONOFF)
420 """Turn the light on."""
421 brightness = kwargs.get(ATTR_BRIGHTNESS)
424 if color_temp := kwargs.get(ATTR_COLOR_TEMP_KELVIN):
426 if rgb := kwargs.get(ATTR_RGB_COLOR):
428 if rgbw := kwargs.get(ATTR_RGBW_COLOR):
430 if hs_color := kwargs.get(ATTR_HS_COLOR):
432 if xy_color := kwargs.get(ATTR_XY_COLOR):
437 and brightness
is None
438 and color_temp
is None
444 await self._device.set_on()
448 rgb: tuple[int, int, int], white: int |
None, brightness: int |
None
450 """Set color of light. Normalize colors for brightness when not writable."""
451 if self._device.brightness.writable:
453 await self._device.set_color(rgb, white)
455 await self._device.set_brightness(brightness)
458 if brightness
is None:
462 tuple[int, int, int],
463 tuple(color * brightness // 255
for color
in rgb),
465 white = white * brightness // 255
if white
is not None else None
466 await self._device.set_color(rgb, white)
470 await set_color(rgbw[:3], rgbw[3], brightness)
473 await set_color(rgb,
None, brightness)
476 if color_temp
is not None:
478 self._attr_max_color_temp_kelvin,
479 max(self._attr_min_color_temp_kelvin, color_temp),
481 if self._device.supports_color_temperature:
482 await self._device.set_color_temperature(color_temp)
483 elif self._device.supports_tunable_white:
486 * (color_temp - self._attr_min_color_temp_kelvin)
488 self._attr_max_color_temp_kelvin
489 - self._attr_min_color_temp_kelvin
492 await self._device.set_tunable_white(relative_ct)
494 if xy_color
is not None:
495 await self._device.set_xyy_color(
496 XYYColor(color=xy_color, brightness=brightness)
500 if hs_color
is not None:
502 hue = round(hs_color[0])
503 sat = round(hs_color[1])
504 await self._device.set_hs_color((hue, sat))
506 if brightness
is not None:
508 if self._device.brightness.writable:
509 await self._device.set_brightness(brightness)
513 await self._device.set_xyy_color(XYYColor(brightness=brightness))
518 if not _rgbw
or not any(_rgbw):
519 _rgbw = (0, 0, 0, 255)
520 await set_color(_rgbw[:3], _rgbw[3], brightness)
524 if not _rgb
or not any(_rgb):
525 _rgb = (255, 255, 255)
526 await set_color(_rgb,
None, brightness)
530 """Turn the light off."""
531 await self._device.set_off()
535 """Representation of a KNX light."""
539 def __init__(self, knx_module: KNXModule, config: ConfigType) ->
None:
540 """Initialize of KNX light."""
542 knx_module=knx_module,
546 self._attr_max_color_temp_kelvin: int = config[LightSchema.CONF_MAX_KELVIN]
547 self._attr_min_color_temp_kelvin: int = config[LightSchema.CONF_MIN_KELVIN]
552 """Return unique id for this device."""
553 if self.
_device_device.switch.group_address
is not None:
554 return f
"{self._device.switch.group_address}"
556 f
"{self._device.red.brightness.group_address}_"
557 f
"{self._device.green.brightness.group_address}_"
558 f
"{self._device.blue.brightness.group_address}"
563 """Representation of a KNX light."""
568 self, knx_module: KNXModule, unique_id: str, config: ConfigType
570 """Initialize of KNX light."""
572 knx_module=knx_module,
574 entity_config=config[CONF_ENTITY],
577 knx_module.xknx, config[DOMAIN], config[CONF_ENTITY][CONF_NAME]
580 self._attr_max_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MAX]
581 self._attr_min_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MIN]
None __init__(self, KNXModule knx_module, str unique_id, ConfigType config)
None __init__(self, KNXModule knx_module, ConfigType config)
str _device_unique_id(self)
tuple[float, float]|None xy_color(self)
tuple[int, int, int]|None rgb_color(self)
int|None brightness(self)
None async_turn_on(self, **Any kwargs)
tuple[int, int, int, int]|None rgbw_color(self)
set[ColorMode] supported_color_modes(self)
int|None color_temp_kelvin(self)
None async_turn_off(self, **Any kwargs)
tuple[float, float]|None hs_color(self)
int|None brightness(self)
tuple[int, int, int]|None rgb_color(self)
tuple[int, int, int, int]|None rgbw_color(self)
set[ColorMode]|set[str]|None supported_color_modes(self)
web.Response get(self, web.Request request, str config_key)
str|float get_state(dict[str, float] data, str key)
None async_setup_entry(HomeAssistant hass, config_entries.ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
XknxLight _create_yaml_light(XKNX xknx, ConfigType config)
XknxLight _create_ui_light(XKNX xknx, ConfigType knx_config, str name)