Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for IKEA Tradfri sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from typing import Any, cast
8 
9 from pytradfri.command import Command
10 from pytradfri.device import Device
11 
13  SensorDeviceClass,
14  SensorEntity,
15  SensorEntityDescription,
16  SensorStateClass,
17 )
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.const import (
20  CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
21  PERCENTAGE,
22  Platform,
23  UnitOfTime,
24 )
25 from homeassistant.core import HomeAssistant, callback
26 from homeassistant.helpers import entity_registry as er
27 from homeassistant.helpers.entity_platform import AddEntitiesCallback
28 
29 from .const import (
30  CONF_GATEWAY_ID,
31  COORDINATOR,
32  COORDINATOR_LIST,
33  DOMAIN,
34  KEY_API,
35  LOGGER,
36 )
37 from .coordinator import TradfriDeviceDataUpdateCoordinator
38 from .entity import TradfriBaseEntity
39 
40 
41 @dataclass(frozen=True, kw_only=True)
43  """Class describing Tradfri sensor entities."""
44 
45  value: Callable[[Device], Any | None]
46 
47 
48 def _get_air_quality(device: Device) -> int | None:
49  """Fetch the air quality value."""
50  assert device.air_purifier_control is not None
51  if (
52  device.air_purifier_control.air_purifiers[0].air_quality == 65535
53  ): # The sensor returns 65535 if the fan is turned off
54  return None
55 
56  return cast(int, device.air_purifier_control.air_purifiers[0].air_quality)
57 
58 
59 def _get_filter_time_left(device: Device) -> int:
60  """Fetch the filter's remaining lifetime (in hours)."""
61  assert device.air_purifier_control is not None
62  return round(
63  cast(
64  int, device.air_purifier_control.air_purifiers[0].filter_lifetime_remaining
65  )
66  / 60
67  )
68 
69 
70 SENSOR_DESCRIPTIONS_BATTERY: tuple[TradfriSensorEntityDescription, ...] = (
72  key="battery_level",
73  device_class=SensorDeviceClass.BATTERY,
74  state_class=SensorStateClass.MEASUREMENT,
75  native_unit_of_measurement=PERCENTAGE,
76  value=lambda device: cast(int, device.device_info.battery_level),
77  ),
78 )
79 
80 
81 SENSOR_DESCRIPTIONS_FAN: tuple[TradfriSensorEntityDescription, ...] = (
83  key="aqi",
84  translation_key="aqi",
85  state_class=SensorStateClass.MEASUREMENT,
86  native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
87  value=_get_air_quality,
88  ),
90  key="filter_life_remaining",
91  translation_key="filter_life_remaining",
92  state_class=SensorStateClass.MEASUREMENT,
93  native_unit_of_measurement=UnitOfTime.HOURS,
94  value=_get_filter_time_left,
95  ),
96 )
97 
98 
99 @callback
100 def _migrate_old_unique_ids(hass: HomeAssistant, old_unique_id: str, key: str) -> None:
101  """Migrate unique IDs to the new format."""
102  ent_reg = er.async_get(hass)
103 
104  entity_id = ent_reg.async_get_entity_id(Platform.SENSOR, DOMAIN, old_unique_id)
105 
106  if entity_id is None:
107  return
108 
109  new_unique_id = f"{old_unique_id}-{key}"
110 
111  try:
112  ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
113  except ValueError:
114  LOGGER.warning(
115  "Skip migration of id [%s] to [%s] because it already exists",
116  old_unique_id,
117  new_unique_id,
118  )
119  return
120 
121  LOGGER.debug(
122  "Migrating unique_id from [%s] to [%s]",
123  old_unique_id,
124  new_unique_id,
125  )
126 
127 
129  hass: HomeAssistant,
130  config_entry: ConfigEntry,
131  async_add_entities: AddEntitiesCallback,
132 ) -> None:
133  """Set up a Tradfri config entry."""
134  gateway_id = config_entry.data[CONF_GATEWAY_ID]
135  coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR]
136  api = coordinator_data[KEY_API]
137 
138  entities: list[TradfriSensor] = []
139 
140  for device_coordinator in coordinator_data[COORDINATOR_LIST]:
141  if (
142  not device_coordinator.device.has_light_control
143  and not device_coordinator.device.has_socket_control
144  and not device_coordinator.device.has_signal_repeater_control
145  and not device_coordinator.device.has_air_purifier_control
146  ):
147  descriptions = SENSOR_DESCRIPTIONS_BATTERY
148  elif device_coordinator.device.has_air_purifier_control:
149  descriptions = SENSOR_DESCRIPTIONS_FAN
150  else:
151  continue
152 
153  for description in descriptions:
154  # Added in Home assistant 2022.3
156  hass=hass,
157  old_unique_id=f"{gateway_id}-{device_coordinator.device.id}",
158  key=description.key,
159  )
160 
161  entities.append(
163  device_coordinator,
164  api,
165  gateway_id,
166  description=description,
167  )
168  )
169 
170  async_add_entities(entities)
171 
172 
174  """The platform class required by Home Assistant."""
175 
176  entity_description: TradfriSensorEntityDescription
177 
178  def __init__(
179  self,
180  device_coordinator: TradfriDeviceDataUpdateCoordinator,
181  api: Callable[[Command | list[Command]], Any],
182  gateway_id: str,
183  description: TradfriSensorEntityDescription,
184  ) -> None:
185  """Initialize a Tradfri sensor."""
186  super().__init__(
187  device_coordinator=device_coordinator,
188  api=api,
189  gateway_id=gateway_id,
190  )
191 
192  self.entity_descriptionentity_description = description
193 
194  self._attr_unique_id_attr_unique_id_attr_unique_id = f"{self._attr_unique_id}-{description.key}"
195 
196  self._refresh_refresh_refresh() # Set initial state
197 
198  def _refresh(self) -> None:
199  """Refresh the device."""
200  self._attr_native_value_attr_native_value = self.entity_descriptionentity_description.value(self.coordinator.data)
None __init__(self, TradfriDeviceDataUpdateCoordinator device_coordinator, Callable[[Command|list[Command]], Any] api, str gateway_id, TradfriSensorEntityDescription description)
Definition: sensor.py:184
None _migrate_old_unique_ids(HomeAssistant hass, str old_unique_id, str key)
Definition: sensor.py:100
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:132
int _get_filter_time_left(Device device)
Definition: sensor.py:59
int|None _get_air_quality(Device device)
Definition: sensor.py:48