Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for TPLink Omada binary sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 
8 from tplink_omada_client.definitions import DeviceStatus, DeviceStatusCategory
9 from tplink_omada_client.devices import OmadaListDevice
10 
12  SensorDeviceClass,
13  SensorEntity,
14  SensorEntityDescription,
15  SensorStateClass,
16 )
17 from homeassistant.const import PERCENTAGE, EntityCategory
18 from homeassistant.core import HomeAssistant
19 from homeassistant.helpers.entity_platform import AddEntitiesCallback
20 from homeassistant.helpers.typing import StateType
21 
22 from . import OmadaConfigEntry
23 from .const import OmadaDeviceStatus
24 from .coordinator import OmadaDevicesCoordinator
25 from .entity import OmadaDeviceEntity
26 
27 # Useful low level status categories, mapped to a more descriptive status.
28 DEVICE_STATUS_MAP = {
29  DeviceStatus.PROVISIONING: OmadaDeviceStatus.PENDING,
30  DeviceStatus.CONFIGURING: OmadaDeviceStatus.PENDING,
31  DeviceStatus.UPGRADING: OmadaDeviceStatus.PENDING,
32  DeviceStatus.REBOOTING: OmadaDeviceStatus.PENDING,
33  DeviceStatus.ADOPT_FAILED: OmadaDeviceStatus.ADOPT_FAILED,
34  DeviceStatus.ADOPT_FAILED_WIRELESS: OmadaDeviceStatus.ADOPT_FAILED,
35  DeviceStatus.MANAGED_EXTERNALLY: OmadaDeviceStatus.MANAGED_EXTERNALLY,
36  DeviceStatus.MANAGED_EXTERNALLY_WIRELESS: OmadaDeviceStatus.MANAGED_EXTERNALLY,
37 }
38 
39 # High level status categories, suitable for most device statuses.
40 DEVICE_STATUS_CATEGORY_MAP = {
41  DeviceStatusCategory.DISCONNECTED: OmadaDeviceStatus.DISCONNECTED,
42  DeviceStatusCategory.CONNECTED: OmadaDeviceStatus.CONNECTED,
43  DeviceStatusCategory.PENDING: OmadaDeviceStatus.PENDING,
44  DeviceStatusCategory.HEARTBEAT_MISSED: OmadaDeviceStatus.HEARTBEAT_MISSED,
45  DeviceStatusCategory.ISOLATED: OmadaDeviceStatus.ISOLATED,
46 }
47 
48 
49 def _map_device_status(device: OmadaListDevice) -> str | None:
50  """Map the API device status to the best available descriptive device status."""
51  display_status = DEVICE_STATUS_MAP.get(
52  device.status
53  ) or DEVICE_STATUS_CATEGORY_MAP.get(device.status_category)
54  return display_status.value if display_status else None
55 
56 
58  hass: HomeAssistant,
59  config_entry: OmadaConfigEntry,
60  async_add_entities: AddEntitiesCallback,
61 ) -> None:
62  """Set up sensors."""
63  controller = config_entry.runtime_data
64 
65  devices_coordinator = controller.devices_coordinator
66 
68  OmadaDeviceSensor(devices_coordinator, device, desc)
69  for device in devices_coordinator.data.values()
70  for desc in OMADA_DEVICE_SENSORS
71  if desc.exists_func(device)
72  )
73 
74 
75 @dataclass(frozen=True, kw_only=True)
77  """Entity description for a status derived from an Omada device in the device list."""
78 
79  exists_func: Callable[[OmadaListDevice], bool] = lambda _: True
80  update_func: Callable[[OmadaListDevice], StateType]
81 
82 
83 OMADA_DEVICE_SENSORS: list[OmadaDeviceSensorEntityDescription] = [
85  key="device_status",
86  translation_key="device_status",
87  device_class=SensorDeviceClass.ENUM,
88  entity_category=EntityCategory.DIAGNOSTIC,
89  update_func=_map_device_status,
90  options=[v.value for v in OmadaDeviceStatus],
91  ),
93  key="cpu_usage",
94  translation_key="cpu_usage",
95  entity_category=EntityCategory.DIAGNOSTIC,
96  state_class=SensorStateClass.MEASUREMENT,
97  native_unit_of_measurement=PERCENTAGE,
98  update_func=lambda device: device.cpu_usage,
99  ),
101  key="mem_usage",
102  translation_key="mem_usage",
103  entity_category=EntityCategory.DIAGNOSTIC,
104  state_class=SensorStateClass.MEASUREMENT,
105  native_unit_of_measurement=PERCENTAGE,
106  update_func=lambda device: device.mem_usage,
107  ),
108 ]
109 
110 
111 class OmadaDeviceSensor(OmadaDeviceEntity[OmadaDevicesCoordinator], SensorEntity):
112  """Sensor for property of a generic Omada device."""
113 
114  entity_description: OmadaDeviceSensorEntityDescription
115 
116  def __init__(
117  self,
118  coordinator: OmadaDevicesCoordinator,
119  device: OmadaListDevice,
120  entity_description: OmadaDeviceSensorEntityDescription,
121  ) -> None:
122  """Initialize the device sensor."""
123  super().__init__(coordinator, device)
124  self.entity_descriptionentity_description = entity_description
125  self._attr_unique_id_attr_unique_id = f"{device.mac}_{entity_description.key}"
126 
127  @property
128  def native_value(self) -> StateType:
129  """Return the state of the sensor."""
130  return self.entity_descriptionentity_description.update_func(
131  self.coordinator.data[self.device.mac]
132  )