Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Generic Hue Entity Model."""
2 
3 from __future__ import annotations
4 
5 from typing import TYPE_CHECKING
6 
7 from aiohue.v2.controllers.base import BaseResourcesController
8 from aiohue.v2.controllers.events import EventType
9 from aiohue.v2.models.resource import ResourceTypes
10 from aiohue.v2.models.zigbee_connectivity import ConnectivityServiceStatus
11 
12 from homeassistant.core import callback
13 from homeassistant.helpers import entity_registry as er
14 from homeassistant.helpers.device_registry import DeviceInfo
15 from homeassistant.helpers.entity import Entity
16 
17 from ..bridge import HueBridge
18 from ..const import CONF_IGNORE_AVAILABILITY, DOMAIN
19 
20 if TYPE_CHECKING:
21  from aiohue.v2.models.device_power import DevicePower
22  from aiohue.v2.models.grouped_light import GroupedLight
23  from aiohue.v2.models.light import Light
24  from aiohue.v2.models.light_level import LightLevel
25  from aiohue.v2.models.motion import Motion
26 
27  type HueResource = Light | DevicePower | GroupedLight | LightLevel | Motion
28 
29 
30 RESOURCE_TYPE_NAMES = {
31  # a simple mapping of hue resource type to Hass name
32  ResourceTypes.LIGHT_LEVEL: "Illuminance",
33  ResourceTypes.DEVICE_POWER: "Battery",
34 }
35 
36 
37 class HueBaseEntity(Entity): # pylint: disable=hass-enforce-class-module
38  """Generic Entity Class for a Hue resource."""
39 
40  _attr_should_poll = False
41 
42  def __init__(
43  self,
44  bridge: HueBridge,
45  controller: BaseResourcesController,
46  resource: HueResource,
47  ) -> None:
48  """Initialize a generic Hue resource entity."""
49  self.bridgebridge = bridge
50  self.controllercontroller = controller
51  self.resourceresource = resource
52  self.devicedevice = controller.get_device(resource.id)
53  self.loggerlogger = bridge.logger.getChild(resource.type.value)
54 
55  # Entity class attributes
56  self._attr_unique_id_attr_unique_id = resource.id
57  # device is precreated in main handler
58  # this attaches the entity to the precreated device
59  if self.devicedevice is None:
60  # attach all device-less entities to the bridge itself
61  # e.g. config based sensors like entertainment area
62  self._attr_device_info_attr_device_info = DeviceInfo(
63  identifiers={(DOMAIN, bridge.api.config.bridge.bridge_id)},
64  )
65  else:
66  self._attr_device_info_attr_device_info = DeviceInfo(
67  identifiers={(DOMAIN, self.devicedevice.id)},
68  )
69  # used for availability workaround
70  self._ignore_availability_ignore_availability = None
71  self._last_state_last_state = None
72 
73  async def async_added_to_hass(self) -> None:
74  """Call when entity is added."""
75  self._check_availability_check_availability()
76  # Add value_changed callbacks.
77  self.async_on_removeasync_on_remove(
78  self.controllercontroller.subscribe(
79  self._handle_event_handle_event,
80  self.resourceresource.id,
81  (EventType.RESOURCE_UPDATED, EventType.RESOURCE_DELETED),
82  )
83  )
84  # also subscribe to device update event to catch device changes (e.g. name)
85  if self.devicedevice is None:
86  return
87  self.async_on_removeasync_on_remove(
88  self.bridgebridge.api.devices.subscribe(
89  self._handle_event_handle_event,
90  self.devicedevice.id,
91  EventType.RESOURCE_UPDATED,
92  )
93  )
94  # subscribe to zigbee_connectivity to catch availability changes
95  if zigbee := self.bridgebridge.api.devices.get_zigbee_connectivity(self.devicedevice.id):
96  self.async_on_removeasync_on_remove(
97  self.bridgebridge.api.sensors.zigbee_connectivity.subscribe(
98  self._handle_event_handle_event,
99  zigbee.id,
100  EventType.RESOURCE_UPDATED,
101  )
102  )
103 
104  @property
105  def available(self) -> bool:
106  """Return entity availability."""
107  # entities without a device attached should be always available
108  if self.devicedevice is None:
109  return True
110  # the zigbee connectivity sensor itself should be always available
111  if self.resourceresource.type == ResourceTypes.ZIGBEE_CONNECTIVITY:
112  return True
113  if self._ignore_availability_ignore_availability:
114  return True
115  # all device-attached entities get availability from the zigbee connectivity
116  if zigbee := self.bridgebridge.api.devices.get_zigbee_connectivity(self.devicedevice.id):
117  return zigbee.status == ConnectivityServiceStatus.CONNECTED
118  return True
119 
120  @callback
121  def on_update(self) -> None:
122  """Call on update event."""
123  # used in subclasses
124 
125  @callback
126  def _handle_event(self, event_type: EventType, resource: HueResource) -> None:
127  """Handle status event for this resource (or it's parent)."""
128  if event_type == EventType.RESOURCE_DELETED:
129  # cleanup entities that are not strictly device-bound and have the bridge as parent
130  if self.devicedevice is None and resource.id == self.resourceresource.id:
131  ent_reg = er.async_get(self.hasshass)
132  ent_reg.async_remove(self.entity_identity_id)
133  return
134 
135  self.loggerlogger.debug("Received status update for %s", self.entity_identity_id)
136  self._check_availability_check_availability()
137  self.on_updateon_update()
138  self.async_write_ha_stateasync_write_ha_state()
139 
140  @callback
142  """Check availability of the device."""
143  # return if we already processed this entity
144  if self._ignore_availability_ignore_availability is not None:
145  return
146  # only do the availability check for entities connected to a device (with `on` feature)
147  if self.devicedevice is None or not hasattr(self.resourceresource, "on"):
148  self._ignore_availability_ignore_availability = False
149  return
150  # ignore availability if user added device to ignore list
151  if self.devicedevice.id in self.bridgebridge.config_entry.options.get(
152  CONF_IGNORE_AVAILABILITY, []
153  ):
154  self._ignore_availability_ignore_availability = True
155  self.loggerlogger.info(
156  "Device %s is configured to ignore availability status. ",
157  self.namename,
158  )
159  return
160  # certified products (normally) report their state correctly
161  # no need for workaround/reporting
162  if self.devicedevice.product_data.certified:
163  self._ignore_availability_ignore_availability = False
164  return
165  # some (3th party) Hue lights report their connection status incorrectly
166  # causing the zigbee availability to report as disconnected while in fact
167  # it can be controlled. If the light is reported unavailable
168  # by the zigbee connectivity but the state changes its considered as a
169  # malfunctioning device and we report it.
170  # While the user should actually fix this issue, we allow to
171  # ignore the availability for this light/device from the config options.
172  cur_state = self.resourceresource.on.on
173  if self._last_state_last_state is None:
174  self._last_state_last_state = cur_state
175  return
176  if zigbee := self.bridgebridge.api.devices.get_zigbee_connectivity(self.devicedevice.id):
177  if (
178  self._last_state_last_state != cur_state
179  and zigbee.status != ConnectivityServiceStatus.CONNECTED
180  ):
181  # the device state changed from on->off or off->on
182  # while it was reported as not connected!
183  self.loggerlogger.warning(
184  (
185  "Device %s changed state while reported as disconnected. This"
186  " might be an indicator that routing is not working for this"
187  " device or the device is having connectivity issues. You can"
188  " disable availability reporting for this device in the Hue"
189  " options. Device details: %s - %s (%s) fw: %s"
190  ),
191  self.namename,
192  self.devicedevice.product_data.manufacturer_name,
193  self.devicedevice.product_data.product_name,
194  self.devicedevice.product_data.model_id,
195  self.devicedevice.product_data.software_version,
196  )
197  # set attribute to false because we only want to log once per light/device.
198  # a user must opt-in to ignore availability through integration options
199  self._ignore_availability_ignore_availability = False
200  self._last_state_last_state = cur_state
None __init__(self, HueBridge bridge, BaseResourcesController controller, HueResource resource)
Definition: entity.py:47
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
str|UndefinedType|None name(self)
Definition: entity.py:738
Callable[[], None] subscribe(HomeAssistant hass, str topic, MessageCallbackType msg_callback, int qos=DEFAULT_QOS, str encoding="utf-8")
Definition: client.py:247