Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Platform for sensor integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from datetime import datetime, timedelta
8 from enum import StrEnum
9 from typing import Any
10 
11 from devolo_plc_api.device_api import ConnectedStationInfo, NeighborAPInfo
12 from devolo_plc_api.plcnet_api import REMOTE, DataRate, LogicalNetwork
13 
15  SensorDeviceClass,
16  SensorEntity,
17  SensorEntityDescription,
18  SensorStateClass,
19 )
20 from homeassistant.const import EntityCategory, UnitOfDataRate
21 from homeassistant.core import HomeAssistant
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
23 from homeassistant.util.dt import utcnow
24 
25 from . import DevoloHomeNetworkConfigEntry
26 from .const import (
27  CONNECTED_PLC_DEVICES,
28  CONNECTED_WIFI_CLIENTS,
29  LAST_RESTART,
30  NEIGHBORING_WIFI_NETWORKS,
31  PLC_RX_RATE,
32  PLC_TX_RATE,
33 )
34 from .coordinator import DevoloDataUpdateCoordinator
35 from .entity import DevoloCoordinatorEntity
36 
37 PARALLEL_UPDATES = 0
38 
39 
40 def _last_restart(runtime: int) -> datetime:
41  """Calculate uptime. As fetching the data might also take some time, let's floor to the nearest 5 seconds."""
42  now = utcnow()
43  return (
44  now
45  - timedelta(seconds=runtime)
46  - timedelta(seconds=(now.timestamp() - runtime) % 5)
47  )
48 
49 
50 type _CoordinatorDataType = (
51  LogicalNetwork | DataRate | list[ConnectedStationInfo] | list[NeighborAPInfo] | int
52 )
53 type _SensorDataType = int | float | datetime
54 
55 
56 class DataRateDirection(StrEnum):
57  """Direction of data transfer."""
58 
59  RX = "rx_rate"
60  TX = "tx_rate"
61 
62 
63 @dataclass(frozen=True, kw_only=True)
65  _CoordinatorDataT: _CoordinatorDataType,
66  _SensorDataT: _SensorDataType,
67 ](SensorEntityDescription):
68  """Describes devolo sensor entity."""
69 
70  value_func: Callable[[_CoordinatorDataT], _SensorDataT]
71 
72 
73 SENSOR_TYPES: dict[str, DevoloSensorEntityDescription[Any, Any]] = {
74  CONNECTED_PLC_DEVICES: DevoloSensorEntityDescription[LogicalNetwork, int](
75  key=CONNECTED_PLC_DEVICES,
76  entity_category=EntityCategory.DIAGNOSTIC,
77  entity_registry_enabled_default=False,
78  value_func=lambda data: len(
79  {device.mac_address_from for device in data.data_rates}
80  ),
81  ),
82  CONNECTED_WIFI_CLIENTS: DevoloSensorEntityDescription[
83  list[ConnectedStationInfo], int
84  ](
85  key=CONNECTED_WIFI_CLIENTS,
86  state_class=SensorStateClass.MEASUREMENT,
87  value_func=len,
88  ),
89  NEIGHBORING_WIFI_NETWORKS: DevoloSensorEntityDescription[list[NeighborAPInfo], int](
90  key=NEIGHBORING_WIFI_NETWORKS,
91  entity_category=EntityCategory.DIAGNOSTIC,
92  entity_registry_enabled_default=False,
93  value_func=len,
94  ),
95  PLC_RX_RATE: DevoloSensorEntityDescription[DataRate, float](
96  key=PLC_RX_RATE,
97  entity_category=EntityCategory.DIAGNOSTIC,
98  name="PLC downlink PHY rate",
99  device_class=SensorDeviceClass.DATA_RATE,
100  native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
101  value_func=lambda data: getattr(data, DataRateDirection.RX, 0),
102  suggested_display_precision=0,
103  ),
104  PLC_TX_RATE: DevoloSensorEntityDescription[DataRate, float](
105  key=PLC_TX_RATE,
106  entity_category=EntityCategory.DIAGNOSTIC,
107  name="PLC uplink PHY rate",
108  device_class=SensorDeviceClass.DATA_RATE,
109  native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
110  value_func=lambda data: getattr(data, DataRateDirection.TX, 0),
111  suggested_display_precision=0,
112  ),
113  LAST_RESTART: DevoloSensorEntityDescription[int, datetime](
114  key=LAST_RESTART,
115  entity_category=EntityCategory.DIAGNOSTIC,
116  entity_registry_enabled_default=False,
117  device_class=SensorDeviceClass.TIMESTAMP,
118  value_func=_last_restart,
119  ),
120 }
121 
122 
124  hass: HomeAssistant,
125  entry: DevoloHomeNetworkConfigEntry,
126  async_add_entities: AddEntitiesCallback,
127 ) -> None:
128  """Get all devices and sensors and setup them via config entry."""
129  device = entry.runtime_data.device
130  coordinators = entry.runtime_data.coordinators
131 
132  entities: list[BaseDevoloSensorEntity[Any, Any, Any]] = []
133  if device.plcnet:
134  entities.append(
136  entry,
137  coordinators[CONNECTED_PLC_DEVICES],
138  SENSOR_TYPES[CONNECTED_PLC_DEVICES],
139  )
140  )
141  network = await device.plcnet.async_get_network_overview()
142  peers = [
143  peer.mac_address for peer in network.devices if peer.topology == REMOTE
144  ]
145  for peer in peers:
146  entities.append(
148  entry,
149  coordinators[CONNECTED_PLC_DEVICES],
150  SENSOR_TYPES[PLC_TX_RATE],
151  peer,
152  )
153  )
154  entities.append(
156  entry,
157  coordinators[CONNECTED_PLC_DEVICES],
158  SENSOR_TYPES[PLC_RX_RATE],
159  peer,
160  )
161  )
162  if device.device and "restart" in device.device.features:
163  entities.append(
165  entry,
166  coordinators[LAST_RESTART],
167  SENSOR_TYPES[LAST_RESTART],
168  )
169  )
170  if device.device and "wifi1" in device.device.features:
171  entities.append(
173  entry,
174  coordinators[CONNECTED_WIFI_CLIENTS],
175  SENSOR_TYPES[CONNECTED_WIFI_CLIENTS],
176  )
177  )
178  entities.append(
180  entry,
181  coordinators[NEIGHBORING_WIFI_NETWORKS],
182  SENSOR_TYPES[NEIGHBORING_WIFI_NETWORKS],
183  )
184  )
185  async_add_entities(entities)
186 
187 
189  _CoordinatorDataT: _CoordinatorDataType,
190  _ValueDataT: _CoordinatorDataType,
191  _SensorDataT: _SensorDataType,
192 ](
193  DevoloCoordinatorEntity[_CoordinatorDataT],
194  SensorEntity,
195 ):
196  """Representation of a devolo sensor."""
197 
198  def __init__(
199  self,
200  entry: DevoloHomeNetworkConfigEntry,
201  coordinator: DevoloDataUpdateCoordinator[_CoordinatorDataT],
202  description: DevoloSensorEntityDescription[_ValueDataT, _SensorDataT],
203  ) -> None:
204  """Initialize entity."""
205  self.entity_description = description
206  super().__init__(entry, coordinator)
207 
208 
210  _CoordinatorDataT: _CoordinatorDataType,
211  _ValueDataT: _CoordinatorDataType,
212  _SensorDataT: _SensorDataType,
213 ](BaseDevoloSensorEntity[_CoordinatorDataT, _ValueDataT, _SensorDataT]):
214  """Representation of a generic devolo sensor."""
215 
216  entity_description: DevoloSensorEntityDescription[_CoordinatorDataT, _SensorDataT]
217 
218  @property
219  def native_value(self) -> int | float | datetime:
220  """State of the sensor."""
221  return self.entity_description.value_func(self.coordinator.data)
222 
223 
225  BaseDevoloSensorEntity[LogicalNetwork, DataRate, float]
226 ):
227  """Representation of a devolo PLC data rate sensor."""
228 
229  entity_description: DevoloSensorEntityDescription[DataRate, float]
230 
231  def __init__(
232  self,
233  entry: DevoloHomeNetworkConfigEntry,
234  coordinator: DevoloDataUpdateCoordinator[LogicalNetwork],
235  description: DevoloSensorEntityDescription[DataRate, float],
236  peer: str,
237  ) -> None:
238  """Initialize entity."""
239  super().__init__(entry, coordinator, description)
240  self._peer_peer = peer
241  peer_device = next(
242  device
243  for device in self.coordinator.data.devices
244  if device.mac_address == peer
245  )
246 
247  self._attr_unique_id_attr_unique_id = f"{self._attr_unique_id}_{peer}"
248  self._attr_name_attr_name = f"{description.name} ({peer_device.user_device_name})"
249  self._attr_entity_registry_enabled_default_attr_entity_registry_enabled_default = peer_device.attached_to_router
250 
251  @property
252  def native_value(self) -> float:
253  """State of the sensor."""
254  return self.entity_description.value_func(
255  next(
256  data_rate
257  for data_rate in self.coordinator.data.data_rates
258  if data_rate.mac_address_from == self.device.mac
259  and data_rate.mac_address_to == self._peer_peer
260  )
261  )
None __init__(self, DevoloHomeNetworkConfigEntry entry, DevoloDataUpdateCoordinator[LogicalNetwork] coordinator, DevoloSensorEntityDescription[DataRate, float] description, str peer)
Definition: sensor.py:237
None async_setup_entry(HomeAssistant hass, DevoloHomeNetworkConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:127
None __init__(self, DevoloHomeNetworkConfigEntry entry, DevoloDataUpdateCoordinator[_CoordinatorDataT] coordinator, DevoloSensorEntityDescription[_ValueDataT, _SensorDataT] description)
Definition: sensor.py:203