Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Component providing support for Reolink sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from datetime import date, datetime
8 from decimal import Decimal
9 
10 from reolink_aio.api import Host
11 from reolink_aio.enums import BatteryEnum
12 
14  SensorDeviceClass,
15  SensorEntity,
16  SensorEntityDescription,
17  SensorStateClass,
18 )
19 from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature
20 from homeassistant.core import HomeAssistant
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 from homeassistant.helpers.typing import StateType
23 
24 from .entity import (
25  ReolinkChannelCoordinatorEntity,
26  ReolinkChannelEntityDescription,
27  ReolinkHostCoordinatorEntity,
28  ReolinkHostEntityDescription,
29 )
30 from .util import ReolinkConfigEntry, ReolinkData
31 
32 PARALLEL_UPDATES = 0
33 
34 
35 @dataclass(frozen=True, kw_only=True)
37  SensorEntityDescription,
38  ReolinkChannelEntityDescription,
39 ):
40  """A class that describes sensor entities for a camera channel."""
41 
42  value: Callable[[Host, int], StateType]
43 
44 
45 @dataclass(frozen=True, kw_only=True)
47  SensorEntityDescription,
48  ReolinkHostEntityDescription,
49 ):
50  """A class that describes host sensor entities."""
51 
52  value: Callable[[Host], StateType]
53 
54 
55 SENSORS = (
57  key="ptz_pan_position",
58  cmd_key="GetPtzCurPos",
59  translation_key="ptz_pan_position",
60  state_class=SensorStateClass.MEASUREMENT,
61  entity_category=EntityCategory.DIAGNOSTIC,
62  value=lambda api, ch: api.ptz_pan_position(ch),
63  supported=lambda api, ch: api.supported(ch, "ptz_pan_position"),
64  ),
66  key="ptz_tilt_position",
67  cmd_key="GetPtzCurPos",
68  translation_key="ptz_tilt_position",
69  state_class=SensorStateClass.MEASUREMENT,
70  entity_category=EntityCategory.DIAGNOSTIC,
71  value=lambda api, ch: api.ptz_tilt_position(ch),
72  supported=lambda api, ch: api.supported(ch, "ptz_tilt_position"),
73  ),
75  key="battery_percent",
76  cmd_id=252,
77  cmd_key="GetBatteryInfo",
78  native_unit_of_measurement=PERCENTAGE,
79  device_class=SensorDeviceClass.BATTERY,
80  state_class=SensorStateClass.MEASUREMENT,
81  entity_category=EntityCategory.DIAGNOSTIC,
82  value=lambda api, ch: api.battery_percentage(ch),
83  supported=lambda api, ch: api.supported(ch, "battery"),
84  ),
86  key="battery_temperature",
87  cmd_id=252,
88  cmd_key="GetBatteryInfo",
89  translation_key="battery_temperature",
90  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
91  device_class=SensorDeviceClass.TEMPERATURE,
92  state_class=SensorStateClass.MEASUREMENT,
93  entity_category=EntityCategory.DIAGNOSTIC,
94  entity_registry_enabled_default=False,
95  value=lambda api, ch: api.battery_temperature(ch),
96  supported=lambda api, ch: api.supported(ch, "battery"),
97  ),
99  key="battery_state",
100  cmd_id=252,
101  cmd_key="GetBatteryInfo",
102  translation_key="battery_state",
103  device_class=SensorDeviceClass.ENUM,
104  entity_category=EntityCategory.DIAGNOSTIC,
105  entity_registry_enabled_default=False,
106  options=[state.name for state in BatteryEnum],
107  value=lambda api, ch: BatteryEnum(api.battery_status(ch)).name,
108  supported=lambda api, ch: api.supported(ch, "battery"),
109  ),
110 )
111 
112 HOST_SENSORS = (
114  key="wifi_signal",
115  cmd_key="GetWifiSignal",
116  translation_key="wifi_signal",
117  state_class=SensorStateClass.MEASUREMENT,
118  entity_category=EntityCategory.DIAGNOSTIC,
119  entity_registry_enabled_default=False,
120  value=lambda api: api.wifi_signal,
121  supported=lambda api: api.supported(None, "wifi") and api.wifi_connection,
122  ),
124  key="cpu_usage",
125  cmd_key="GetPerformance",
126  translation_key="cpu_usage",
127  native_unit_of_measurement=PERCENTAGE,
128  state_class=SensorStateClass.MEASUREMENT,
129  entity_category=EntityCategory.DIAGNOSTIC,
130  entity_registry_enabled_default=False,
131  value=lambda api: api.cpu_usage,
132  supported=lambda api: api.supported(None, "performance"),
133  ),
134 )
135 
136 HDD_SENSORS = (
138  key="storage",
139  cmd_key="GetHddInfo",
140  native_unit_of_measurement=PERCENTAGE,
141  state_class=SensorStateClass.MEASUREMENT,
142  entity_category=EntityCategory.DIAGNOSTIC,
143  entity_registry_enabled_default=False,
144  value=lambda api, idx: api.hdd_storage(idx),
145  supported=lambda api, idx: api.supported(None, "hdd"),
146  ),
147 )
148 
149 
151  hass: HomeAssistant,
152  config_entry: ReolinkConfigEntry,
153  async_add_entities: AddEntitiesCallback,
154 ) -> None:
155  """Set up a Reolink IP Camera."""
156  reolink_data: ReolinkData = config_entry.runtime_data
157 
158  entities: list[
159  ReolinkSensorEntity | ReolinkHostSensorEntity | ReolinkHddSensorEntity
160  ] = [
161  ReolinkSensorEntity(reolink_data, channel, entity_description)
162  for entity_description in SENSORS
163  for channel in reolink_data.host.api.channels
164  if entity_description.supported(reolink_data.host.api, channel)
165  ]
166  entities.extend(
167  ReolinkHostSensorEntity(reolink_data, entity_description)
168  for entity_description in HOST_SENSORS
169  if entity_description.supported(reolink_data.host.api)
170  )
171  entities.extend(
172  ReolinkHddSensorEntity(reolink_data, hdd_index, entity_description)
173  for entity_description in HDD_SENSORS
174  for hdd_index in reolink_data.host.api.hdd_list
175  if entity_description.supported(reolink_data.host.api, hdd_index)
176  )
177  async_add_entities(entities)
178 
179 
181  """Base sensor class for Reolink IP camera sensors."""
182 
183  entity_description: ReolinkSensorEntityDescription
184 
185  def __init__(
186  self,
187  reolink_data: ReolinkData,
188  channel: int,
189  entity_description: ReolinkSensorEntityDescription,
190  ) -> None:
191  """Initialize Reolink sensor."""
192  self.entity_descriptionentity_description = entity_description
193  super().__init__(reolink_data, channel)
194 
195  @property
196  def native_value(self) -> StateType | date | datetime | Decimal:
197  """Return the value reported by the sensor."""
198  return self.entity_descriptionentity_description.value(self._host_host.api, self._channel_channel)
199 
200 
202  """Base sensor class for Reolink host sensors."""
203 
204  entity_description: ReolinkHostSensorEntityDescription
205 
206  def __init__(
207  self,
208  reolink_data: ReolinkData,
209  entity_description: ReolinkHostSensorEntityDescription,
210  ) -> None:
211  """Initialize Reolink host sensor."""
212  self.entity_descriptionentity_description = entity_description
213  super().__init__(reolink_data)
214 
215  @property
216  def native_value(self) -> StateType | date | datetime | Decimal:
217  """Return the value reported by the sensor."""
218  return self.entity_descriptionentity_description.value(self._host_host.api)
219 
220 
222  """Base sensor class for Reolink host sensors."""
223 
224  entity_description: ReolinkSensorEntityDescription
225 
226  def __init__(
227  self,
228  reolink_data: ReolinkData,
229  hdd_index: int,
230  entity_description: ReolinkSensorEntityDescription,
231  ) -> None:
232  """Initialize Reolink host sensor."""
233  self.entity_descriptionentity_description = entity_description
234  super().__init__(reolink_data)
235  self._hdd_index_hdd_index = hdd_index
236  self._attr_translation_placeholders_attr_translation_placeholders = {"hdd_index": str(hdd_index)}
237  self._attr_unique_id_attr_unique_id_attr_unique_id = (
238  f"{self._host.unique_id}_{hdd_index}_{entity_description.key}"
239  )
240  if self._host_host.api.hdd_type(hdd_index) == "HDD":
241  self._attr_translation_key_attr_translation_key = "hdd_storage"
242  else:
243  self._attr_translation_key_attr_translation_key = "sd_storage"
244 
245  @property
246  def native_value(self) -> StateType | date | datetime | Decimal:
247  """Return the value reported by the sensor."""
248  return self.entity_descriptionentity_description.value(self._host_host.api, self._hdd_index_hdd_index)
249 
250  @property
251  def available(self) -> bool:
252  """Return True if entity is available."""
253  return self._host_host.api.hdd_available(self._hdd_index_hdd_index) and super().available