Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for QNAP NAS Sensors."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 from typing import Any
7 
8 from homeassistant import config_entries
10  SensorDeviceClass,
11  SensorEntity,
12  SensorEntityDescription,
13  SensorStateClass,
14 )
15 from homeassistant.const import (
16  PERCENTAGE,
17  EntityCategory,
18  UnitOfDataRate,
19  UnitOfInformation,
20  UnitOfTemperature,
21 )
22 from homeassistant.core import HomeAssistant
23 from homeassistant.exceptions import PlatformNotReady
24 from homeassistant.helpers.device_registry import DeviceInfo
25 from homeassistant.helpers.entity_platform import AddEntitiesCallback
26 from homeassistant.helpers.update_coordinator import CoordinatorEntity
27 from homeassistant.util import dt as dt_util
28 
29 from .const import DOMAIN
30 from .coordinator import QnapCoordinator
31 
32 ATTR_DRIVE = "Drive"
33 ATTR_IP = "IP Address"
34 ATTR_MAC = "MAC Address"
35 ATTR_MASK = "Mask"
36 ATTR_MAX_SPEED = "Max Speed"
37 ATTR_MEMORY_SIZE = "Memory Size"
38 ATTR_MODEL = "Model"
39 ATTR_PACKETS_ERR = "Packets (Err)"
40 ATTR_SERIAL = "Serial #"
41 ATTR_TYPE = "Type"
42 ATTR_UPTIME = "Uptime"
43 ATTR_VOLUME_SIZE = "Volume Size"
44 
45 _SYSTEM_MON_COND: tuple[SensorEntityDescription, ...] = (
47  key="status",
48  translation_key="status",
49  entity_category=EntityCategory.DIAGNOSTIC,
50  ),
52  key="system_temp",
53  translation_key="system_temp",
54  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
55  device_class=SensorDeviceClass.TEMPERATURE,
56  entity_category=EntityCategory.DIAGNOSTIC,
57  state_class=SensorStateClass.MEASUREMENT,
58  ),
60  key="uptime",
61  translation_key="uptime",
62  device_class=SensorDeviceClass.TIMESTAMP,
63  entity_category=EntityCategory.DIAGNOSTIC,
64  entity_registry_enabled_default=False,
65  ),
66 )
67 _CPU_MON_COND: tuple[SensorEntityDescription, ...] = (
69  key="cpu_temp",
70  translation_key="cpu_temp",
71  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
72  device_class=SensorDeviceClass.TEMPERATURE,
73  entity_category=EntityCategory.DIAGNOSTIC,
74  entity_registry_enabled_default=False,
75  state_class=SensorStateClass.MEASUREMENT,
76  ),
78  key="cpu_usage",
79  translation_key="cpu_usage",
80  native_unit_of_measurement=PERCENTAGE,
81  entity_category=EntityCategory.DIAGNOSTIC,
82  state_class=SensorStateClass.MEASUREMENT,
83  suggested_display_precision=0,
84  ),
85 )
86 _MEMORY_MON_COND: tuple[SensorEntityDescription, ...] = (
88  key="memory_size",
89  translation_key="memory_size",
90  native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
91  device_class=SensorDeviceClass.DATA_SIZE,
92  entity_category=EntityCategory.DIAGNOSTIC,
93  entity_registry_enabled_default=False,
94  state_class=SensorStateClass.MEASUREMENT,
95  suggested_display_precision=1,
96  suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
97  ),
99  key="memory_free",
100  translation_key="memory_free",
101  native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
102  device_class=SensorDeviceClass.DATA_SIZE,
103  entity_category=EntityCategory.DIAGNOSTIC,
104  entity_registry_enabled_default=False,
105  state_class=SensorStateClass.MEASUREMENT,
106  suggested_display_precision=1,
107  suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
108  ),
110  key="memory_used",
111  translation_key="memory_used",
112  native_unit_of_measurement=UnitOfInformation.MEBIBYTES,
113  device_class=SensorDeviceClass.DATA_SIZE,
114  entity_category=EntityCategory.DIAGNOSTIC,
115  entity_registry_enabled_default=False,
116  state_class=SensorStateClass.MEASUREMENT,
117  suggested_display_precision=1,
118  suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
119  ),
121  key="memory_percent_used",
122  translation_key="memory_percent_used",
123  native_unit_of_measurement=PERCENTAGE,
124  entity_category=EntityCategory.DIAGNOSTIC,
125  state_class=SensorStateClass.MEASUREMENT,
126  suggested_display_precision=0,
127  ),
128 )
129 _NETWORK_MON_COND: tuple[SensorEntityDescription, ...] = (
131  key="network_link_status",
132  translation_key="network_link_status",
133  entity_category=EntityCategory.DIAGNOSTIC,
134  ),
136  key="network_tx",
137  translation_key="network_tx",
138  native_unit_of_measurement=UnitOfDataRate.BITS_PER_SECOND,
139  device_class=SensorDeviceClass.DATA_RATE,
140  entity_category=EntityCategory.DIAGNOSTIC,
141  entity_registry_enabled_default=False,
142  state_class=SensorStateClass.MEASUREMENT,
143  suggested_display_precision=1,
144  suggested_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
145  ),
147  key="network_rx",
148  translation_key="network_rx",
149  native_unit_of_measurement=UnitOfDataRate.BITS_PER_SECOND,
150  device_class=SensorDeviceClass.DATA_RATE,
151  entity_category=EntityCategory.DIAGNOSTIC,
152  entity_registry_enabled_default=False,
153  state_class=SensorStateClass.MEASUREMENT,
154  suggested_display_precision=1,
155  suggested_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
156  ),
158  key="network_err",
159  translation_key="network_err",
160  entity_category=EntityCategory.DIAGNOSTIC,
161  entity_registry_enabled_default=False,
162  state_class=SensorStateClass.MEASUREMENT,
163  ),
165  key="network_max_speed",
166  translation_key="network_max_speed",
167  native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
168  device_class=SensorDeviceClass.DATA_RATE,
169  entity_category=EntityCategory.DIAGNOSTIC,
170  entity_registry_enabled_default=False,
171  state_class=SensorStateClass.MEASUREMENT,
172  ),
173 )
174 _DRIVE_MON_COND: tuple[SensorEntityDescription, ...] = (
176  key="drive_smart_status",
177  translation_key="drive_smart_status",
178  entity_category=EntityCategory.DIAGNOSTIC,
179  entity_registry_enabled_default=False,
180  ),
182  key="drive_temp",
183  translation_key="drive_temp",
184  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
185  device_class=SensorDeviceClass.TEMPERATURE,
186  entity_category=EntityCategory.DIAGNOSTIC,
187  entity_registry_enabled_default=False,
188  state_class=SensorStateClass.MEASUREMENT,
189  ),
190 )
191 _VOLUME_MON_COND: tuple[SensorEntityDescription, ...] = (
193  key="volume_size_total",
194  translation_key="volume_size_total",
195  native_unit_of_measurement=UnitOfInformation.BYTES,
196  device_class=SensorDeviceClass.DATA_SIZE,
197  entity_category=EntityCategory.DIAGNOSTIC,
198  entity_registry_enabled_default=False,
199  state_class=SensorStateClass.MEASUREMENT,
200  suggested_display_precision=1,
201  suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
202  ),
204  key="volume_size_used",
205  translation_key="volume_size_used",
206  native_unit_of_measurement=UnitOfInformation.BYTES,
207  device_class=SensorDeviceClass.DATA_SIZE,
208  entity_category=EntityCategory.DIAGNOSTIC,
209  entity_registry_enabled_default=False,
210  state_class=SensorStateClass.MEASUREMENT,
211  suggested_display_precision=1,
212  suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
213  ),
215  key="volume_size_free",
216  translation_key="volume_size_free",
217  native_unit_of_measurement=UnitOfInformation.BYTES,
218  device_class=SensorDeviceClass.DATA_SIZE,
219  entity_category=EntityCategory.DIAGNOSTIC,
220  entity_registry_enabled_default=False,
221  state_class=SensorStateClass.MEASUREMENT,
222  suggested_display_precision=1,
223  suggested_unit_of_measurement=UnitOfInformation.GIBIBYTES,
224  ),
226  key="volume_percentage_used",
227  translation_key="volume_percentage_used",
228  native_unit_of_measurement=PERCENTAGE,
229  entity_category=EntityCategory.DIAGNOSTIC,
230  state_class=SensorStateClass.MEASUREMENT,
231  suggested_display_precision=0,
232  ),
233 )
234 
235 SENSOR_KEYS: list[str] = [
236  desc.key
237  for desc in (
238  *_SYSTEM_MON_COND,
239  *_CPU_MON_COND,
240  *_MEMORY_MON_COND,
241  *_NETWORK_MON_COND,
242  *_DRIVE_MON_COND,
243  *_VOLUME_MON_COND,
244  )
245 ]
246 
247 
249  hass: HomeAssistant,
250  config_entry: config_entries.ConfigEntry,
251  async_add_entities: AddEntitiesCallback,
252 ) -> None:
253  """Set up entry."""
254  coordinator = QnapCoordinator(hass, config_entry)
255  await coordinator.async_refresh()
256  if not coordinator.last_update_success:
257  raise PlatformNotReady
258  uid = config_entry.unique_id
259  assert uid is not None
260  sensors: list[QNAPSensor] = []
261 
262  sensors.extend(
263  [
264  QNAPSystemSensor(coordinator, description, uid)
265  for description in _SYSTEM_MON_COND
266  ]
267  )
268 
269  sensors.extend(
270  [QNAPCPUSensor(coordinator, description, uid) for description in _CPU_MON_COND]
271  )
272 
273  sensors.extend(
274  [
275  QNAPMemorySensor(coordinator, description, uid)
276  for description in _MEMORY_MON_COND
277  ]
278  )
279 
280  # Network sensors
281  sensors.extend(
282  [
283  QNAPNetworkSensor(coordinator, description, uid, nic)
284  for nic in coordinator.data["system_stats"]["nics"]
285  for description in _NETWORK_MON_COND
286  ]
287  )
288 
289  # Drive sensors
290  sensors.extend(
291  [
292  QNAPDriveSensor(coordinator, description, uid, drive)
293  for drive in coordinator.data["smart_drive_health"]
294  for description in _DRIVE_MON_COND
295  ]
296  )
297 
298  # Volume sensors
299  sensors.extend(
300  [
301  QNAPVolumeSensor(coordinator, description, uid, volume)
302  for volume in coordinator.data["volumes"]
303  for description in _VOLUME_MON_COND
304  ]
305  )
306  async_add_entities(sensors)
307 
308 
309 class QNAPSensor(CoordinatorEntity[QnapCoordinator], SensorEntity):
310  """Base class for a QNAP sensor."""
311 
312  _attr_has_entity_name = True
313 
314  def __init__(
315  self,
316  coordinator: QnapCoordinator,
317  description: SensorEntityDescription,
318  unique_id: str,
319  monitor_device: str | None = None,
320  ) -> None:
321  """Initialize the sensor."""
322  super().__init__(coordinator)
323  self.entity_descriptionentity_description = description
324  self.device_namedevice_name = self.coordinator.data["system_stats"]["system"]["name"]
325  self.monitor_devicemonitor_device = monitor_device
326  self._attr_unique_id_attr_unique_id = f"{unique_id}_{description.key}"
327  if monitor_device:
328  self._attr_unique_id_attr_unique_id = f"{self._attr_unique_id}_{monitor_device}"
329  self._attr_translation_placeholders_attr_translation_placeholders = {"monitor_device": monitor_device}
330  self._attr_device_info_attr_device_info = DeviceInfo(
331  identifiers={(DOMAIN, unique_id)},
332  serial_number=unique_id,
333  name=self.device_namedevice_name,
334  model=self.coordinator.data["system_stats"]["system"]["model"],
335  sw_version=self.coordinator.data["system_stats"]["firmware"]["version"],
336  manufacturer="QNAP",
337  )
338 
339 
341  """A QNAP sensor that monitors CPU stats."""
342 
343  @property
344  def native_value(self):
345  """Return the state of the sensor."""
346  if self.entity_descriptionentity_description.key == "cpu_temp":
347  return self.coordinator.data["system_stats"]["cpu"]["temp_c"]
348  if self.entity_descriptionentity_description.key == "cpu_usage":
349  return self.coordinator.data["system_stats"]["cpu"]["usage_percent"]
350 
351  return None
352 
353 
355  """A QNAP sensor that monitors memory stats."""
356 
357  @property
358  def native_value(self):
359  """Return the state of the sensor."""
360  free = float(self.coordinator.data["system_stats"]["memory"]["free"])
361  if self.entity_descriptionentity_description.key == "memory_free":
362  return free
363 
364  total = float(self.coordinator.data["system_stats"]["memory"]["total"])
365  if self.entity_descriptionentity_description.key == "memory_size":
366  return total
367 
368  used = total - free
369  if self.entity_descriptionentity_description.key == "memory_used":
370  return used
371 
372  if self.entity_descriptionentity_description.key == "memory_percent_used":
373  return used / total * 100
374 
375  return None
376 
377 
379  """A QNAP sensor that monitors network stats."""
380 
381  monitor_device: str
382 
383  @property
384  def native_value(self):
385  """Return the state of the sensor."""
386  nic = self.coordinator.data["system_stats"]["nics"][self.monitor_devicemonitor_device]
387  if self.entity_descriptionentity_description.key == "network_link_status":
388  return nic["link_status"]
389 
390  if self.entity_descriptionentity_description.key == "network_max_speed":
391  return nic["max_speed"]
392 
393  if self.entity_descriptionentity_description.key == "network_err":
394  return nic["err_packets"]
395 
396  data = self.coordinator.data["bandwidth"][self.monitor_devicemonitor_device]
397  if self.entity_descriptionentity_description.key == "network_tx":
398  return data["tx"]
399 
400  if self.entity_descriptionentity_description.key == "network_rx":
401  return data["rx"]
402 
403  return None
404 
405 
407  """A QNAP sensor that monitors overall system health."""
408 
409  @property
410  def native_value(self):
411  """Return the state of the sensor."""
412  if self.entity_descriptionentity_description.key == "status":
413  return self.coordinator.data["system_health"]
414 
415  if self.entity_descriptionentity_description.key == "system_temp":
416  return int(self.coordinator.data["system_stats"]["system"]["temp_c"])
417 
418  if self.entity_descriptionentity_description.key == "uptime":
419  uptime = self.coordinator.data["system_stats"]["uptime"]
420  uptime_duration = timedelta(
421  days=uptime["days"],
422  hours=uptime["hours"],
423  minutes=uptime["minutes"],
424  seconds=uptime["seconds"],
425  )
426  return dt_util.now() - uptime_duration
427 
428  return None
429 
430 
432  """A QNAP sensor that monitors HDD/SSD drive stats."""
433 
434  monitor_device: str
435 
436  @property
437  def native_value(self):
438  """Return the state of the sensor."""
439  data = self.coordinator.data["smart_drive_health"][self.monitor_devicemonitor_device]
440 
441  if self.entity_descriptionentity_description.key == "drive_smart_status":
442  return data["health"]
443 
444  if self.entity_descriptionentity_description.key == "drive_temp":
445  return int(data["temp_c"]) if data["temp_c"] is not None else 0
446 
447  return None
448 
449  @property
450  def extra_state_attributes(self) -> dict[str, Any] | None:
451  """Return the state attributes."""
452  if self.coordinator.data:
453  data = self.coordinator.data["smart_drive_health"][self.monitor_devicemonitor_device]
454  return {
455  ATTR_DRIVE: data["drive_number"],
456  ATTR_MODEL: data["model"],
457  ATTR_SERIAL: data["serial"],
458  ATTR_TYPE: data["type"],
459  }
460  return None
461 
462 
464  """A QNAP sensor that monitors storage volume stats."""
465 
466  monitor_device: str
467 
468  @property
469  def native_value(self):
470  """Return the state of the sensor."""
471  data = self.coordinator.data["volumes"][self.monitor_devicemonitor_device]
472 
473  free_gb = int(data["free_size"])
474  if self.entity_descriptionentity_description.key == "volume_size_free":
475  return free_gb
476 
477  total_gb = int(data["total_size"])
478  if self.entity_descriptionentity_description.key == "volume_size_total":
479  return total_gb
480 
481  used_gb = total_gb - free_gb
482  if self.entity_descriptionentity_description.key == "volume_size_used":
483  return used_gb
484 
485  if self.entity_descriptionentity_description.key == "volume_percentage_used":
486  return used_gb / total_gb * 100
487 
488  return None
dict[str, Any]|None extra_state_attributes(self)
Definition: sensor.py:450
None __init__(self, QnapCoordinator coordinator, SensorEntityDescription description, str unique_id, str|None monitor_device=None)
Definition: sensor.py:320
None async_setup_entry(HomeAssistant hass, config_entries.ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:252