Home Assistant Unofficial Reference 2024.12.1
scene.py
Go to the documentation of this file.
1 """Support for scene platform for Hue scenes (V2 only)."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from aiohue.v2 import HueBridgeV2
8 from aiohue.v2.controllers.events import EventType
9 from aiohue.v2.controllers.scenes import ScenesController
10 from aiohue.v2.models.scene import Scene as HueScene, ScenePut as HueScenePut
11 from aiohue.v2.models.smart_scene import SmartScene as HueSmartScene, SmartSceneState
12 import voluptuous as vol
13 
14 from homeassistant.components.scene import ATTR_TRANSITION, Scene as SceneEntity
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.core import HomeAssistant, callback
17 from homeassistant.helpers.device_registry import DeviceInfo
19  AddEntitiesCallback,
20  async_get_current_platform,
21 )
22 
23 from .bridge import HueBridge
24 from .const import DOMAIN
25 from .v2.entity import HueBaseEntity
26 from .v2.helpers import normalize_hue_brightness, normalize_hue_transition
27 
28 SERVICE_ACTIVATE_SCENE = "activate_scene"
29 ATTR_DYNAMIC = "dynamic"
30 ATTR_SPEED = "speed"
31 ATTR_BRIGHTNESS = "brightness"
32 
33 
35  hass: HomeAssistant,
36  config_entry: ConfigEntry,
37  async_add_entities: AddEntitiesCallback,
38 ) -> None:
39  """Set up scene platform from Hue group scenes."""
40  bridge: HueBridge = hass.data[DOMAIN][config_entry.entry_id]
41  api: HueBridgeV2 = bridge.api
42 
43  if bridge.api_version == 1:
44  # should not happen, but just in case
45  raise NotImplementedError("Scene support is only available for V2 bridges")
46 
47  # add entities for all scenes
48  @callback
49  def async_add_entity(
50  event_type: EventType, resource: HueScene | HueSmartScene
51  ) -> None:
52  """Add entity from Hue resource."""
53  if isinstance(resource, HueSmartScene):
54  async_add_entities([HueSmartSceneEntity(bridge, api.scenes, resource)])
55  else:
56  async_add_entities([HueSceneEntity(bridge, api.scenes, resource)])
57 
58  # add all current items in controller
59  for item in api.scenes:
60  async_add_entity(EventType.RESOURCE_ADDED, item)
61 
62  # register listener for new items only
63  config_entry.async_on_unload(
64  api.scenes.subscribe(async_add_entity, event_filter=EventType.RESOURCE_ADDED)
65  )
66 
67  # add platform service to turn_on/activate scene with advanced options
68  platform = async_get_current_platform()
69  platform.async_register_entity_service(
70  SERVICE_ACTIVATE_SCENE,
71  {
72  vol.Optional(ATTR_DYNAMIC): vol.Coerce(bool),
73  vol.Optional(ATTR_SPEED): vol.All(
74  vol.Coerce(int), vol.Range(min=0, max=100)
75  ),
76  vol.Optional(ATTR_TRANSITION): vol.All(
77  vol.Coerce(float), vol.Range(min=0, max=3600)
78  ),
79  vol.Optional(ATTR_BRIGHTNESS): vol.All(
80  vol.Coerce(int), vol.Range(min=1, max=255)
81  ),
82  },
83  "_async_activate",
84  )
85 
86 
87 class HueSceneEntityBase(HueBaseEntity, SceneEntity):
88  """Base Representation of a Scene entity from Hue Scenes."""
89 
90  _attr_has_entity_name = True
91 
92  def __init__(
93  self,
94  bridge: HueBridge,
95  controller: ScenesController,
96  resource: HueScene | HueSmartScene,
97  ) -> None:
98  """Initialize the entity."""
99  super().__init__(bridge, controller, resource)
100  self.resourceresourceresource = resource
101  self.controllercontrollercontroller = controller
102  self.groupgroup = self.controllercontrollercontroller.get_group(self.resourceresourceresource.id)
103  # we create a virtual service/device for Hue zones/rooms
104  # so we have a parent for grouped lights and scenes
106  identifiers={(DOMAIN, self.groupgroup.id)},
107  )
108 
109  async def async_added_to_hass(self) -> None:
110  """Call when entity is added."""
111  await super().async_added_to_hass()
112  # Add value_changed callback for group to catch name changes.
113  self.async_on_removeasync_on_remove(
114  self.bridgebridge.api.groups.subscribe(
115  self._handle_event_handle_event,
116  self.groupgroup.id,
117  (EventType.RESOURCE_UPDATED),
118  )
119  )
120 
121  @property
122  def name(self) -> str:
123  """Return name of the scene."""
124  return self.resourceresourceresource.metadata.name
125 
126 
128  """Representation of a Scene entity from Hue Scenes."""
129 
130  @property
131  def is_dynamic(self) -> bool:
132  """Return if this scene has a dynamic color palette."""
133  if (
134  self.resourceresourceresource.palette
135  and self.resourceresourceresource.palette.color
136  and len(self.resourceresourceresource.palette.color) > 1
137  ):
138  return True
139  if (
140  self.resourceresourceresource.palette
141  and self.resourceresourceresource.palette.color_temperature
142  and len(self.resourceresourceresource.palette.color_temperature) > 1
143  ):
144  return True
145  return False
146 
147  async def async_activate(self, **kwargs: Any) -> None:
148  """Activate Hue scene."""
149  transition = normalize_hue_transition(kwargs.get(ATTR_TRANSITION))
150  # the options below are advanced only
151  # as we're not allowed to override the default scene turn_on service
152  # we've implemented a `activate_scene` entity service
153  dynamic = kwargs.get(ATTR_DYNAMIC, False)
154  speed = kwargs.get(ATTR_SPEED)
155  brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
156 
157  if speed is not None:
158  await self.bridgebridge.async_request_call(
159  self.controllercontrollercontroller.scene.update,
160  self.resourceresourceresource.id,
161  HueScenePut(speed=speed / 100),
162  )
163 
164  await self.bridgebridge.async_request_call(
165  self.controllercontrollercontroller.scene.recall,
166  self.resourceresourceresource.id,
167  dynamic=dynamic,
168  duration=transition,
169  brightness=brightness,
170  )
171 
172  @property
173  def extra_state_attributes(self) -> dict[str, Any] | None:
174  """Return the optional state attributes."""
175  brightness = None
176  if palette := self.resourceresourceresource.palette:
177  if palette.dimming:
178  brightness = palette.dimming[0].brightness
179  if brightness is None:
180  # get brightness from actions
181  for action in self.resourceresourceresource.actions:
182  if action.action.dimming:
183  brightness = action.action.dimming.brightness
184  break
185  if brightness is not None:
186  # Hue uses a range of [0, 100] to control brightness.
187  brightness = round((brightness / 100) * 255)
188  return {
189  "group_name": self.groupgroup.metadata.name,
190  "group_type": self.groupgroup.type.value,
191  "name": self.resourceresourceresource.metadata.name,
192  "speed": self.resourceresourceresource.speed,
193  "brightness": brightness,
194  "is_dynamic": self.is_dynamicis_dynamic,
195  }
196 
197 
199  """Representation of a Smart Scene entity from Hue Scenes."""
200 
201  @property
202  def is_active(self) -> bool:
203  """Return if this smart scene is currently active."""
204  return self.resourceresourceresource.state == SmartSceneState.ACTIVE
205 
206  async def async_activate(self, **kwargs: Any) -> None:
207  """Activate Hue Smart scene."""
208 
209  await self.bridgebridge.async_request_call(
210  self.controllercontrollercontroller.smart_scene.recall,
211  self.resourceresourceresource.id,
212  )
213 
214  @property
215  def extra_state_attributes(self) -> dict[str, Any] | None:
216  """Return the optional state attributes."""
217  res = {
218  "group_name": self.groupgroup.metadata.name,
219  "group_type": self.groupgroup.type.value,
220  "name": self.resourceresourceresource.metadata.name,
221  "is_active": self.is_activeis_active,
222  }
223  if self.is_activeis_active and self.resourceresourceresource.active_timeslot:
224  res["active_timeslot_id"] = self.resourceresourceresource.active_timeslot.timeslot_id
225  res["active_timeslot_name"] = self.resourceresourceresource.active_timeslot.weekday.value
226  # lookup active scene in timeslot
227  active_scene = None
228  count = 0
229  for day_timeslot in self.resourceresourceresource.week_timeslots:
230  for timeslot in day_timeslot.timeslots:
231  if count != self.resourceresourceresource.active_timeslot.timeslot_id:
232  count += 1
233  continue
234  active_scene = self.controllercontrollercontroller.get(timeslot.target.rid)
235  break
236  if active_scene is not None:
237  res["active_scene"] = active_scene.metadata.name
238  return res
None __init__(self, HueBridge bridge, ScenesController controller, HueScene|HueSmartScene resource)
Definition: scene.py:97
dict[str, Any]|None extra_state_attributes(self)
Definition: scene.py:173
None async_activate(self, **Any kwargs)
Definition: scene.py:147
dict[str, Any]|None extra_state_attributes(self)
Definition: scene.py:215
None _handle_event(self, EventType event_type, HueResource resource)
Definition: entity.py:126
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: scene.py:38
float|None normalize_hue_brightness(float|None brightness)
Definition: helpers.py:6
float|None normalize_hue_transition(float|None transition)
Definition: helpers.py:15