1 """Support for ESPHome lights."""
3 from __future__
import annotations
5 from functools
import lru_cache, partial
6 from typing
import TYPE_CHECKING, Any, cast
8 from aioesphomeapi
import (
18 ATTR_COLOR_TEMP_KELVIN,
36 convert_api_error_ha_error,
37 esphome_state_property,
38 platform_async_setup_entry,
41 FLASH_LENGTHS = {FLASH_SHORT: 2, FLASH_LONG: 10}
44 _COLOR_MODE_MAPPING = {
46 LightColorCapability.ON_OFF,
48 ColorMode.BRIGHTNESS: [
49 LightColorCapability.ON_OFF | LightColorCapability.BRIGHTNESS,
51 LightColorCapability.BRIGHTNESS,
53 ColorMode.COLOR_TEMP: [
54 LightColorCapability.ON_OFF
55 | LightColorCapability.BRIGHTNESS
56 | LightColorCapability.COLOR_TEMPERATURE,
57 LightColorCapability.ON_OFF
58 | LightColorCapability.BRIGHTNESS
59 | LightColorCapability.COLD_WARM_WHITE,
62 LightColorCapability.ON_OFF
63 | LightColorCapability.BRIGHTNESS
64 | LightColorCapability.RGB,
67 LightColorCapability.ON_OFF
68 | LightColorCapability.BRIGHTNESS
69 | LightColorCapability.RGB
70 | LightColorCapability.WHITE,
73 LightColorCapability.ON_OFF
74 | LightColorCapability.BRIGHTNESS
75 | LightColorCapability.RGB
76 | LightColorCapability.WHITE
77 | LightColorCapability.COLOR_TEMPERATURE,
78 LightColorCapability.ON_OFF
79 | LightColorCapability.BRIGHTNESS
80 | LightColorCapability.RGB
81 | LightColorCapability.COLD_WARM_WHITE,
84 LightColorCapability.ON_OFF
85 | LightColorCapability.BRIGHTNESS
86 | LightColorCapability.WHITE
92 """Convert absolute mired shift to degrees kelvin.
94 This function rounds the converted value instead of flooring the value as
95 is done in homeassistant.util.color.color_temperature_mired_to_kelvin().
97 If the value of mired_temperature is less than or equal to zero, return
98 the original value to avoid a divide by zero.
100 if mired_temperature <= 0:
101 return round(mired_temperature)
102 return round(1000000 / mired_temperature)
107 """Convert an esphome color mode to a HA color mode constant.
109 Choses the color mode that best matches the feature-set.
112 for ha_mode, cap_lists
in _COLOR_MODE_MAPPING.items():
113 for caps
in cap_lists:
117 if (mode & caps) == caps:
119 candidates.append((ha_mode, caps))
122 return ColorMode.UNKNOWN
125 candidates.sort(key=
lambda key: key[1].bit_count())
126 return candidates[-1][0]
131 supported: list[int], features: LightColorCapability
132 ) -> tuple[int, ...]:
133 """Filter the given supported color modes.
135 Excluding all values that don't have the requested features.
137 features_value = features.value
139 mode
for mode
in supported
if (mode & features_value) == features_value
145 """Return the color mode with the least complexity."""
148 color_modes_list =
list(color_modes)
149 color_modes_list.sort(key=
lambda mode: (mode).bit_count())
150 return color_modes_list[0]
154 """A light implementation for ESPHome."""
156 _native_supported_color_modes: tuple[int, ...]
157 _supports_color_mode =
False
160 @esphome_state_property
162 """Return true if the light is on."""
163 return self.
_state_state.state
165 @convert_api_error_ha_error
167 """Turn the entity on."""
168 data: dict[str, Any] = {
"key": self.
_key_key,
"state":
True}
171 try_keep_current_mode =
True
175 if (brightness_ha := kwargs.get(ATTR_BRIGHTNESS))
is not None:
176 data[
"brightness"] = brightness_ha / 255
178 color_modes, LightColorCapability.BRIGHTNESS
181 if (rgb_ha := kwargs.get(ATTR_RGB_COLOR))
is not None:
182 rgb =
tuple(x / 255
for x
in rgb_ha)
185 data[
"rgb"] =
tuple(x / (color_bri
or 1)
for x
in rgb)
186 data[
"color_brightness"] = color_bri
188 try_keep_current_mode =
False
190 if (rgbw_ha := kwargs.get(ATTR_RGBW_COLOR))
is not None:
191 *rgb, w =
tuple(x / 255
for x
in rgbw_ha)
194 data[
"rgb"] =
tuple(x / (color_bri
or 1)
for x
in rgb)
196 data[
"color_brightness"] = color_bri
198 color_modes, LightColorCapability.RGB | LightColorCapability.WHITE
200 try_keep_current_mode =
False
202 if (rgbww_ha := kwargs.get(ATTR_RGBWW_COLOR))
is not None:
203 *rgb, cw, ww =
tuple(x / 255
for x
in rgbww_ha)
206 data[
"rgb"] =
tuple(x / (color_bri
or 1)
for x
in rgb)
210 data[
"cold_white"] = cw
211 data[
"warm_white"] = ww
213 color_modes, LightColorCapability.COLD_WARM_WHITE
217 white = data[
"white"] =
max(cw, ww)
220 min_ct = static_info.min_mireds
221 max_ct = static_info.max_mireds
222 ct_ratio = ww / (cw + ww)
223 data[
"color_temperature"] = min_ct + ct_ratio * (max_ct - min_ct)
226 LightColorCapability.COLOR_TEMPERATURE | LightColorCapability.WHITE,
228 try_keep_current_mode =
False
230 data[
"color_brightness"] = color_bri
232 if (flash := kwargs.get(ATTR_FLASH))
is not None:
233 data[
"flash_length"] = FLASH_LENGTHS[flash]
235 if (transition := kwargs.get(ATTR_TRANSITION))
is not None:
236 data[
"transition_length"] = transition
238 if (color_temp_k := kwargs.get(ATTR_COLOR_TEMP_KELVIN))
is not None:
240 data[
"color_temperature"] = 1000000.0 / color_temp_k
242 color_modes, LightColorCapability.COLOR_TEMPERATURE
244 color_modes = color_temp_modes
247 color_modes, LightColorCapability.COLD_WARM_WHITE
249 try_keep_current_mode =
False
251 if (effect := kwargs.get(ATTR_EFFECT))
is not None:
252 data[
"effect"] = effect
254 if (white_ha := kwargs.get(ATTR_WHITE))
is not None:
258 data[
"brightness"] = white_ha / 255
262 LightColorCapability.BRIGHTNESS | LightColorCapability.WHITE,
264 try_keep_current_mode =
False
268 try_keep_current_mode
269 and self.
_state_state
is not None
270 and self.
_state_state.color_mode
in color_modes
273 data[
"color_mode"] = self.
_state_state.color_mode
279 self.
_client_client.light_command(**data)
281 @convert_api_error_ha_error
283 """Turn the entity off."""
284 data: dict[str, Any] = {
"key": self.
_key_key,
"state":
False}
285 if ATTR_FLASH
in kwargs:
286 data[
"flash_length"] = FLASH_LENGTHS[kwargs[ATTR_FLASH]]
287 if ATTR_TRANSITION
in kwargs:
288 data[
"transition_length"] = kwargs[ATTR_TRANSITION]
289 self.
_client_client.light_command(**data)
292 @esphome_state_property
294 """Return the brightness of this light between 0..255."""
295 return round(self.
_state_state.brightness * 255)
298 @esphome_state_property
300 """Return the color mode of the light."""
304 assert supported_color_modes
is not None
305 return next(iter(supported_color_modes))
310 @esphome_state_property
312 """Return the rgb color value [int, int, int]."""
316 round(state.red * 255),
317 round(state.green * 255),
318 round(state.blue * 255),
322 round(state.red * state.color_brightness * 255),
323 round(state.green * state.color_brightness * 255),
324 round(state.blue * state.color_brightness * 255),
328 @esphome_state_property
330 """Return the rgbw color value [int, int, int, int]."""
331 white = round(self.
_state_state.white * 255)
336 @esphome_state_property
338 """Return the rgbww color value [int, int, int, int, int]."""
346 min_ct = static_info.min_mireds
347 max_ct = static_info.max_mireds
348 color_temp =
min(
max(state.color_temperature, min_ct), max_ct)
351 ww_frac = (color_temp - min_ct) / (max_ct - min_ct)
352 cw_frac = 1 - ww_frac
356 round(white * cw_frac /
max(cw_frac, ww_frac) * 255),
357 round(white * ww_frac /
max(cw_frac, ww_frac) * 255),
361 round(state.cold_white * 255),
362 round(state.warm_white * 255),
366 @esphome_state_property
368 """Return the CT color value in Kelvin."""
372 @esphome_state_property
374 """Return the current effect."""
375 return self.
_state_state.effect
379 """Set attrs from static info."""
384 static_info.supported_color_modes_compat(self.
_api_version_api_version)
386 flags = LightEntityFeature.FLASH
390 if any(m
not in (0, LightColorCapability.ON_OFF)
for m
in modes):
391 flags |= LightEntityFeature.TRANSITION
392 if static_info.effects:
393 flags |= LightEntityFeature.EFFECT
400 supported.discard(ColorMode.UNKNOWN)
402 if ColorMode.ONOFF
in supported
and len(supported) > 1:
403 supported.remove(ColorMode.ONOFF)
404 if ColorMode.BRIGHTNESS
in supported
and len(supported) > 1:
405 supported.remove(ColorMode.BRIGHTNESS)
406 if ColorMode.WHITE
in supported
and len(supported) == 1:
407 supported.remove(ColorMode.WHITE)
413 supported.add(ColorMode.ONOFF)
419 if ColorMode.COLOR_TEMP
in supported:
424 async_setup_entry = partial(
425 platform_async_setup_entry,
427 entity_type=EsphomeLight,
428 state_type=LightState,
tuple[int, int, int, int]|None rgbw_color(self)
tuple[int, int, int]|None rgb_color(self)
None _on_static_info_update(self, EntityInfo static_info)
str|None color_mode(self)
bool _supports_color_mode
None async_turn_off(self, **Any kwargs)
_native_supported_color_modes
None async_turn_on(self, **Any kwargs)
_attr_supported_color_modes
int color_temp_kelvin(self)
int|None brightness(self)
_attr_min_color_temp_kelvin
tuple[int, int, int, int, int]|None rgbww_color(self)
_attr_max_color_temp_kelvin
tuple[int, int, int]|None rgb_color(self)
set[ColorMode]|set[str]|None supported_color_modes(self)
str _color_mode_to_ha(int mode)
tuple[int,...] _filter_color_modes(list[int] supported, LightColorCapability features)
int _least_complex_color_mode(tuple[int,...] color_modes)
int _mired_to_kelvin(float mired_temperature)