Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for AVM FRITZ!SmartHome temperature sensor only devices."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from datetime import datetime
8 from typing import Final
9 
10 from pyfritzhome.fritzhomedevice import FritzhomeDevice
11 
12 from homeassistant.components.climate import PRESET_COMFORT, PRESET_ECO
14  SensorDeviceClass,
15  SensorEntity,
16  SensorEntityDescription,
17  SensorStateClass,
18 )
19 from homeassistant.const import (
20  PERCENTAGE,
21  EntityCategory,
22  UnitOfElectricCurrent,
23  UnitOfElectricPotential,
24  UnitOfEnergy,
25  UnitOfPower,
26  UnitOfTemperature,
27 )
28 from homeassistant.core import HomeAssistant, callback
29 from homeassistant.helpers.entity_platform import AddEntitiesCallback
30 from homeassistant.helpers.typing import StateType
31 from homeassistant.util.dt import utc_from_timestamp
32 
33 from .coordinator import FritzboxConfigEntry
34 from .entity import FritzBoxDeviceEntity
35 from .model import FritzEntityDescriptionMixinBase
36 
37 
38 @dataclass(frozen=True)
40  """Sensor description mixin for Fritz!Smarthome entities."""
41 
42  native_value: Callable[[FritzhomeDevice], StateType | datetime]
43 
44 
45 @dataclass(frozen=True)
47  SensorEntityDescription, FritzEntityDescriptionMixinSensor
48 ):
49  """Description for Fritz!Smarthome sensor entities."""
50 
51  entity_category_fn: Callable[[FritzhomeDevice], EntityCategory | None] | None = None
52 
53 
54 def suitable_eco_temperature(device: FritzhomeDevice) -> bool:
55  """Check suitablity for eco temperature sensor."""
56  return device.has_thermostat and device.eco_temperature is not None
57 
58 
59 def suitable_comfort_temperature(device: FritzhomeDevice) -> bool:
60  """Check suitablity for comfort temperature sensor."""
61  return device.has_thermostat and device.comfort_temperature is not None
62 
63 
64 def suitable_nextchange_temperature(device: FritzhomeDevice) -> bool:
65  """Check suitablity for next scheduled temperature sensor."""
66  return device.has_thermostat and device.nextchange_temperature is not None
67 
68 
69 def suitable_nextchange_time(device: FritzhomeDevice) -> bool:
70  """Check suitablity for next scheduled changed time sensor."""
71  return device.has_thermostat and device.nextchange_endperiod is not None
72 
73 
74 def suitable_temperature(device: FritzhomeDevice) -> bool:
75  """Check suitablity for temperature sensor."""
76  return device.has_temperature_sensor and not device.has_thermostat
77 
78 
79 def entity_category_temperature(device: FritzhomeDevice) -> EntityCategory | None:
80  """Determine proper entity category for temperature sensor."""
81  if device.has_switch or device.has_lightbulb:
82  return EntityCategory.DIAGNOSTIC
83  return None
84 
85 
86 def value_nextchange_preset(device: FritzhomeDevice) -> str | None:
87  """Return native value for next scheduled preset sensor."""
88  if not device.nextchange_endperiod:
89  return None
90  if device.nextchange_temperature == device.eco_temperature:
91  return PRESET_ECO
92  return PRESET_COMFORT
93 
94 
95 def value_scheduled_preset(device: FritzhomeDevice) -> str | None:
96  """Return native value for current scheduled preset sensor."""
97  if not device.nextchange_endperiod:
98  return None
99  if device.nextchange_temperature == device.eco_temperature:
100  return PRESET_COMFORT
101  return PRESET_ECO
102 
103 
104 def value_nextchange_temperature(device: FritzhomeDevice) -> float | None:
105  """Return native value for next scheduled temperature time sensor."""
106  if device.nextchange_endperiod and isinstance(device.nextchange_temperature, float):
107  return device.nextchange_temperature
108  return None
109 
110 
111 def value_nextchange_time(device: FritzhomeDevice) -> datetime | None:
112  """Return native value for next scheduled changed time sensor."""
113  if device.nextchange_endperiod:
114  return utc_from_timestamp(device.nextchange_endperiod)
115  return None
116 
117 
118 SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = (
120  key="temperature",
121  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
122  device_class=SensorDeviceClass.TEMPERATURE,
123  state_class=SensorStateClass.MEASUREMENT,
124  entity_category_fn=entity_category_temperature,
125  suitable=suitable_temperature,
126  native_value=lambda device: device.temperature,
127  ),
129  key="humidity",
130  native_unit_of_measurement=PERCENTAGE,
131  device_class=SensorDeviceClass.HUMIDITY,
132  state_class=SensorStateClass.MEASUREMENT,
133  suitable=lambda device: device.rel_humidity is not None,
134  native_value=lambda device: device.rel_humidity,
135  ),
137  key="battery",
138  native_unit_of_measurement=PERCENTAGE,
139  device_class=SensorDeviceClass.BATTERY,
140  entity_category=EntityCategory.DIAGNOSTIC,
141  suitable=lambda device: device.battery_level is not None,
142  native_value=lambda device: device.battery_level,
143  ),
145  key="power_consumption",
146  native_unit_of_measurement=UnitOfPower.WATT,
147  device_class=SensorDeviceClass.POWER,
148  state_class=SensorStateClass.MEASUREMENT,
149  suitable=lambda device: device.has_powermeter,
150  native_value=lambda device: round((device.power or 0.0) / 1000, 3),
151  ),
153  key="voltage",
154  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
155  device_class=SensorDeviceClass.VOLTAGE,
156  state_class=SensorStateClass.MEASUREMENT,
157  suitable=lambda device: device.has_powermeter,
158  native_value=lambda device: round((device.voltage or 0.0) / 1000, 2),
159  ),
161  key="electric_current",
162  native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
163  device_class=SensorDeviceClass.CURRENT,
164  state_class=SensorStateClass.MEASUREMENT,
165  suitable=lambda device: device.has_powermeter,
166  native_value=lambda device: round((device.current or 0.0) / 1000, 3),
167  ),
169  key="total_energy",
170  native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
171  device_class=SensorDeviceClass.ENERGY,
172  state_class=SensorStateClass.TOTAL_INCREASING,
173  suitable=lambda device: device.has_powermeter,
174  native_value=lambda device: (device.energy or 0.0) / 1000,
175  ),
176  # Thermostat Sensors
178  key="comfort_temperature",
179  translation_key="comfort_temperature",
180  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
181  device_class=SensorDeviceClass.TEMPERATURE,
182  entity_category=EntityCategory.DIAGNOSTIC,
183  suitable=suitable_comfort_temperature,
184  native_value=lambda device: device.comfort_temperature,
185  ),
187  key="eco_temperature",
188  translation_key="eco_temperature",
189  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
190  device_class=SensorDeviceClass.TEMPERATURE,
191  entity_category=EntityCategory.DIAGNOSTIC,
192  suitable=suitable_eco_temperature,
193  native_value=lambda device: device.eco_temperature,
194  ),
196  key="nextchange_temperature",
197  translation_key="nextchange_temperature",
198  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
199  device_class=SensorDeviceClass.TEMPERATURE,
200  entity_category=EntityCategory.DIAGNOSTIC,
201  suitable=suitable_nextchange_temperature,
202  native_value=value_nextchange_temperature,
203  ),
205  key="nextchange_time",
206  translation_key="nextchange_time",
207  device_class=SensorDeviceClass.TIMESTAMP,
208  entity_category=EntityCategory.DIAGNOSTIC,
209  suitable=suitable_nextchange_time,
210  native_value=value_nextchange_time,
211  ),
213  key="nextchange_preset",
214  translation_key="nextchange_preset",
215  entity_category=EntityCategory.DIAGNOSTIC,
216  suitable=suitable_nextchange_temperature,
217  native_value=value_nextchange_preset,
218  ),
220  key="scheduled_preset",
221  translation_key="scheduled_preset",
222  entity_category=EntityCategory.DIAGNOSTIC,
223  suitable=suitable_nextchange_temperature,
224  native_value=value_scheduled_preset,
225  ),
226 )
227 
228 
230  hass: HomeAssistant,
231  entry: FritzboxConfigEntry,
232  async_add_entities: AddEntitiesCallback,
233 ) -> None:
234  """Set up the FRITZ!SmartHome sensor from ConfigEntry."""
235  coordinator = entry.runtime_data
236 
237  @callback
238  def _add_entities(devices: set[str] | None = None) -> None:
239  """Add devices."""
240  if devices is None:
241  devices = coordinator.new_devices
242  if not devices:
243  return
245  FritzBoxSensor(coordinator, ain, description)
246  for ain in devices
247  for description in SENSOR_TYPES
248  if description.suitable(coordinator.data.devices[ain])
249  )
250 
251  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
252 
253  _add_entities(set(coordinator.data.devices))
254 
255 
257  """The entity class for FRITZ!SmartHome sensors."""
258 
259  entity_description: FritzSensorEntityDescription
260 
261  @property
262  def native_value(self) -> StateType | datetime:
263  """Return the state of the sensor."""
264  return self.entity_descriptionentity_description.native_value(self.datadatadatadatadata)
265 
266  @property
267  def entity_category(self) -> EntityCategory | None:
268  """Return the category of the entity, if any."""
269  if self.entity_descriptionentity_description.entity_category_fn is not None:
270  return self.entity_descriptionentity_description.entity_category_fn(self.datadatadatadatadata)
271  return super().entity_category
bool suitable_nextchange_temperature(FritzhomeDevice device)
Definition: sensor.py:64
bool suitable_nextchange_time(FritzhomeDevice device)
Definition: sensor.py:69
float|None value_nextchange_temperature(FritzhomeDevice device)
Definition: sensor.py:104
bool suitable_comfort_temperature(FritzhomeDevice device)
Definition: sensor.py:59
bool suitable_temperature(FritzhomeDevice device)
Definition: sensor.py:74
str|None value_scheduled_preset(FritzhomeDevice device)
Definition: sensor.py:95
None async_setup_entry(HomeAssistant hass, FritzboxConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:233
str|None value_nextchange_preset(FritzhomeDevice device)
Definition: sensor.py:86
EntityCategory|None entity_category_temperature(FritzhomeDevice device)
Definition: sensor.py:79
bool suitable_eco_temperature(FritzhomeDevice device)
Definition: sensor.py:54
datetime|None value_nextchange_time(FritzhomeDevice device)
Definition: sensor.py:111