1 """Support for the Philips Hue lights."""
3 from __future__
import annotations
6 from datetime
import timedelta
7 from functools
import partial
27 filter_supported_color_modes,
35 DataUpdateCoordinator,
40 from ..bridge
import HueBridge
42 CONF_ALLOW_HUE_GROUPS,
43 CONF_ALLOW_UNREACHABLE,
44 DEFAULT_ALLOW_HUE_GROUPS,
45 DEFAULT_ALLOW_UNREACHABLE,
47 GROUP_TYPE_ENTERTAINMENT,
48 GROUP_TYPE_LIGHT_GROUP,
49 GROUP_TYPE_LIGHT_SOURCE,
53 REQUEST_REFRESH_DELAY,
55 from .helpers
import remove_devices
59 LOGGER = logging.getLogger(__name__)
61 COLOR_MODES_HUE_ON_OFF = {ColorMode.ONOFF}
62 COLOR_MODES_HUE_DIMMABLE = {ColorMode.BRIGHTNESS}
63 COLOR_MODES_HUE_COLOR_TEMP = {ColorMode.COLOR_TEMP}
64 COLOR_MODES_HUE_COLOR = {ColorMode.HS}
65 COLOR_MODES_HUE_EXTENDED = {ColorMode.COLOR_TEMP, ColorMode.HS}
68 "Extended color light": COLOR_MODES_HUE_EXTENDED,
69 "Color light": COLOR_MODES_HUE_COLOR,
70 "Dimmable light": COLOR_MODES_HUE_DIMMABLE,
71 "On/Off plug-in unit": COLOR_MODES_HUE_ON_OFF,
72 "Color temperature light": COLOR_MODES_HUE_COLOR_TEMP,
75 SUPPORT_HUE_ON_OFF = LightEntityFeature.FLASH | LightEntityFeature.TRANSITION
76 SUPPORT_HUE_DIMMABLE = SUPPORT_HUE_ON_OFF
77 SUPPORT_HUE_COLOR_TEMP = SUPPORT_HUE_DIMMABLE
78 SUPPORT_HUE_COLOR = SUPPORT_HUE_DIMMABLE | LightEntityFeature.EFFECT
79 SUPPORT_HUE_EXTENDED = SUPPORT_HUE_COLOR_TEMP | SUPPORT_HUE_COLOR
82 "Extended color light": SUPPORT_HUE_EXTENDED,
83 "Color light": SUPPORT_HUE_COLOR,
84 "Dimmable light": SUPPORT_HUE_DIMMABLE,
85 "On/Off plug-in unit": SUPPORT_HUE_ON_OFF,
86 "Color temperature light": SUPPORT_HUE_COLOR_TEMP,
89 ATTR_IS_HUE_GROUP =
"is_hue_group"
90 GAMUT_TYPE_UNAVAILABLE =
"None"
95 GROUP_MIN_API_VERSION = (1, 13, 0)
99 """Old way of setting up Hue lights.
101 Can only be called when a user accidentally mentions hue platform in their
102 config. But even in that case it would have been ignored.
106 def create_light(item_class, coordinator, bridge, is_group, rooms, api, item_id):
107 """Create the light."""
108 api_item = api[item_id]
111 supported_color_modes = set()
112 supported_features = LightEntityFeature(0)
113 for light_id
in api_item.lights:
114 if light_id
not in bridge.api.lights:
116 light = bridge.api.lights[light_id]
117 supported_features |= SUPPORT_HUE.get(light.type, SUPPORT_HUE_EXTENDED)
118 supported_color_modes.update(
119 COLOR_MODES_HUE.get(light.type, COLOR_MODES_HUE_EXTENDED)
121 supported_features = supported_features
or SUPPORT_HUE_EXTENDED
122 supported_color_modes = supported_color_modes
or COLOR_MODES_HUE_EXTENDED
125 supported_color_modes = COLOR_MODES_HUE.get(
126 api_item.type, COLOR_MODES_HUE_EXTENDED
128 supported_features = SUPPORT_HUE.get(api_item.type, SUPPORT_HUE_EXTENDED)
134 supported_color_modes,
141 """Set up the Hue lights from a config entry."""
142 bridge: HueBridge = hass.data[HUE_DOMAIN][config_entry.entry_id]
143 api_version =
tuple(
int(v)
for v
in bridge.api.config.apiversion.split(
"."))
146 allow_groups = config_entry.options.get(
147 CONF_ALLOW_HUE_GROUPS, DEFAULT_ALLOW_HUE_GROUPS
149 supports_groups = api_version >= GROUP_MIN_API_VERSION
150 if allow_groups
and not supports_groups:
151 LOGGER.warning(
"Please update your Hue bridge to support groups")
157 update_method=partial(async_safe_fetch, bridge, bridge.api.lights.update),
158 update_interval=SCAN_INTERVAL,
160 bridge.hass, LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=
True
166 await light_coordinator.async_refresh()
168 if not light_coordinator.last_update_success:
169 raise PlatformNotReady
171 if not supports_groups:
172 update_lights_without_group_support = partial(
178 partial(create_light, HueLight, light_coordinator, bridge,
False, rooms),
182 bridge.reset_jobs.append(
183 light_coordinator.async_add_listener(update_lights_without_group_support)
191 update_method=partial(async_safe_fetch, bridge, bridge.api.groups.update),
192 update_interval=SCAN_INTERVAL,
194 bridge.hass, LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=
True
199 update_groups = partial(
205 partial(create_light, HueLight, group_coordinator, bridge,
True,
None),
209 bridge.reset_jobs.append(group_coordinator.async_add_listener(update_groups))
211 cancel_update_rooms_listener =
None
214 def _async_update_rooms():
216 nonlocal cancel_update_rooms_listener
218 for item_id
in bridge.api.groups:
219 group = bridge.api.groups[item_id]
220 if group.type
not in [GROUP_TYPE_ROOM, GROUP_TYPE_ZONE]:
222 for light_id
in group.lights:
223 rooms[light_id] = group.name
227 bridge.reset_jobs.remove(cancel_update_rooms_listener)
228 cancel_update_rooms_listener()
229 cancel_update_rooms_listener =
None
232 def _setup_rooms_listener():
233 nonlocal cancel_update_rooms_listener
234 if cancel_update_rooms_listener
is not None:
239 cancel_update_rooms_listener = group_coordinator.async_add_listener(
242 bridge.reset_jobs.append(cancel_update_rooms_listener)
244 _setup_rooms_listener()
245 await group_coordinator.async_refresh()
247 update_lights_with_group_support = partial(
253 partial(create_light, HueLight, light_coordinator, bridge,
False, rooms),
254 _setup_rooms_listener,
257 bridge.reset_jobs.append(
258 light_coordinator.async_add_listener(update_lights_with_group_support)
260 update_lights_with_group_support()
264 """Safely fetch data."""
266 async
with asyncio.timeout(4):
267 return await bridge.async_request_call(fetch_method)
268 except aiohue.Unauthorized
as err:
269 await bridge.handle_unauthorized_error()
271 except aiohue.AiohueException
as err:
277 bridge, api, current, async_add_entities, create_item, new_items_callback
283 if item_id
in current:
286 current[item_id] = create_item(api, item_id)
287 new_items.append(current[item_id])
289 bridge.hass.async_create_task(
remove_devices(bridge, api, current))
293 if new_items_callback:
299 """Convert hue brightness 1..254 to hass format 0..255."""
300 return min(255, round((value / 254) * 255))
304 """Convert hass brightness 0..255 to hue 1..254 scale."""
305 return max(1, round((value / 255) * 254))
310 """Representation of a Hue light."""
318 supported_color_modes,
322 """Initialize the light."""
331 CONF_ALLOW_UNREACHABLE, DEFAULT_ALLOW_UNREACHABLE
335 if len(supported_color_modes) == 1:
338 assert supported_color_modes == {ColorMode.COLOR_TEMP, ColorMode.HS}
350 self.
is_osramis_osram = light.manufacturername ==
"OSRAM"
351 self.
is_philipsis_philips = light.manufacturername ==
"Philips"
352 self.
is_innris_innr = light.manufacturername ==
"innr"
353 self.
is_ewelinkis_ewelink = light.manufacturername ==
"eWeLink"
354 self.
is_livarnois_livarno = light.manufacturername.startswith(
"_TZ3000_")
355 self.
is_s31litezbis_s31litezb = light.modelid ==
"S31 Lite zb"
358 LOGGER.debug(
"Color gamut of %s: %s", self.
namenamename,
str(self.
gamutgamut))
359 if self.
lightlight.swupdatestate ==
"readytoinstall":
361 "Please check for software updates of the %s "
362 "bulb in the Philips Hue App."
364 LOGGER.warning(err, self.
namenamename)
365 if self.
gamutgamut
and not color.check_valid_gamut(self.
gamutgamut):
366 err =
"Color gamut of %s: %s, not valid, setting gamut to None."
368 self.
gamut_typgamut_typ = GAMUT_TYPE_UNAVAILABLE
369 self.
gamutgamut =
None
373 """Return the unique ID of this Hue light."""
374 unique_id = self.
lightlight.uniqueid
375 if not unique_id
and self.
is_groupis_group:
376 unique_id = self.
lightlight.id
382 """Return the ID of this Hue light."""
387 """Return the name of the Hue light."""
388 return self.
lightlight.name
392 """Return the brightness of this light between 0..255."""
394 bri = self.
lightlight.action.get(
"bri")
396 bri = self.
lightlight.state.get(
"bri")
405 """Return the color mode of the light."""
411 if mode
in (
"xy",
"hs"):
414 return ColorMode.COLOR_TEMP
418 """Return the hue color mode."""
420 return self.
lightlight.action.get(
"colormode")
421 return self.
lightlight.state.get(
"colormode")
425 """Return the hs color value."""
429 if mode
in (
"xy",
"hs")
and "xy" in source:
430 return color.color_xy_to_hs(*source[
"xy"], self.
gamutgamut)
436 """Return the CT color value."""
442 return self.
lightlight.action.get(
"ct")
443 return self.
lightlight.state.get(
"ct")
447 """Return the coldest color_temp that this light supports."""
449 return super().min_mireds
451 min_mireds = self.
lightlight.controlcapabilities.get(
"ct", {}).
get(
"min")
455 return super().min_mireds
461 """Return the warmest color_temp that this light supports."""
463 return super().max_mireds
467 max_mireds = self.
lightlight.controlcapabilities.get(
"ct", {}).
get(
"max")
470 return super().max_mireds
476 """Return true if device is on."""
478 return self.
lightlight.state[
"any_on"]
479 return self.
lightlight.state[
"on"]
483 """Return if light is available."""
484 return self.coordinator.last_update_success
and (
490 """Return the current effect."""
491 return self.
lightlight.state.get(
"effect",
None)
495 """Return the list of supported effects."""
497 return [EFFECT_RANDOM]
498 return [EFFECT_COLORLOOP, EFFECT_RANDOM]
502 """Return the device info."""
503 if self.
lightlight.type
in (
504 GROUP_TYPE_ENTERTAINMENT,
505 GROUP_TYPE_LIGHT_GROUP,
507 GROUP_TYPE_LUMINAIRE,
508 GROUP_TYPE_LIGHT_SOURCE,
513 suggested_area =
None
515 suggested_area = self.
_rooms_rooms[self.
lightlight.id]
518 identifiers={(HUE_DOMAIN, self.
device_iddevice_id)},
519 manufacturer=self.
lightlight.manufacturername,
522 model=self.
lightlight.productname
or self.
lightlight.modelid,
524 sw_version=self.
lightlight.swversion,
525 suggested_area=suggested_area,
526 via_device=(HUE_DOMAIN, self.
bridgebridge.api.config.bridgeid),
530 """Turn the specified or all lights on."""
531 command = {
"on":
True}
533 if ATTR_TRANSITION
in kwargs:
534 command[
"transitiontime"] =
int(kwargs[ATTR_TRANSITION] * 10)
536 if ATTR_HS_COLOR
in kwargs:
538 command[
"hue"] =
int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535)
539 command[
"sat"] =
int(kwargs[ATTR_HS_COLOR][1] / 100 * 255)
544 xy_color = color.color_hs_to_xy(*kwargs[ATTR_HS_COLOR], self.
gamutgamut)
545 command[
"xy"] = xy_color
546 elif ATTR_COLOR_TEMP
in kwargs:
547 temp = kwargs[ATTR_COLOR_TEMP]
550 if ATTR_BRIGHTNESS
in kwargs:
553 flash = kwargs.get(ATTR_FLASH)
555 if flash == FLASH_LONG:
556 command[
"alert"] =
"lselect"
558 elif flash == FLASH_SHORT:
559 command[
"alert"] =
"select"
567 command[
"alert"] =
"none"
569 if ATTR_EFFECT
in kwargs:
570 effect = kwargs[ATTR_EFFECT]
571 if effect == EFFECT_COLORLOOP:
572 command[
"effect"] =
"colorloop"
573 elif effect == EFFECT_RANDOM:
574 command[
"hue"] = random.randrange(0, 65535)
575 command[
"sat"] = random.randrange(150, 254)
577 command[
"effect"] =
"none"
580 await self.
bridgebridge.async_request_call(self.
lightlight.set_action, **command)
582 await self.
bridgebridge.async_request_call(self.
lightlight.set_state, **command)
584 await self.coordinator.async_request_refresh()
587 """Turn the specified or all lights off."""
588 command = {
"on":
False}
590 if ATTR_TRANSITION
in kwargs:
591 command[
"transitiontime"] =
int(kwargs[ATTR_TRANSITION] * 10)
593 flash = kwargs.get(ATTR_FLASH)
595 if flash == FLASH_LONG:
596 command[
"alert"] =
"lselect"
598 elif flash == FLASH_SHORT:
599 command[
"alert"] =
"select"
602 command[
"alert"] =
"none"
605 await self.
bridgebridge.async_request_call(self.
lightlight.set_action, **command)
607 await self.
bridgebridge.async_request_call(self.
lightlight.set_state, **command)
609 await self.coordinator.async_request_refresh()
613 """Return the device state attributes."""
616 return {ATTR_IS_HUE_GROUP: self.
is_groupis_group}
def __init__(self, coordinator, bridge, is_group, light, supported_color_modes, supported_features, rooms)
def async_turn_on(self, **kwargs)
def async_turn_off(self, **kwargs)
_attr_supported_color_modes
def extra_state_attributes(self)
DeviceInfo|None device_info(self)
str|UndefinedType|None name(self)
web.Response get(self, web.Request request, str config_key)
def remove_devices(bridge, api_ids, current)
def hue_brightness_to_hass(value)
def async_setup_entry(hass, config_entry, async_add_entities)
def create_light(item_class, coordinator, bridge, is_group, rooms, api, item_id)
def async_update_items(bridge, api, current, async_add_entities, create_item, new_items_callback)
def async_safe_fetch(bridge, fetch_method)
def async_setup_platform(hass, config, async_add_entities, discovery_info=None)
def hass_to_hue_brightness(value)
set[ColorMode] filter_supported_color_modes(Iterable[ColorMode] color_modes)