Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Handle MySensors devices."""
2 
3 from __future__ import annotations
4 
5 from abc import abstractmethod
6 import logging
7 from typing import Any
8 
9 from mysensors import BaseAsyncGateway, Sensor
10 from mysensors.sensor import ChildSensor
11 
12 from homeassistant.const import (
13  ATTR_BATTERY_LEVEL,
14  CONF_DEVICE,
15  STATE_OFF,
16  STATE_ON,
17  Platform,
18 )
19 from homeassistant.core import HomeAssistant, callback
20 from homeassistant.helpers.debounce import Debouncer
21 from homeassistant.helpers.device_registry import DeviceInfo
22 from homeassistant.helpers.dispatcher import async_dispatcher_connect
23 from homeassistant.helpers.entity import Entity
24 
25 from .const import (
26  CHILD_CALLBACK,
27  DOMAIN,
28  NODE_CALLBACK,
29  PLATFORM_TYPES,
30  UPDATE_DELAY,
31  DevId,
32  GatewayId,
33 )
34 
35 _LOGGER = logging.getLogger(__name__)
36 
37 ATTR_CHILD_ID = "child_id"
38 ATTR_DESCRIPTION = "description"
39 ATTR_DEVICE = "device"
40 ATTR_NODE_ID = "node_id"
41 ATTR_HEARTBEAT = "heartbeat"
42 MYSENSORS_PLATFORM_DEVICES = "mysensors_devices_{}"
43 
44 
46  """Representation of a MySensors device."""
47 
48  hass: HomeAssistant
49 
50  def __init__(
51  self, gateway_id: GatewayId, gateway: BaseAsyncGateway, node_id: int
52  ) -> None:
53  """Set up the MySensors node entity."""
54  self.gateway_id: GatewayId = gateway_id
55  self.gateway: BaseAsyncGateway = gateway
56  self.node_id: int = node_id
57  self._debouncer_debouncer: Debouncer | None = None
58 
59  @property
60  def _node(self) -> Sensor:
61  return self.gateway.sensors[self.node_id]
62 
63  @property
64  def sketch_name(self) -> str:
65  """Return the name of the sketch running on the whole node.
66 
67  The name will be the same for several entities.
68  """
69  return self._node_node.sketch_name # type: ignore[no-any-return]
70 
71  @property
72  def sketch_version(self) -> str:
73  """Return the version of the sketch running on the whole node.
74 
75  The name will be the same for several entities.
76  """
77  return self._node_node.sketch_version # type: ignore[no-any-return]
78 
79  @property
80  def node_name(self) -> str:
81  """Name of the whole node.
82 
83  The name will be the same for several entities.
84  """
85  return f"{self.sketch_name} {self.node_id}"
86 
87  @property
88  def device_info(self) -> DeviceInfo:
89  """Return the device info."""
90  return DeviceInfo(
91  identifiers={(DOMAIN, f"{self.gateway_id}-{self.node_id}")},
92  manufacturer=DOMAIN,
93  name=self.node_namenode_name,
94  sw_version=self.sketch_versionsketch_version,
95  )
96 
97  @property
98  def extra_state_attributes(self) -> dict[str, Any]:
99  """Return device specific attributes."""
100  node = self.gateway.sensors[self.node_id]
101 
102  return {
103  ATTR_HEARTBEAT: node.heartbeat,
104  ATTR_NODE_ID: self.node_id,
105  }
106 
107  @callback
108  @abstractmethod
109  def _async_update_callback(self) -> None:
110  """Update the device."""
111 
112  async def async_update_callback(self) -> None:
113  """Update the device after delay."""
114  if not self._debouncer:
115  self._debouncer_debouncer = Debouncer(
116  self.hasshass,
117  _LOGGER,
118  cooldown=UPDATE_DELAY,
119  immediate=False,
120  function=self._async_update_callback_async_update_callback,
121  )
122 
123  await self._debouncer_debouncer.async_call()
124 
125  async def async_added_to_hass(self) -> None:
126  """Register update callback."""
127  self.async_on_removeasync_on_remove(
129  self.hasshass,
130  NODE_CALLBACK.format(self.gateway_id, self.node_id),
131  self.async_update_callbackasync_update_callback,
132  )
133  )
134  self._async_update_callback_async_update_callback()
135 
136 
138  hass: HomeAssistant, domain: Platform
139 ) -> dict[DevId, MySensorsChildEntity]:
140  """Return MySensors devices for a hass platform name."""
141  if MYSENSORS_PLATFORM_DEVICES.format(domain) not in hass.data[DOMAIN]:
142  hass.data[DOMAIN][MYSENSORS_PLATFORM_DEVICES.format(domain)] = {}
143  devices: dict[DevId, MySensorsChildEntity] = hass.data[DOMAIN][
144  MYSENSORS_PLATFORM_DEVICES.format(domain)
145  ]
146  return devices
147 
148 
150  """Representation of a MySensors entity."""
151 
152  _attr_should_poll = False
153 
154  def __init__(
155  self,
156  gateway_id: GatewayId,
157  gateway: BaseAsyncGateway,
158  node_id: int,
159  child_id: int,
160  value_type: int,
161  ) -> None:
162  """Set up the MySensors child entity."""
163  super().__init__(gateway_id, gateway, node_id)
164  self.child_id: int = child_id
165  # value_type as int. string variant can be looked up in gateway consts
166  self.value_type: int = value_type
167  self.child_typechild_type = self._child_child.type
168  self._values: dict[int, Any] = {}
169 
170  @property
171  def dev_id(self) -> DevId:
172  """Return the DevId of this device.
173 
174  It is used to route incoming MySensors messages to the correct device/entity.
175  """
176  return self.gateway_id, self.node_id, self.child_id, self.value_type
177 
178  @property
179  def _child(self) -> ChildSensor:
180  return self._node_node.children[self.child_id]
181 
182  @property
183  def unique_id(self) -> str:
184  """Return a unique ID for use in home assistant."""
185  return f"{self.gateway_id}-{self.node_id}-{self.child_id}-{self.value_type}"
186 
187  @property
188  def name(self) -> str:
189  """Return the name of this entity."""
190  child = self._child_child
191 
192  if child.description:
193  return str(child.description)
194  return f"{self.node_name} {self.child_id}"
195 
196  async def async_will_remove_from_hass(self) -> None:
197  """Remove this entity from home assistant."""
198  for platform in PLATFORM_TYPES:
199  platform_str = MYSENSORS_PLATFORM_DEVICES.format(platform)
200  if platform_str in self.hasshass.data[DOMAIN]:
201  platform_dict = self.hasshass.data[DOMAIN][platform_str]
202  if self.dev_iddev_id in platform_dict:
203  del platform_dict[self.dev_iddev_id]
204  _LOGGER.debug("Deleted %s from platform %s", self.dev_iddev_id, platform)
205 
206  @property
207  def available(self) -> bool:
208  """Return true if entity is available."""
209  return self.value_type in self._values
210 
211  @property
212  def extra_state_attributes(self) -> dict[str, Any]:
213  """Return entity and device specific state attributes."""
214  attr = super().extra_state_attributes
215 
216  assert self.platformplatform.config_entry
217  attr[ATTR_DEVICE] = self.platformplatform.config_entry.data[CONF_DEVICE]
218 
219  attr[ATTR_CHILD_ID] = self.child_id
220  attr[ATTR_DESCRIPTION] = self._child_child.description
221  # We should deprecate the battery level attribute in the future.
222  attr[ATTR_BATTERY_LEVEL] = self._node_node.battery_level
223 
224  set_req = self.gateway.const.SetReq
225  for value_type, value in self._values.items():
226  attr[set_req(value_type).name] = value
227 
228  return attr
229 
230  @callback
231  def _async_update(self) -> None:
232  """Update the controller with the latest value from a sensor."""
233  set_req = self.gateway.const.SetReq
234  for value_type, value in self._child_child.values.items():
235  _LOGGER.debug(
236  "Entity update: %s: value_type %s, value = %s",
237  self.namenamename,
238  value_type,
239  value,
240  )
241  if value_type in (
242  set_req.V_ARMED,
243  set_req.V_LIGHT,
244  set_req.V_LOCK_STATUS,
245  set_req.V_TRIPPED,
246  set_req.V_UP,
247  set_req.V_DOWN,
248  set_req.V_STOP,
249  ):
250  self._values[value_type] = STATE_ON if int(value) == 1 else STATE_OFF
251  elif value_type == set_req.V_DIMMER:
252  self._values[value_type] = int(value)
253  else:
254  self._values[value_type] = value
255 
256  @callback
257  def _async_update_callback(self) -> None:
258  """Update the entity."""
259  self._async_update_async_update()
260  self.async_write_ha_stateasync_write_ha_state()
261 
262  async def async_added_to_hass(self) -> None:
263  """Register update callback."""
264  await super().async_added_to_hass()
265  self.async_on_removeasync_on_remove(
267  self.hasshass,
268  CHILD_CALLBACK.format(*self.dev_iddev_id),
269  self.async_update_callbackasync_update_callback,
270  )
271  )
None __init__(self, GatewayId gateway_id, BaseAsyncGateway gateway, int node_id)
Definition: entity.py:52
None __init__(self, GatewayId gateway_id, BaseAsyncGateway gateway, int node_id, int child_id, int value_type)
Definition: entity.py:161
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
str|UndefinedType|None name(self)
Definition: entity.py:738
dict[DevId, MySensorsChildEntity] get_mysensors_devices(HomeAssistant hass, Platform domain)
Definition: entity.py:139
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103