1 """Support for TPLink lights."""
3 from __future__
import annotations
5 from collections.abc
import Sequence
9 from kasa
import Device, DeviceType, LightState, Module
10 from kasa.interfaces
import Light, LightEffect
11 from kasa.iot
import IotDevice
12 import voluptuous
as vol
16 ATTR_COLOR_TEMP_KELVIN,
24 filter_supported_color_modes,
32 from .
import TPLinkConfigEntry, legacy_device_id
33 from .coordinator
import TPLinkDataUpdateCoordinator
34 from .entity
import CoordinatedTPLinkEntity, async_refresh_after
36 _LOGGER = logging.getLogger(__name__)
38 SERVICE_RANDOM_EFFECT =
"random_effect"
39 SERVICE_SEQUENCE_EFFECT =
"sequence_effect"
41 HUE = vol.Range(min=0, max=360)
42 SAT = vol.Range(min=0, max=100)
43 VAL = vol.Range(min=0, max=100)
44 TRANSITION = vol.Range(min=0, max=6000)
45 HSV_SEQUENCE = vol.ExactSequence((HUE, SAT, VAL))
47 BASE_EFFECT_DICT: VolDictType = {
48 vol.Optional(
"brightness", default=100): vol.All(
49 vol.Coerce(int), vol.Range(min=0, max=100)
51 vol.Optional(
"duration", default=0): vol.All(
52 vol.Coerce(int), vol.Range(min=0, max=5000)
54 vol.Optional(
"transition", default=0): vol.All(vol.Coerce(int), TRANSITION),
55 vol.Optional(
"segments", default=[0]): vol.All(
57 vol.Length(min=1, max=80),
58 [vol.All(vol.Coerce(int), vol.Range(min=0, max=80))],
62 SEQUENCE_EFFECT_DICT: VolDictType = {
64 vol.Required(
"sequence"): vol.All(
66 vol.Length(min=1, max=16),
67 [vol.All(vol.Coerce(tuple), HSV_SEQUENCE)],
69 vol.Optional(
"repeat_times", default=0): vol.All(
70 vol.Coerce(int), vol.Range(min=0, max=10)
72 vol.Optional(
"spread", default=1): vol.All(
73 vol.Coerce(int), vol.Range(min=1, max=16)
75 vol.Optional(
"direction", default=4): vol.All(
76 vol.Coerce(int), vol.Range(min=1, max=4)
80 RANDOM_EFFECT_DICT: VolDictType = {
82 vol.Optional(
"fadeoff", default=0): vol.All(
83 vol.Coerce(int), vol.Range(min=0, max=3000)
85 vol.Optional(
"hue_range"): vol.All(
86 cv.ensure_list_csv, [vol.Coerce(int)], vol.ExactSequence((HUE, HUE))
88 vol.Optional(
"saturation_range"): vol.All(
89 cv.ensure_list_csv, [vol.Coerce(int)], vol.ExactSequence((SAT, SAT))
91 vol.Optional(
"brightness_range"): vol.All(
92 cv.ensure_list_csv, [vol.Coerce(int)], vol.ExactSequence((VAL, VAL))
94 vol.Optional(
"transition_range"): vol.All(
97 vol.ExactSequence((TRANSITION, TRANSITION)),
99 vol.Required(
"init_states"): vol.All(
100 cv.ensure_list_csv, [vol.Coerce(int)], HSV_SEQUENCE
102 vol.Optional(
"random_seed", default=100): vol.All(
103 vol.Coerce(int), vol.Range(min=1, max=600)
105 vol.Optional(
"backgrounds"): vol.All(
107 vol.Length(min=1, max=16),
108 [vol.All(vol.Coerce(tuple), HSV_SEQUENCE)],
122 "id":
"yMwcNpLxijmoKamskHCvvravpbnIqAIN",
123 "brightness": brightness,
125 "segments": segments,
126 "expansion_strategy": 1,
128 "duration": duration,
129 "transition": transition,
135 config_entry: TPLinkConfigEntry,
136 async_add_entities: AddEntitiesCallback,
138 """Set up switches."""
139 data = config_entry.runtime_data
140 parent_coordinator = data.parent_coordinator
141 device = parent_coordinator.device
142 entities: list[TPLinkLightEntity | TPLinkLightEffectEntity] = []
143 if effect_module := device.modules.get(Module.LightEffect):
148 light_module=device.modules[Module.Light],
149 effect_module=effect_module,
152 if effect_module.has_custom_effects:
153 platform = entity_platform.async_get_current_platform()
154 platform.async_register_entity_service(
155 SERVICE_RANDOM_EFFECT,
157 "async_set_random_effect",
159 platform.async_register_entity_service(
160 SERVICE_SEQUENCE_EFFECT,
161 SEQUENCE_EFFECT_DICT,
162 "async_set_sequence_effect",
164 elif Module.Light
in device.modules:
167 device, parent_coordinator, light_module=device.modules[Module.Light]
174 light_module=child.modules[Module.Light],
177 for child
in device.children
178 if Module.Light
in child.modules
184 """Representation of a TPLink Smart Bulb."""
186 _attr_supported_features = LightEntityFeature.TRANSITION
187 _fixed_color_mode: ColorMode |
None =
None
192 coordinator: TPLinkDataUpdateCoordinator,
195 parent: Device |
None =
None,
197 """Initialize the light."""
201 self.
_attr_name_attr_name =
None if parent
is None else device.alias
202 modes: set[ColorMode] = {ColorMode.ONOFF}
203 if light_module.is_variable_color_temp:
204 modes.add(ColorMode.COLOR_TEMP)
205 temp_range = light_module.valid_temperature_range
208 if light_module.is_color:
209 modes.add(ColorMode.HS)
210 if light_module.is_dimmable:
211 modes.add(ColorMode.BRIGHTNESS)
217 super().
__init__(device, coordinator, parent=parent)
220 """Return unique ID for the entity."""
223 device = self._device
226 if device.device_type
is DeviceType.Dimmer
and isinstance(device, IotDevice):
237 if self.
_parent_parent
or device.children:
240 return device.mac.replace(
":",
"").upper()
245 ) -> tuple[int |
None, int |
None]:
246 if (transition := kwargs.get(ATTR_TRANSITION))
is not None:
247 transition =
int(transition * 1_000)
249 if (brightness := kwargs.get(ATTR_BRIGHTNESS))
is not None:
250 brightness = round((brightness * 100.0) / 255.0)
252 if self._device.device_type
is DeviceType.Dimmer
and transition
is None:
260 return brightness, transition
263 self, hs_color: tuple[int, int], brightness: int |
None, transition: int |
None
266 hue, sat =
tuple(
int(val)
for val
in hs_color)
267 await self.
_light_module_light_module.set_hsv(hue, sat, brightness, transition=transition)
270 self, color_temp: float, brightness: int |
None, transition: int |
None
273 valid_temperature_range = light_module.valid_temperature_range
274 requested_color_temp = round(color_temp)
279 clamped_color_temp =
min(
280 valid_temperature_range.max,
281 max(valid_temperature_range.min, requested_color_temp),
283 await light_module.set_color_temp(
285 brightness=brightness,
286 transition=transition,
290 self, brightness: int |
None, transition: int |
None
293 if brightness
is not None:
294 await self.
_light_module_light_module.set_brightness(brightness, transition=transition)
297 LightState(light_on=
True, transition=transition)
302 """Turn the light on."""
304 if ATTR_COLOR_TEMP_KELVIN
in kwargs:
306 kwargs[ATTR_COLOR_TEMP_KELVIN], brightness, transition
308 if ATTR_HS_COLOR
in kwargs:
309 await self.
_async_set_hsv_async_set_hsv(kwargs[ATTR_HS_COLOR], brightness, transition)
315 """Turn the light off."""
316 if (transition := kwargs.get(ATTR_TRANSITION))
is not None:
317 transition =
int(transition * 1_000)
319 LightState(light_on=
False, transition=transition)
323 """Return the active color mode."""
330 return ColorMode.COLOR_TEMP
335 """Update the entity's attributes."""
338 if light_module.is_dimmable:
342 if color_mode
is ColorMode.COLOR_TEMP:
344 elif color_mode
is ColorMode.HS:
345 hue, saturation, _ = light_module.hsv
350 """Representation of a TPLink Smart Light Strip."""
355 coordinator: TPLinkDataUpdateCoordinator,
358 effect_module: LightEffect,
360 """Initialize the light strip."""
362 super().
__init__(device, coordinator, light_module=light_module)
364 _attr_supported_features = LightEntityFeature.TRANSITION | LightEntityFeature.EFFECT
368 """Update the entity's attributes."""
371 if effect_module.effect != LightEffect.LIGHT_EFFECTS_OFF:
376 if effect_list := effect_module.effect_list:
383 """Turn the light on."""
385 effect_off_called =
False
386 if effect := kwargs.get(ATTR_EFFECT):
387 if effect
in {LightEffect.LIGHT_EFFECTS_OFF, EFFECT_OFF}:
388 if self.
_effect_module_effect_module.effect
is not LightEffect.LIGHT_EFFECTS_OFF:
389 await self.
_effect_module_effect_module.set_effect(LightEffect.LIGHT_EFFECTS_OFF)
390 effect_off_called =
True
395 kwargs[ATTR_EFFECT], brightness=brightness, transition=transition
399 _LOGGER.error(
"Invalid effect %s for %s", effect, self._device.host)
402 if ATTR_COLOR_TEMP_KELVIN
in kwargs:
403 if self.
effecteffect
and self.
effecteffect != EFFECT_OFF
and not effect_off_called:
407 await self.
_effect_module_effect_module.set_effect(LightEffect.LIGHT_EFFECTS_OFF)
409 kwargs[ATTR_COLOR_TEMP_KELVIN], brightness, transition
411 elif ATTR_HS_COLOR
in kwargs:
412 await self.
_async_set_hsv_async_set_hsv(kwargs[ATTR_HS_COLOR], brightness, transition)
423 init_states: tuple[int, int, int],
425 backgrounds: Sequence[tuple[int, int, int]] |
None =
None,
426 hue_range: tuple[int, int] |
None =
None,
427 saturation_range: tuple[int, int] |
None =
None,
428 brightness_range: tuple[int, int] |
None =
None,
429 transition_range: tuple[int, int] |
None =
None,
431 """Set a random effect."""
432 effect: dict[str, Any] = {
435 "init_states": [init_states],
436 "random_seed": random_seed,
439 effect[
"backgrounds"] = backgrounds
441 effect[
"fadeoff"] = fadeoff
443 effect[
"hue_range"] = hue_range
445 effect[
"saturation_range"] = saturation_range
447 effect[
"brightness_range"] = brightness_range
448 effect[
"brightness"] =
min(
449 brightness_range[1],
max(brightness, brightness_range[0])
452 effect[
"transition_range"] = transition_range
453 effect[
"transition"] = 0
462 sequence: Sequence[tuple[int, int, int]],
467 """Set a sequence effect."""
468 effect: dict[str, Any] = {
471 "sequence": sequence,
472 "repeat_times": repeat_times,
474 "direction": direction,
None async_set_random_effect(self, int brightness, int duration, int transition, list[int] segments, int fadeoff, tuple[int, int, int] init_states, int random_seed, Sequence[tuple[int, int, int]]|None backgrounds=None, tuple[int, int]|None hue_range=None, tuple[int, int]|None saturation_range=None, tuple[int, int]|None brightness_range=None, tuple[int, int]|None transition_range=None)
None async_set_sequence_effect(self, int brightness, int duration, int transition, list[int] segments, Sequence[tuple[int, int, int]] sequence, int repeat_times, int spread, int direction)
None __init__(self, Device device, TPLinkDataUpdateCoordinator coordinator, *Light light_module, LightEffect effect_module)
None async_turn_on(self, **Any kwargs)
None _async_update_attrs(self)
_attr_supported_color_modes
tuple[int|None, int|None] _async_extract_brightness_transition(self, **Any kwargs)
None _async_set_hsv(self, tuple[int, int] hs_color, int|None brightness, int|None transition)
_attr_max_color_temp_kelvin
None _async_set_color_temp(self, float color_temp, int|None brightness, int|None transition)
_attr_min_color_temp_kelvin
ColorMode _determine_color_mode(self)
None _async_turn_on_with_brightness(self, int|None brightness, int|None transition)
None async_turn_on(self, **Any kwargs)
None async_turn_off(self, **Any kwargs)
None _async_update_attrs(self)
None __init__(self, Device device, TPLinkDataUpdateCoordinator coordinator, *Light light_module, Device|None parent=None)
set[ColorMode] filter_supported_color_modes(Iterable[ColorMode] color_modes)
dict[str, Any] _async_build_base_effect(int brightness, int duration, int transition, list[int] segments)
None async_setup_entry(HomeAssistant hass, TPLinkConfigEntry config_entry, AddEntitiesCallback async_add_entities)
str legacy_device_id(Device device)