Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Ecovacs sensor module."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from typing import Any, Generic
8 
9 from deebot_client.capabilities import CapabilityEvent, CapabilityLifeSpan
10 from deebot_client.events import (
11  BatteryEvent,
12  ErrorEvent,
13  Event,
14  LifeSpan,
15  LifeSpanEvent,
16  NetworkInfoEvent,
17  StatsEvent,
18  TotalStatsEvent,
19 )
20 from sucks import VacBot
21 
23  SensorDeviceClass,
24  SensorEntity,
25  SensorEntityDescription,
26  SensorStateClass,
27 )
28 from homeassistant.const import (
29  ATTR_BATTERY_LEVEL,
30  CONF_DESCRIPTION,
31  PERCENTAGE,
32  EntityCategory,
33  UnitOfArea,
34  UnitOfTime,
35 )
36 from homeassistant.core import HomeAssistant
37 from homeassistant.helpers.entity_platform import AddEntitiesCallback
38 from homeassistant.helpers.typing import StateType
39 
40 from . import EcovacsConfigEntry
41 from .const import LEGACY_SUPPORTED_LIFESPANS, SUPPORTED_LIFESPANS
42 from .entity import (
43  EcovacsCapabilityEntityDescription,
44  EcovacsDescriptionEntity,
45  EcovacsEntity,
46  EcovacsLegacyEntity,
47  EventT,
48 )
49 from .util import get_supported_entitites
50 
51 
52 @dataclass(kw_only=True, frozen=True)
54  EcovacsCapabilityEntityDescription,
55  SensorEntityDescription,
56  Generic[EventT],
57 ):
58  """Ecovacs sensor entity description."""
59 
60  value_fn: Callable[[EventT], StateType]
61 
62 
63 ENTITY_DESCRIPTIONS: tuple[EcovacsSensorEntityDescription, ...] = (
64  # Stats
65  EcovacsSensorEntityDescription[StatsEvent](
66  key="stats_area",
67  capability_fn=lambda caps: caps.stats.clean,
68  value_fn=lambda e: e.area,
69  translation_key="stats_area",
70  native_unit_of_measurement=UnitOfArea.SQUARE_METERS,
71  ),
72  EcovacsSensorEntityDescription[StatsEvent](
73  key="stats_time",
74  capability_fn=lambda caps: caps.stats.clean,
75  value_fn=lambda e: e.time,
76  translation_key="stats_time",
77  device_class=SensorDeviceClass.DURATION,
78  native_unit_of_measurement=UnitOfTime.SECONDS,
79  suggested_unit_of_measurement=UnitOfTime.MINUTES,
80  ),
81  # TotalStats
82  EcovacsSensorEntityDescription[TotalStatsEvent](
83  capability_fn=lambda caps: caps.stats.total,
84  value_fn=lambda e: e.area,
85  key="total_stats_area",
86  translation_key="total_stats_area",
87  native_unit_of_measurement=UnitOfArea.SQUARE_METERS,
88  state_class=SensorStateClass.TOTAL_INCREASING,
89  ),
90  EcovacsSensorEntityDescription[TotalStatsEvent](
91  capability_fn=lambda caps: caps.stats.total,
92  value_fn=lambda e: e.time,
93  key="total_stats_time",
94  translation_key="total_stats_time",
95  device_class=SensorDeviceClass.DURATION,
96  native_unit_of_measurement=UnitOfTime.SECONDS,
97  suggested_unit_of_measurement=UnitOfTime.HOURS,
98  state_class=SensorStateClass.TOTAL_INCREASING,
99  ),
100  EcovacsSensorEntityDescription[TotalStatsEvent](
101  capability_fn=lambda caps: caps.stats.total,
102  value_fn=lambda e: e.cleanings,
103  key="total_stats_cleanings",
104  translation_key="total_stats_cleanings",
105  state_class=SensorStateClass.TOTAL_INCREASING,
106  ),
107  EcovacsSensorEntityDescription[BatteryEvent](
108  capability_fn=lambda caps: caps.battery,
109  value_fn=lambda e: e.value,
110  key=ATTR_BATTERY_LEVEL,
111  native_unit_of_measurement=PERCENTAGE,
112  device_class=SensorDeviceClass.BATTERY,
113  entity_category=EntityCategory.DIAGNOSTIC,
114  ),
115  EcovacsSensorEntityDescription[NetworkInfoEvent](
116  capability_fn=lambda caps: caps.network,
117  value_fn=lambda e: e.ip,
118  key="network_ip",
119  translation_key="network_ip",
120  entity_registry_enabled_default=False,
121  entity_category=EntityCategory.DIAGNOSTIC,
122  ),
123  EcovacsSensorEntityDescription[NetworkInfoEvent](
124  capability_fn=lambda caps: caps.network,
125  value_fn=lambda e: e.rssi,
126  key="network_rssi",
127  translation_key="network_rssi",
128  entity_registry_enabled_default=False,
129  entity_category=EntityCategory.DIAGNOSTIC,
130  ),
131  EcovacsSensorEntityDescription[NetworkInfoEvent](
132  capability_fn=lambda caps: caps.network,
133  value_fn=lambda e: e.ssid,
134  key="network_ssid",
135  translation_key="network_ssid",
136  entity_registry_enabled_default=False,
137  entity_category=EntityCategory.DIAGNOSTIC,
138  ),
139 )
140 
141 
142 @dataclass(kw_only=True, frozen=True)
144  """Ecovacs lifespan sensor entity description."""
145 
146  component: LifeSpan
147  value_fn: Callable[[LifeSpanEvent], int | float]
148 
149 
150 LIFESPAN_ENTITY_DESCRIPTIONS = tuple(
152  component=component,
153  value_fn=lambda e: e.percent,
154  key=f"lifespan_{component.name.lower()}",
155  translation_key=f"lifespan_{component.name.lower()}",
156  native_unit_of_measurement=PERCENTAGE,
157  entity_category=EntityCategory.DIAGNOSTIC,
158  )
159  for component in SUPPORTED_LIFESPANS
160 )
161 
162 
163 @dataclass(kw_only=True, frozen=True)
165  """Ecovacs lifespan sensor entity description."""
166 
167  component: str
168 
169 
170 LEGACY_LIFESPAN_SENSORS = tuple(
172  component=component,
173  key=f"lifespan_{component}",
174  translation_key=f"lifespan_{component}",
175  native_unit_of_measurement=PERCENTAGE,
176  entity_category=EntityCategory.DIAGNOSTIC,
177  )
178  for component in LEGACY_SUPPORTED_LIFESPANS
179 )
180 
181 
183  hass: HomeAssistant,
184  config_entry: EcovacsConfigEntry,
185  async_add_entities: AddEntitiesCallback,
186 ) -> None:
187  """Add entities for passed config_entry in HA."""
188  controller = config_entry.runtime_data
189 
190  entities: list[EcovacsEntity] = get_supported_entitites(
191  controller, EcovacsSensor, ENTITY_DESCRIPTIONS
192  )
193  entities.extend(
194  EcovacsLifespanSensor(device, device.capabilities.life_span, description)
195  for device in controller.devices
196  for description in LIFESPAN_ENTITY_DESCRIPTIONS
197  if description.component in device.capabilities.life_span.types
198  )
199  entities.extend(
200  EcovacsErrorSensor(device, capability)
201  for device in controller.devices
202  if (capability := device.capabilities.error)
203  )
204 
205  async_add_entities(entities)
206 
207  async def _add_legacy_entities() -> None:
208  entities = []
209  for device in controller.legacy_devices:
210  for description in LEGACY_LIFESPAN_SENSORS:
211  if (
212  description.component in device.components
213  and not controller.legacy_entity_is_added(
214  device, description.component
215  )
216  ):
217  controller.add_legacy_entity(device, description.component)
218  entities.append(EcovacsLegacyLifespanSensor(device, description))
219 
220  if entities:
221  async_add_entities(entities)
222 
223  def _fire_ecovacs_legacy_lifespan_event(_: Any) -> None:
224  hass.create_task(_add_legacy_entities())
225 
226  for device in controller.legacy_devices:
227  config_entry.async_on_unload(
228  device.lifespanEvents.subscribe(
229  _fire_ecovacs_legacy_lifespan_event
230  ).unsubscribe
231  )
232 
233 
235  EcovacsDescriptionEntity[CapabilityEvent],
236  SensorEntity,
237 ):
238  """Ecovacs sensor."""
239 
240  entity_description: EcovacsSensorEntityDescription
241 
242  async def async_added_to_hass(self) -> None:
243  """Set up the event listeners now that hass is ready."""
244  await super().async_added_to_hass()
245 
246  async def on_event(event: Event) -> None:
247  value = self.entity_descriptionentity_description.value_fn(event)
248  if value is None:
249  return
250 
251  self._attr_native_value_attr_native_value = value
252  self.async_write_ha_stateasync_write_ha_state()
253 
254  self._subscribe_subscribe(self._capability_capability.event, on_event)
255 
256 
258  EcovacsDescriptionEntity[CapabilityLifeSpan],
259  SensorEntity,
260 ):
261  """Lifespan sensor."""
262 
263  entity_description: EcovacsLifespanSensorEntityDescription
264 
265  async def async_added_to_hass(self) -> None:
266  """Set up the event listeners now that hass is ready."""
267  await super().async_added_to_hass()
268 
269  async def on_event(event: LifeSpanEvent) -> None:
270  if event.type == self.entity_descriptionentity_description.component:
271  self._attr_native_value_attr_native_value = self.entity_descriptionentity_description.value_fn(event)
272  self.async_write_ha_stateasync_write_ha_state()
273 
274  self._subscribe_subscribe(self._capability_capability.event, on_event)
275 
276 
278  EcovacsEntity[CapabilityEvent[ErrorEvent]],
279  SensorEntity,
280 ):
281  """Error sensor."""
282 
283  _always_available = True
284  _unrecorded_attributes = frozenset({CONF_DESCRIPTION})
285  entity_description: SensorEntityDescription = SensorEntityDescription(
286  key="error",
287  translation_key="error",
288  entity_registry_enabled_default=False,
289  entity_category=EntityCategory.DIAGNOSTIC,
290  )
291 
292  async def async_added_to_hass(self) -> None:
293  """Set up the event listeners now that hass is ready."""
294  await super().async_added_to_hass()
295 
296  async def on_event(event: ErrorEvent) -> None:
297  self._attr_native_value_attr_native_value = event.code
298  self._attr_extra_state_attributes_attr_extra_state_attributes = {CONF_DESCRIPTION: event.description}
299 
300  self.async_write_ha_stateasync_write_ha_state()
301 
302  self._subscribe_subscribe(self._capability_capability.event, on_event)
303 
304 
306  """Legacy Lifespan sensor."""
307 
308  entity_description: EcovacsLegacyLifespanSensorEntityDescription
309 
310  def __init__(
311  self,
312  device: VacBot,
313  description: EcovacsLegacyLifespanSensorEntityDescription,
314  ) -> None:
315  """Initialize the entity."""
316  super().__init__(device)
317  self.entity_descriptionentity_description = description
318  self._attr_unique_id_attr_unique_id_attr_unique_id = f"{device.vacuum['did']}_{description.key}"
319 
320  if (value := device.components.get(description.component)) is not None:
321  value = int(value * 100)
322  self._attr_native_value_attr_native_value = value
323 
324  async def async_added_to_hass(self) -> None:
325  """Set up the event listeners now that hass is ready."""
326 
327  def on_event(_: Any) -> None:
328  if (
329  value := self.devicedevice.components.get(self.entity_descriptionentity_description.component)
330  ) is not None:
331  value = int(value * 100)
332  self._attr_native_value_attr_native_value = value
333  self.schedule_update_ha_stateschedule_update_ha_state()
334 
335  self._event_listeners.append(self.devicedevice.lifespanEvents.subscribe(on_event))
None _subscribe(self, type[EventT] event_type, Callable[[EventT], Coroutine[Any, Any, None]] callback)
Definition: entity.py:87
None __init__(self, VacBot device, EcovacsLegacyLifespanSensorEntityDescription description)
Definition: sensor.py:314
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
None async_setup_entry(HomeAssistant hass, EcovacsConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:186
list[EcovacsEntity] get_supported_entitites(EcovacsController controller, type[EcovacsDescriptionEntity] entity_class, tuple[EcovacsCapabilityEntityDescription,...] descriptions)
Definition: util.py:37