Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for APCUPSd sensors."""
2 
3 from __future__ import annotations
4 
5 import logging
6 
8  SensorDeviceClass,
9  SensorEntity,
10  SensorEntityDescription,
11  SensorStateClass,
12 )
13 from homeassistant.const import (
14  PERCENTAGE,
15  UnitOfApparentPower,
16  UnitOfElectricCurrent,
17  UnitOfElectricPotential,
18  UnitOfFrequency,
19  UnitOfPower,
20  UnitOfTemperature,
21  UnitOfTime,
22 )
23 from homeassistant.core import HomeAssistant, callback
24 from homeassistant.helpers.entity_platform import AddEntitiesCallback
25 from homeassistant.helpers.update_coordinator import CoordinatorEntity
26 
27 from . import APCUPSdConfigEntry
28 from .const import LAST_S_TEST
29 from .coordinator import APCUPSdCoordinator
30 
31 PARALLEL_UPDATES = 0
32 
33 _LOGGER = logging.getLogger(__name__)
34 
35 SENSORS: dict[str, SensorEntityDescription] = {
36  "alarmdel": SensorEntityDescription(
37  key="alarmdel",
38  translation_key="alarm_delay",
39  ),
40  "ambtemp": SensorEntityDescription(
41  key="ambtemp",
42  translation_key="ambient_temperature",
43  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
44  device_class=SensorDeviceClass.TEMPERATURE,
45  state_class=SensorStateClass.MEASUREMENT,
46  ),
48  key="apc",
49  translation_key="apc_status",
50  entity_registry_enabled_default=False,
51  ),
52  "apcmodel": SensorEntityDescription(
53  key="apcmodel",
54  translation_key="apc_model",
55  entity_registry_enabled_default=False,
56  ),
57  "badbatts": SensorEntityDescription(
58  key="badbatts",
59  translation_key="bad_batteries",
60  ),
61  "battdate": SensorEntityDescription(
62  key="battdate",
63  translation_key="battery_replacement_date",
64  ),
65  "battstat": SensorEntityDescription(
66  key="battstat",
67  translation_key="battery_status",
68  ),
69  "battv": SensorEntityDescription(
70  key="battv",
71  translation_key="battery_voltage",
72  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
73  device_class=SensorDeviceClass.VOLTAGE,
74  state_class=SensorStateClass.MEASUREMENT,
75  ),
76  "bcharge": SensorEntityDescription(
77  key="bcharge",
78  native_unit_of_measurement=PERCENTAGE,
79  device_class=SensorDeviceClass.BATTERY,
80  state_class=SensorStateClass.MEASUREMENT,
81  ),
82  "cable": SensorEntityDescription(
83  key="cable",
84  translation_key="cable_type",
85  entity_registry_enabled_default=False,
86  ),
87  "cumonbatt": SensorEntityDescription(
88  key="cumonbatt",
89  translation_key="total_time_on_battery",
90  native_unit_of_measurement=UnitOfTime.SECONDS,
91  state_class=SensorStateClass.TOTAL_INCREASING,
92  device_class=SensorDeviceClass.DURATION,
93  ),
95  key="date",
96  translation_key="date",
97  entity_registry_enabled_default=False,
98  ),
99  "dipsw": SensorEntityDescription(
100  key="dipsw",
101  translation_key="dip_switch_settings",
102  ),
103  "dlowbatt": SensorEntityDescription(
104  key="dlowbatt",
105  translation_key="low_battery_signal",
106  ),
107  "driver": SensorEntityDescription(
108  key="driver",
109  translation_key="driver",
110  entity_registry_enabled_default=False,
111  ),
112  "dshutd": SensorEntityDescription(
113  key="dshutd",
114  translation_key="shutdown_delay",
115  ),
116  "dwake": SensorEntityDescription(
117  key="dwake",
118  translation_key="wake_delay",
119  ),
120  "end apc": SensorEntityDescription(
121  key="end apc",
122  translation_key="date_and_time",
123  entity_registry_enabled_default=False,
124  ),
125  "extbatts": SensorEntityDescription(
126  key="extbatts",
127  translation_key="external_batteries",
128  ),
129  "firmware": SensorEntityDescription(
130  key="firmware",
131  translation_key="firmware_version",
132  entity_registry_enabled_default=False,
133  ),
134  "hitrans": SensorEntityDescription(
135  key="hitrans",
136  translation_key="transfer_high",
137  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
138  device_class=SensorDeviceClass.VOLTAGE,
139  ),
140  "hostname": SensorEntityDescription(
141  key="hostname",
142  translation_key="hostname",
143  entity_registry_enabled_default=False,
144  ),
145  "humidity": SensorEntityDescription(
146  key="humidity",
147  translation_key="humidity",
148  native_unit_of_measurement=PERCENTAGE,
149  device_class=SensorDeviceClass.HUMIDITY,
150  state_class=SensorStateClass.MEASUREMENT,
151  ),
152  "itemp": SensorEntityDescription(
153  key="itemp",
154  translation_key="internal_temperature",
155  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
156  device_class=SensorDeviceClass.TEMPERATURE,
157  state_class=SensorStateClass.MEASUREMENT,
158  ),
159  LAST_S_TEST: SensorEntityDescription(
160  key=LAST_S_TEST,
161  translation_key="last_self_test",
162  ),
163  "lastxfer": SensorEntityDescription(
164  key="lastxfer",
165  translation_key="last_transfer",
166  entity_registry_enabled_default=False,
167  ),
168  "linefail": SensorEntityDescription(
169  key="linefail",
170  translation_key="line_failure",
171  ),
172  "linefreq": SensorEntityDescription(
173  key="linefreq",
174  translation_key="line_frequency",
175  native_unit_of_measurement=UnitOfFrequency.HERTZ,
176  device_class=SensorDeviceClass.FREQUENCY,
177  state_class=SensorStateClass.MEASUREMENT,
178  ),
179  "linev": SensorEntityDescription(
180  key="linev",
181  translation_key="line_voltage",
182  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
183  device_class=SensorDeviceClass.VOLTAGE,
184  state_class=SensorStateClass.MEASUREMENT,
185  ),
186  "loadpct": SensorEntityDescription(
187  key="loadpct",
188  translation_key="load_capacity",
189  native_unit_of_measurement=PERCENTAGE,
190  state_class=SensorStateClass.MEASUREMENT,
191  ),
192  "loadapnt": SensorEntityDescription(
193  key="loadapnt",
194  translation_key="apparent_power",
195  native_unit_of_measurement=PERCENTAGE,
196  ),
197  "lotrans": SensorEntityDescription(
198  key="lotrans",
199  translation_key="transfer_low",
200  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
201  device_class=SensorDeviceClass.VOLTAGE,
202  ),
203  "mandate": SensorEntityDescription(
204  key="mandate",
205  translation_key="manufacture_date",
206  entity_registry_enabled_default=False,
207  ),
208  "masterupd": SensorEntityDescription(
209  key="masterupd",
210  translation_key="master_update",
211  ),
212  "maxlinev": SensorEntityDescription(
213  key="maxlinev",
214  translation_key="input_voltage_high",
215  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
216  device_class=SensorDeviceClass.VOLTAGE,
217  ),
218  "maxtime": SensorEntityDescription(
219  key="maxtime",
220  translation_key="max_time",
221  ),
222  "mbattchg": SensorEntityDescription(
223  key="mbattchg",
224  translation_key="max_battery_charge",
225  native_unit_of_measurement=PERCENTAGE,
226  ),
227  "minlinev": SensorEntityDescription(
228  key="minlinev",
229  translation_key="input_voltage_low",
230  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
231  device_class=SensorDeviceClass.VOLTAGE,
232  ),
233  "mintimel": SensorEntityDescription(
234  key="mintimel",
235  translation_key="min_time",
236  ),
237  "model": SensorEntityDescription(
238  key="model",
239  translation_key="model",
240  entity_registry_enabled_default=False,
241  ),
242  "nombattv": SensorEntityDescription(
243  key="nombattv",
244  translation_key="battery_nominal_voltage",
245  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
246  device_class=SensorDeviceClass.VOLTAGE,
247  ),
248  "nominv": SensorEntityDescription(
249  key="nominv",
250  translation_key="nominal_input_voltage",
251  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
252  device_class=SensorDeviceClass.VOLTAGE,
253  ),
254  "nomoutv": SensorEntityDescription(
255  key="nomoutv",
256  translation_key="nominal_output_voltage",
257  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
258  device_class=SensorDeviceClass.VOLTAGE,
259  ),
260  "nompower": SensorEntityDescription(
261  key="nompower",
262  translation_key="nominal_output_power",
263  native_unit_of_measurement=UnitOfPower.WATT,
264  device_class=SensorDeviceClass.POWER,
265  ),
266  "nomapnt": SensorEntityDescription(
267  key="nomapnt",
268  translation_key="nominal_apparent_power",
269  native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
270  device_class=SensorDeviceClass.APPARENT_POWER,
271  ),
272  "numxfers": SensorEntityDescription(
273  key="numxfers",
274  translation_key="transfer_count",
275  state_class=SensorStateClass.TOTAL_INCREASING,
276  ),
277  "outcurnt": SensorEntityDescription(
278  key="outcurnt",
279  translation_key="output_current",
280  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
281  device_class=SensorDeviceClass.CURRENT,
282  state_class=SensorStateClass.MEASUREMENT,
283  ),
284  "outputv": SensorEntityDescription(
285  key="outputv",
286  translation_key="output_voltage",
287  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
288  device_class=SensorDeviceClass.VOLTAGE,
289  state_class=SensorStateClass.MEASUREMENT,
290  ),
291  "reg1": SensorEntityDescription(
292  key="reg1",
293  translation_key="register_1_fault",
294  entity_registry_enabled_default=False,
295  ),
296  "reg2": SensorEntityDescription(
297  key="reg2",
298  translation_key="register_2_fault",
299  entity_registry_enabled_default=False,
300  ),
301  "reg3": SensorEntityDescription(
302  key="reg3",
303  translation_key="register_3_fault",
304  entity_registry_enabled_default=False,
305  ),
306  "retpct": SensorEntityDescription(
307  key="retpct",
308  translation_key="restore_capacity",
309  native_unit_of_measurement=PERCENTAGE,
310  ),
311  "selftest": SensorEntityDescription(
312  key="selftest",
313  translation_key="self_test_result",
314  ),
315  "sense": SensorEntityDescription(
316  key="sense",
317  translation_key="sensitivity",
318  entity_registry_enabled_default=False,
319  ),
320  "serialno": SensorEntityDescription(
321  key="serialno",
322  translation_key="serial_number",
323  entity_registry_enabled_default=False,
324  ),
325  "starttime": SensorEntityDescription(
326  key="starttime",
327  translation_key="startup_time",
328  ),
329  "statflag": SensorEntityDescription(
330  key="statflag",
331  translation_key="online_status",
332  entity_registry_enabled_default=False,
333  ),
334  "status": SensorEntityDescription(
335  key="status",
336  translation_key="status",
337  ),
338  "stesti": SensorEntityDescription(
339  key="stesti",
340  translation_key="self_test_interval",
341  ),
342  "timeleft": SensorEntityDescription(
343  key="timeleft",
344  translation_key="time_left",
345  native_unit_of_measurement=UnitOfTime.MINUTES,
346  state_class=SensorStateClass.MEASUREMENT,
347  device_class=SensorDeviceClass.DURATION,
348  ),
349  "tonbatt": SensorEntityDescription(
350  key="tonbatt",
351  translation_key="time_on_battery",
352  native_unit_of_measurement=UnitOfTime.SECONDS,
353  state_class=SensorStateClass.TOTAL_INCREASING,
354  device_class=SensorDeviceClass.DURATION,
355  ),
356  "upsmode": SensorEntityDescription(
357  key="upsmode",
358  translation_key="ups_mode",
359  ),
360  "upsname": SensorEntityDescription(
361  key="upsname",
362  translation_key="ups_name",
363  entity_registry_enabled_default=False,
364  ),
365  "version": SensorEntityDescription(
366  key="version",
367  translation_key="version",
368  entity_registry_enabled_default=False,
369  ),
370  "xoffbat": SensorEntityDescription(
371  key="xoffbat",
372  translation_key="transfer_from_battery",
373  ),
374  "xoffbatt": SensorEntityDescription(
375  key="xoffbatt",
376  translation_key="transfer_from_battery",
377  ),
378  "xonbatt": SensorEntityDescription(
379  key="xonbatt",
380  translation_key="transfer_to_battery",
381  ),
382 }
383 
384 INFERRED_UNITS = {
385  " Minutes": UnitOfTime.MINUTES,
386  " Seconds": UnitOfTime.SECONDS,
387  " Percent": PERCENTAGE,
388  " Volts": UnitOfElectricPotential.VOLT,
389  " Ampere": UnitOfElectricCurrent.AMPERE,
390  " Amps": UnitOfElectricCurrent.AMPERE,
391  " Volt-Ampere": UnitOfApparentPower.VOLT_AMPERE,
392  " VA": UnitOfApparentPower.VOLT_AMPERE,
393  " Watts": UnitOfPower.WATT,
394  " Hz": UnitOfFrequency.HERTZ,
395  " C": UnitOfTemperature.CELSIUS,
396  # APCUPSd reports data for "itemp" field (eventually represented by UPS Internal
397  # Temperature sensor in this integration) with a trailing "Internal", e.g.,
398  # "34.6 C Internal". Here we create a fake unit " C Internal" to handle this case.
399  " C Internal": UnitOfTemperature.CELSIUS,
400  " Percent Load Capacity": PERCENTAGE,
401  # "stesti" field (Self Test Interval) field could report a "days" unit, e.g.,
402  # "7 days", so here we add support for it.
403  " days": UnitOfTime.DAYS,
404 }
405 
406 
408  hass: HomeAssistant,
409  config_entry: APCUPSdConfigEntry,
410  async_add_entities: AddEntitiesCallback,
411 ) -> None:
412  """Set up the APCUPSd sensors from config entries."""
413  coordinator = config_entry.runtime_data
414 
415  # The resource keys in the data dict collected in the coordinator is in upper-case
416  # by default, but we use lower cases throughout this integration.
417  available_resources: set[str] = {k.lower() for k, _ in coordinator.data.items()}
418 
419  entities = []
420 
421  # "laststest" is a special sensor that only appears when the APC UPS daemon has done a
422  # periodical (or manual) self test since last daemon restart. It might not be available
423  # when we set up the integration, and we do not know if it would ever be available. Here we
424  # add it anyway and mark it as unknown initially.
425  for resource in available_resources | {LAST_S_TEST}:
426  if resource not in SENSORS:
427  _LOGGER.warning("Invalid resource from APCUPSd: %s", resource.upper())
428  continue
429 
430  entities.append(APCUPSdSensor(coordinator, SENSORS[resource]))
431 
432  async_add_entities(entities)
433 
434 
435 def infer_unit(value: str) -> tuple[str, str | None]:
436  """If the value ends with any of the units from supported units.
437 
438  Split the unit off the end of the value and return the value, unit tuple
439  pair. Else return the original value and None as the unit.
440  """
441 
442  for unit, ha_unit in INFERRED_UNITS.items():
443  if value.endswith(unit):
444  return value.removesuffix(unit), ha_unit
445 
446  return value, None
447 
448 
449 class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity):
450  """Representation of a sensor entity for APCUPSd status values."""
451 
452  _attr_has_entity_name = True
453 
454  def __init__(
455  self,
456  coordinator: APCUPSdCoordinator,
457  description: SensorEntityDescription,
458  ) -> None:
459  """Initialize the sensor."""
460  super().__init__(coordinator=coordinator, context=description.key.upper())
461 
462  # Set up unique id and device info if serial number is available.
463  if (serial_no := coordinator.data.serial_no) is not None:
464  self._attr_unique_id_attr_unique_id = f"{serial_no}_{description.key}"
465 
466  self.entity_descriptionentity_description = description
467  self._attr_device_info_attr_device_info = coordinator.device_info
468 
469  # Initial update of attributes.
470  self._update_attrs_update_attrs()
471 
472  @callback
473  def _handle_coordinator_update(self) -> None:
474  """Handle updated data from the coordinator."""
475  self._update_attrs_update_attrs()
476  self.async_write_ha_stateasync_write_ha_state()
477 
478  def _update_attrs(self) -> None:
479  """Update sensor attributes based on coordinator data."""
480  key = self.entity_descriptionentity_description.key.upper()
481  # For most sensors the key will always be available for each refresh. However, some sensors
482  # (e.g., "laststest") will only appear after certain event occurs (e.g., a self test is
483  # performed) and may disappear again after certain event. So we mark the state as "unknown"
484  # when it becomes unknown after such events.
485  if key not in self.coordinator.data:
486  self._attr_native_value_attr_native_value = None
487  return
488 
489  self._attr_native_value_attr_native_value, inferred_unit = infer_unit(self.coordinator.data[key])
490  if not self.native_unit_of_measurementnative_unit_of_measurement:
491  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = inferred_unit
None __init__(self, APCUPSdCoordinator coordinator, SensorEntityDescription description)
Definition: sensor.py:458
None async_setup_entry(HomeAssistant hass, APCUPSdConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:411
tuple[str, str|None] infer_unit(str value)
Definition: sensor.py:435