1 """Support for deCONZ lights."""
3 from __future__
import annotations
5 from typing
import Any, TypedDict, cast
7 from pydeconz.interfaces.groups
import GroupHandler
8 from pydeconz.interfaces.lights
import LightHandler
9 from pydeconz.models.event
import EventType
10 from pydeconz.models.group
import Group, TypedGroupAction
11 from pydeconz.models.light.light
import Light, LightAlert, LightColorMode, LightEffect
21 DOMAIN
as LIGHT_DOMAIN,
35 from .const
import DOMAIN
as DECONZ_DOMAIN, POWER_PLUGS
36 from .entity
import DeconzDevice
37 from .hub
import DeconzHub
39 DECONZ_GROUP =
"is_deconz_group"
41 EFFECT_COLORLOOP: LightEffect.COLOR_LOOP,
42 "none": LightEffect.NONE,
44 "candle": LightEffect.CANDLE,
45 "cosmos": LightEffect.COSMOS,
46 "enchant": LightEffect.ENCHANT,
47 "fire": LightEffect.FIRE,
48 "fireplace": LightEffect.FIREPLACE,
49 "glisten": LightEffect.GLISTEN,
50 "loop": LightEffect.LOOP,
51 "opal": LightEffect.OPAL,
52 "prism": LightEffect.PRISM,
53 "sparkle": LightEffect.SPARKLE,
54 "sunbeam": LightEffect.SUNBEAM,
55 "sunrise": LightEffect.SUNRISE,
56 "sunset": LightEffect.SUNSET,
57 "underwater": LightEffect.UNDERWATER,
59 "carnival": LightEffect.CARNIVAL,
60 "collide": LightEffect.COLLIDE,
61 "fading": LightEffect.FADING,
62 "fireworks": LightEffect.FIREWORKS,
63 "flag": LightEffect.FLAG,
64 "glow": LightEffect.GLOW,
65 "rainbow": LightEffect.RAINBOW,
66 "snake": LightEffect.SNAKE,
67 "snow": LightEffect.SNOW,
68 "sparkles": LightEffect.SPARKLES,
69 "steady": LightEffect.STEADY,
70 "strobe": LightEffect.STROBE,
71 "twinkle": LightEffect.TWINKLE,
72 "updown": LightEffect.UPDOWN,
73 "vintage": LightEffect.VINTAGE,
74 "waves": LightEffect.WAVES,
76 FLASH_TO_DECONZ = {FLASH_SHORT: LightAlert.SHORT, FLASH_LONG: LightAlert.LONG}
78 DECONZ_TO_COLOR_MODE = {
79 LightColorMode.CT: ColorMode.COLOR_TEMP,
80 LightColorMode.GRADIENT: ColorMode.XY,
81 LightColorMode.HS: ColorMode.HS,
82 LightColorMode.XY: ColorMode.XY,
85 XMAS_LIGHT_EFFECTS = [
106 """Attributes available with set state call."""
110 color_temperature: int
116 xy: tuple[float, float]
120 group: Group, lights: list[Light], override: bool =
False
122 """Sync group color state with light."""
124 attribute: light_attribute
126 for attribute
in (
"bri",
"ct",
"hue",
"sat",
"xy",
"colormode",
"effect")
127 if (light_attribute := light.raw[
"state"].
get(attribute))
is not None
131 group.raw[
"action"] = cast(TypedGroupAction, data)
133 group.update(cast(dict[str, dict[str, Any]], {
"action": data}))
138 config_entry: ConfigEntry,
139 async_add_entities: AddEntitiesCallback,
141 """Set up the deCONZ lights and groups from a config entry."""
142 hub = DeconzHub.get_hub(hass, config_entry)
143 hub.entities[LIGHT_DOMAIN] = set()
146 def async_add_light(_: EventType, light_id: str) ->
None:
147 """Add light from deCONZ."""
148 light = hub.api.lights.lights[light_id]
149 if light.type
in POWER_PLUGS:
154 hub.register_platform_add_device_callback(
156 hub.api.lights.lights,
160 def async_add_group(_: EventType, group_id: str) ->
None:
161 """Add group from deCONZ.
163 Update group states based on its sum of related lights.
165 if (group := hub.api.groups[group_id])
and not group.lights:
170 for light_id
in group.lights
171 if (light := hub.api.lights.lights.get(light_id))
and light.reachable
177 hub.register_platform_add_device_callback(
184 DeconzDevice[_LightDeviceT], LightEntity
186 """Representation of a deCONZ light."""
189 _attr_color_mode = ColorMode.UNKNOWN
191 def __init__(self, device: _LightDeviceT, hub: DeconzHub) ->
None:
195 self.api: GroupHandler | LightHandler
196 if isinstance(self._device, Light):
197 self.api = self.hub.api.lights.lights
198 elif isinstance(self._device, Group):
199 self.api = self.hub.api.groups
201 self._attr_supported_color_modes: set[ColorMode] = set()
203 if device.color_temp
is not None:
204 self._attr_supported_color_modes.
add(ColorMode.COLOR_TEMP)
206 if device.hue
is not None and device.saturation
is not None:
207 self._attr_supported_color_modes.
add(ColorMode.HS)
209 if device.xy
is not None:
210 self._attr_supported_color_modes.
add(ColorMode.XY)
212 if not self._attr_supported_color_modes
and device.brightness
is not None:
213 self._attr_supported_color_modes.
add(ColorMode.BRIGHTNESS)
215 if not self._attr_supported_color_modes:
216 self._attr_supported_color_modes.
add(ColorMode.ONOFF)
218 if device.brightness
is not None:
219 self._attr_supported_features |= (
220 LightEntityFeature.FLASH | LightEntityFeature.TRANSITION
223 if device.effect
is not None:
224 self._attr_supported_features |= LightEntityFeature.EFFECT
225 self._attr_effect_list = [EFFECT_COLORLOOP]
228 if isinstance(device, Light):
229 if device.supported_effects
is not None:
230 self._attr_effect_list = [
232 for el
in device.supported_effects
233 if el
in EFFECT_TO_DECONZ
235 if device.model_id
in (
"HG06467",
"TS0601"):
236 self._attr_effect_list = XMAS_LIGHT_EFFECTS
240 """Return the color mode of the light."""
241 if self._device.color_mode
in DECONZ_TO_COLOR_MODE:
242 color_mode = DECONZ_TO_COLOR_MODE[self._device.color_mode]
243 elif self._device.brightness
is not None:
244 color_mode = ColorMode.BRIGHTNESS
246 color_mode = ColorMode.ONOFF
247 if color_mode
not in self._attr_supported_color_modes:
249 return self._attr_color_mode
250 self._attr_color_mode = color_mode
255 """Return the brightness of this light between 0..255."""
256 return self._device.brightness
260 """Return the CT color value."""
261 return self._device.color_temp
265 """Return the hs color value."""
266 if (hue := self._device.hue)
and (sat := self._device.saturation):
267 return (hue / 65535 * 360, sat / 255 * 100)
272 """Return the XY color value."""
273 return self._device.xy
277 """Return true if light is on."""
278 return self._device.state
282 data: SetStateAttributes = {
"on":
True}
284 if ATTR_BRIGHTNESS
in kwargs:
285 data[
"brightness"] = kwargs[ATTR_BRIGHTNESS]
287 if ATTR_COLOR_TEMP
in kwargs:
288 data[
"color_temperature"] = kwargs[ATTR_COLOR_TEMP]
290 if ATTR_HS_COLOR
in kwargs:
291 if ColorMode.XY
in self._attr_supported_color_modes:
294 data[
"hue"] =
int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535)
295 data[
"saturation"] =
int(kwargs[ATTR_HS_COLOR][1] / 100 * 255)
297 if ATTR_XY_COLOR
in kwargs:
298 data[
"xy"] = kwargs[ATTR_XY_COLOR]
300 if ATTR_TRANSITION
in kwargs:
301 data[
"transition_time"] =
int(kwargs[ATTR_TRANSITION] * 10)
302 elif "IKEA" in self._device.manufacturer:
303 data[
"transition_time"] = 0
305 if ATTR_FLASH
in kwargs
and kwargs[ATTR_FLASH]
in FLASH_TO_DECONZ:
306 data[
"alert"] = FLASH_TO_DECONZ[kwargs[ATTR_FLASH]]
309 if ATTR_EFFECT
in kwargs
and kwargs[ATTR_EFFECT]
in EFFECT_TO_DECONZ:
310 data[
"effect"] = EFFECT_TO_DECONZ[kwargs[ATTR_EFFECT]]
312 await self.api.set_state(id=self._device.resource_id, **data)
315 """Turn off light."""
316 if not self._device.state:
319 data: SetStateAttributes = {
"on":
False}
321 if ATTR_TRANSITION
in kwargs:
322 data[
"brightness"] = 0
323 data[
"transition_time"] =
int(kwargs[ATTR_TRANSITION] * 10)
325 if ATTR_FLASH
in kwargs
and kwargs[ATTR_FLASH]
in FLASH_TO_DECONZ:
326 data[
"alert"] = FLASH_TO_DECONZ[kwargs[ATTR_FLASH]]
329 await self.api.set_state(id=self._device.resource_id, **data)
333 """Return the device state attributes."""
334 return {DECONZ_GROUP: isinstance(self._device, Group)}
338 """Representation of a deCONZ light."""
342 """Return the warmest color_temp that this light supports."""
343 return self._device.max_color_temp
or super().max_mireds
347 """Return the coldest color_temp that this light supports."""
348 return self._device.min_color_temp
or super().min_mireds
352 """Light state will also reflect in relevant groups."""
355 if self._device.reachable
and "attr" not in self._device.changed_keys:
356 for group
in self.hub.api.groups.values():
357 if self._device.resource_id
in group.lights:
362 """Representation of a deCONZ group."""
364 _attr_has_entity_name =
True
366 def __init__(self, device: Group, hub: DeconzHub) ->
None:
367 """Set up group and create an unique id."""
368 self.
_unique_id_unique_id = f
"{hub.bridgeid}-{device.deconz_id}"
375 """Return a unique identifier for this device."""
380 """Return a device description for device registry."""
382 identifiers={(DECONZ_DOMAIN, self.
unique_idunique_id)},
383 manufacturer=
"Dresden Elektronik",
384 model=
"deCONZ group",
385 name=self._device.name,
386 via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id),
391 """Return the device state attributes."""
392 attributes =
dict(super().extra_state_attributes)
393 attributes[
"all_on"] = self._device.all_on
DeviceInfo device_info(self)
None __init__(self, Group device, DeconzHub hub)
dict[str, bool] extra_state_attributes(self)
None async_update_callback(self)
bool add(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
None async_turn_on(self, **Any kwargs)
None update_color_state(Group group, list[Light] lights, bool override=False)
int|None color_temp(self)
int|None brightness(self)
str|None color_mode(self)
tuple[float, float]|None hs_color(self)
None async_turn_off(self, **Any kwargs)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
None __init__(self, _LightDeviceT device, DeconzHub hub)
dict[str, bool] extra_state_attributes(self)
tuple[float, float]|None xy_color(self)
tuple[float, float] color_hs_to_xy(float iH, float iS, GamutType|None Gamut=None)