Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for the QNAP QSW sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass, replace
7 from datetime import datetime
8 from typing import Final
9 
10 from aioqsw.const import (
11  QSD_FAN1_SPEED,
12  QSD_FAN2_SPEED,
13  QSD_LACP_PORTS,
14  QSD_LINK,
15  QSD_PORT_NUM,
16  QSD_PORTS,
17  QSD_PORTS_STATISTICS,
18  QSD_PORTS_STATUS,
19  QSD_RX_ERRORS,
20  QSD_RX_OCTETS,
21  QSD_RX_SPEED,
22  QSD_SPEED,
23  QSD_SYSTEM_BOARD,
24  QSD_SYSTEM_SENSOR,
25  QSD_SYSTEM_TIME,
26  QSD_TEMP,
27  QSD_TEMP_MAX,
28  QSD_TX_OCTETS,
29  QSD_TX_SPEED,
30  QSD_UPTIME_SECONDS,
31  QSD_UPTIME_TIMESTAMP,
32 )
33 
34 from homeassistant.components.automation import automations_with_entity
35 from homeassistant.components.script import scripts_with_entity
37  SensorDeviceClass,
38  SensorEntity,
39  SensorEntityDescription,
40  SensorStateClass,
41 )
42 from homeassistant.config_entries import ConfigEntry
43 from homeassistant.const import (
44  EntityCategory,
45  UnitOfDataRate,
46  UnitOfInformation,
47  UnitOfTemperature,
48  UnitOfTime,
49 )
50 from homeassistant.core import HomeAssistant, callback
51 from homeassistant.helpers import entity_registry as er, issue_registry as ir
52 from homeassistant.helpers.entity_platform import AddEntitiesCallback
53 from homeassistant.helpers.typing import UNDEFINED, StateType
54 from homeassistant.util import dt as dt_util
55 
56 from .const import ATTR_MAX, DOMAIN, QSW_COORD_DATA, RPM
57 from .coordinator import QswDataCoordinator
58 from .entity import QswEntityDescription, QswEntityType, QswSensorEntity
59 
60 
61 @dataclass(frozen=True)
63  """A class that describes QNAP QSW sensor entities."""
64 
65  attributes: dict[str, list[str]] | None = None
66  qsw_type: QswEntityType | None = None
67  sep_key: str = "_"
68  value_fn: Callable[[str], datetime | StateType] = lambda value: value
69 
70 
71 DEPRECATED_UPTIME_SECONDS = QswSensorEntityDescription(
72  translation_key="uptime",
73  key=QSD_SYSTEM_TIME,
74  entity_category=EntityCategory.DIAGNOSTIC,
75  native_unit_of_measurement=UnitOfTime.SECONDS,
76  state_class=SensorStateClass.TOTAL_INCREASING,
77  subkey=QSD_UPTIME_SECONDS,
78 )
79 
80 
81 SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = (
83  translation_key="fan_1_speed",
84  key=QSD_SYSTEM_SENSOR,
85  native_unit_of_measurement=RPM,
86  state_class=SensorStateClass.MEASUREMENT,
87  subkey=QSD_FAN1_SPEED,
88  ),
90  translation_key="fan_2_speed",
91  key=QSD_SYSTEM_SENSOR,
92  native_unit_of_measurement=RPM,
93  state_class=SensorStateClass.MEASUREMENT,
94  subkey=QSD_FAN2_SPEED,
95  ),
97  translation_key="ports",
98  attributes={
99  ATTR_MAX: [QSD_SYSTEM_BOARD, QSD_PORT_NUM],
100  },
101  entity_registry_enabled_default=False,
102  key=QSD_PORTS_STATUS,
103  state_class=SensorStateClass.MEASUREMENT,
104  subkey=QSD_LINK,
105  ),
107  entity_registry_enabled_default=False,
108  translation_key="rx",
109  device_class=SensorDeviceClass.DATA_SIZE,
110  key=QSD_PORTS_STATISTICS,
111  native_unit_of_measurement=UnitOfInformation.BYTES,
112  state_class=SensorStateClass.TOTAL_INCREASING,
113  subkey=QSD_RX_OCTETS,
114  ),
116  entity_registry_enabled_default=False,
117  translation_key="rx_errors",
118  key=QSD_PORTS_STATISTICS,
119  entity_category=EntityCategory.DIAGNOSTIC,
120  state_class=SensorStateClass.TOTAL_INCREASING,
121  subkey=QSD_RX_ERRORS,
122  ),
124  entity_registry_enabled_default=False,
125  translation_key="rx_speed",
126  device_class=SensorDeviceClass.DATA_RATE,
127  key=QSD_PORTS_STATISTICS,
128  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
129  state_class=SensorStateClass.MEASUREMENT,
130  subkey=QSD_RX_SPEED,
131  ),
133  attributes={
134  ATTR_MAX: [QSD_SYSTEM_SENSOR, QSD_TEMP_MAX],
135  },
136  device_class=SensorDeviceClass.TEMPERATURE,
137  key=QSD_SYSTEM_SENSOR,
138  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
139  state_class=SensorStateClass.MEASUREMENT,
140  subkey=QSD_TEMP,
141  ),
143  entity_registry_enabled_default=False,
144  translation_key="tx",
145  device_class=SensorDeviceClass.DATA_SIZE,
146  key=QSD_PORTS_STATISTICS,
147  native_unit_of_measurement=UnitOfInformation.BYTES,
148  state_class=SensorStateClass.TOTAL_INCREASING,
149  subkey=QSD_TX_OCTETS,
150  ),
152  entity_registry_enabled_default=False,
153  translation_key="tx_speed",
154  device_class=SensorDeviceClass.DATA_RATE,
155  key=QSD_PORTS_STATISTICS,
156  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
157  state_class=SensorStateClass.MEASUREMENT,
158  subkey=QSD_TX_SPEED,
159  ),
161  translation_key="uptime_timestamp",
162  key=QSD_SYSTEM_TIME,
163  device_class=SensorDeviceClass.TIMESTAMP,
164  entity_category=EntityCategory.DIAGNOSTIC,
165  subkey=QSD_UPTIME_TIMESTAMP,
166  value_fn=dt_util.parse_datetime,
167  ),
168 )
169 
170 LACP_PORT_SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = (
172  device_class=SensorDeviceClass.DATA_RATE,
173  entity_registry_enabled_default=False,
174  icon="mdi:speedometer",
175  key=QSD_PORTS_STATUS,
176  name="Link Speed",
177  native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
178  qsw_type=QswEntityType.LACP_PORT,
179  state_class=SensorStateClass.MEASUREMENT,
180  subkey=QSD_SPEED,
181  ),
183  entity_registry_enabled_default=False,
184  icon="mdi:download-network",
185  key=QSD_PORTS_STATISTICS,
186  name="RX",
187  native_unit_of_measurement=UnitOfInformation.BYTES,
188  qsw_type=QswEntityType.LACP_PORT,
189  state_class=SensorStateClass.TOTAL_INCREASING,
190  subkey=QSD_RX_OCTETS,
191  ),
193  entity_registry_enabled_default=False,
194  icon="mdi:close-network",
195  key=QSD_PORTS_STATISTICS,
196  entity_category=EntityCategory.DIAGNOSTIC,
197  name="RX Errors",
198  qsw_type=QswEntityType.LACP_PORT,
199  state_class=SensorStateClass.TOTAL_INCREASING,
200  subkey=QSD_RX_ERRORS,
201  ),
203  device_class=SensorDeviceClass.DATA_RATE,
204  entity_registry_enabled_default=False,
205  icon="mdi:download-network",
206  key=QSD_PORTS_STATISTICS,
207  name="RX Speed",
208  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
209  qsw_type=QswEntityType.LACP_PORT,
210  state_class=SensorStateClass.MEASUREMENT,
211  subkey=QSD_RX_SPEED,
212  ),
214  entity_registry_enabled_default=False,
215  icon="mdi:upload-network",
216  key=QSD_PORTS_STATISTICS,
217  name="TX",
218  native_unit_of_measurement=UnitOfInformation.BYTES,
219  qsw_type=QswEntityType.LACP_PORT,
220  state_class=SensorStateClass.TOTAL_INCREASING,
221  subkey=QSD_TX_OCTETS,
222  ),
224  device_class=SensorDeviceClass.DATA_RATE,
225  entity_registry_enabled_default=False,
226  icon="mdi:upload-network",
227  key=QSD_PORTS_STATISTICS,
228  name="TX Speed",
229  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
230  qsw_type=QswEntityType.LACP_PORT,
231  state_class=SensorStateClass.MEASUREMENT,
232  subkey=QSD_TX_SPEED,
233  ),
234 )
235 
236 PORT_SENSOR_TYPES: Final[tuple[QswSensorEntityDescription, ...]] = (
238  device_class=SensorDeviceClass.DATA_RATE,
239  entity_registry_enabled_default=False,
240  icon="mdi:speedometer",
241  key=QSD_PORTS_STATUS,
242  name="Link Speed",
243  native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
244  qsw_type=QswEntityType.PORT,
245  state_class=SensorStateClass.MEASUREMENT,
246  subkey=QSD_SPEED,
247  ),
249  entity_registry_enabled_default=False,
250  icon="mdi:download-network",
251  key=QSD_PORTS_STATISTICS,
252  name="RX",
253  native_unit_of_measurement=UnitOfInformation.BYTES,
254  qsw_type=QswEntityType.PORT,
255  state_class=SensorStateClass.TOTAL_INCREASING,
256  subkey=QSD_RX_OCTETS,
257  ),
259  entity_registry_enabled_default=False,
260  icon="mdi:close-network",
261  key=QSD_PORTS_STATISTICS,
262  entity_category=EntityCategory.DIAGNOSTIC,
263  name="RX Errors",
264  qsw_type=QswEntityType.PORT,
265  state_class=SensorStateClass.TOTAL_INCREASING,
266  subkey=QSD_RX_ERRORS,
267  ),
269  device_class=SensorDeviceClass.DATA_RATE,
270  entity_registry_enabled_default=False,
271  icon="mdi:download-network",
272  key=QSD_PORTS_STATISTICS,
273  name="RX Speed",
274  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
275  qsw_type=QswEntityType.PORT,
276  state_class=SensorStateClass.MEASUREMENT,
277  subkey=QSD_RX_SPEED,
278  ),
280  entity_registry_enabled_default=False,
281  icon="mdi:upload-network",
282  key=QSD_PORTS_STATISTICS,
283  name="TX",
284  native_unit_of_measurement=UnitOfInformation.BYTES,
285  qsw_type=QswEntityType.PORT,
286  state_class=SensorStateClass.TOTAL_INCREASING,
287  subkey=QSD_TX_OCTETS,
288  ),
290  device_class=SensorDeviceClass.DATA_RATE,
291  entity_registry_enabled_default=False,
292  icon="mdi:upload-network",
293  key=QSD_PORTS_STATISTICS,
294  name="TX Speed",
295  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
296  qsw_type=QswEntityType.PORT,
297  state_class=SensorStateClass.MEASUREMENT,
298  subkey=QSD_TX_SPEED,
299  ),
300 )
301 
302 
304  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
305 ) -> None:
306  """Add QNAP QSW sensors from a config_entry."""
307  coordinator: QswDataCoordinator = hass.data[DOMAIN][entry.entry_id][QSW_COORD_DATA]
308 
309  entities: list[QswSensor] = [
310  QswSensor(coordinator, description, entry)
311  for description in SENSOR_TYPES
312  if (
313  description.key in coordinator.data
314  and description.subkey in coordinator.data[description.key]
315  )
316  ]
317 
318  for description in LACP_PORT_SENSOR_TYPES:
319  if (
320  description.key not in coordinator.data
321  or QSD_LACP_PORTS not in coordinator.data[description.key]
322  ):
323  continue
324 
325  for port_id, port_values in coordinator.data[description.key][
326  QSD_LACP_PORTS
327  ].items():
328  if description.subkey not in port_values:
329  continue
330 
331  _desc = replace(
332  description,
333  sep_key=f"_lacp_port_{port_id}_",
334  name=f"LACP Port {port_id} {description.name}",
335  )
336  entities.append(QswSensor(coordinator, _desc, entry, port_id))
337 
338  for description in PORT_SENSOR_TYPES:
339  if (
340  description.key not in coordinator.data
341  or QSD_PORTS not in coordinator.data[description.key]
342  ):
343  continue
344 
345  for port_id, port_values in coordinator.data[description.key][
346  QSD_PORTS
347  ].items():
348  if description.subkey not in port_values:
349  continue
350 
351  _desc = replace(
352  description,
353  sep_key=f"_port_{port_id}_",
354  name=f"Port {port_id} {description.name}",
355  )
356  entities.append(QswSensor(coordinator, _desc, entry, port_id))
357 
358  # Can be removed in HA 2025.5.0
359  entity_reg = er.async_get(hass)
360  reg_entities = er.async_entries_for_config_entry(entity_reg, entry.entry_id)
361  for entity in reg_entities:
362  if entity.domain == "sensor" and entity.unique_id.endswith(
363  ("_uptime", "_uptime_seconds")
364  ):
365  entity_id = entity.entity_id
366 
367  if entity.disabled:
368  entity_reg.async_remove(entity_id)
369  continue
370 
371  if (
372  DEPRECATED_UPTIME_SECONDS.key in coordinator.data
373  and DEPRECATED_UPTIME_SECONDS.subkey
374  in coordinator.data[DEPRECATED_UPTIME_SECONDS.key]
375  ):
376  entities.append(
377  QswSensor(coordinator, DEPRECATED_UPTIME_SECONDS, entry)
378  )
379 
380  entity_automations = automations_with_entity(hass, entity_id)
381  entity_scripts = scripts_with_entity(hass, entity_id)
382 
383  for item in entity_automations + entity_scripts:
384  ir.async_create_issue(
385  hass,
386  DOMAIN,
387  f"uptime_seconds_deprecated_{entity_id}_{item}",
388  breaks_in_ha_version="2025.5.0",
389  is_fixable=False,
390  severity=ir.IssueSeverity.WARNING,
391  translation_key="uptime_seconds_deprecated",
392  translation_placeholders={
393  "entity": entity_id,
394  "info": item,
395  },
396  )
397 
398  async_add_entities(entities)
399 
400 
402  """Define a QNAP QSW sensor."""
403 
404  entity_description: QswSensorEntityDescription
405 
406  def __init__(
407  self,
408  coordinator: QswDataCoordinator,
409  description: QswSensorEntityDescription,
410  entry: ConfigEntry,
411  type_id: int | None = None,
412  ) -> None:
413  """Initialize."""
414  super().__init__(coordinator, entry, type_id)
415 
416  if description.name == UNDEFINED:
417  self._attr_has_entity_name_attr_has_entity_name = True
418  else:
419  self._attr_name_attr_name = f"{self.product} {description.name}"
420  self._attr_unique_id_attr_unique_id = (
421  f"{entry.unique_id}_{description.key}"
422  f"{description.sep_key}{description.subkey}"
423  )
424  self.entity_descriptionentity_description = description
425  self._async_update_attrs_async_update_attrs_async_update_attrs()
426 
427  @callback
428  def _async_update_attrs(self) -> None:
429  """Update sensor attributes."""
430  value = self.get_device_valueget_device_value(
431  self.entity_descriptionentity_description.key,
432  self.entity_descriptionentity_description.subkey,
433  self.entity_descriptionentity_description.qsw_type,
434  )
435  self._attr_native_value_attr_native_value = self.entity_descriptionentity_description.value_fn(value)
436  super()._async_update_attrs()
Any get_device_value(self, str key, str subkey, QswEntityType|None qsw_type=None)
Definition: entity.py:70
None __init__(self, QswDataCoordinator coordinator, QswSensorEntityDescription description, ConfigEntry entry, int|None type_id=None)
Definition: sensor.py:412
list[str] automations_with_entity(HomeAssistant hass, str entity_id)
Definition: __init__.py:191
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:305
list[str] scripts_with_entity(HomeAssistant hass, str entity_id)
Definition: __init__.py:126