Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for Overkiz sensors."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from typing import cast
8 
9 from pyoverkiz.enums import OverkizAttribute, OverkizState, UIWidget
10 from pyoverkiz.types import StateType as OverkizStateType
11 
13  SensorDeviceClass,
14  SensorEntity,
15  SensorEntityDescription,
16  SensorStateClass,
17 )
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.const import (
20  CONCENTRATION_PARTS_PER_MILLION,
21  LIGHT_LUX,
22  PERCENTAGE,
23  SIGNAL_STRENGTH_DECIBELS,
24  EntityCategory,
25  UnitOfEnergy,
26  UnitOfPower,
27  UnitOfTemperature,
28  UnitOfTime,
29  UnitOfVolume,
30  UnitOfVolumeFlowRate,
31 )
32 from homeassistant.core import HomeAssistant
33 from homeassistant.helpers.device_registry import DeviceInfo
34 from homeassistant.helpers.entity_platform import AddEntitiesCallback
35 from homeassistant.helpers.typing import StateType
36 
37 from . import HomeAssistantOverkizData
38 from .const import (
39  DOMAIN,
40  IGNORED_OVERKIZ_DEVICES,
41  OVERKIZ_STATE_TO_TRANSLATION,
42  OVERKIZ_UNIT_TO_HA,
43 )
44 from .coordinator import OverkizDataUpdateCoordinator
45 from .entity import OverkizDescriptiveEntity, OverkizEntity
46 
47 
48 @dataclass(frozen=True)
50  """Class to describe an Overkiz sensor."""
51 
52  native_value: Callable[[OverkizStateType], StateType] | None = None
53 
54 
55 SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
57  key=OverkizState.CORE_BATTERY_LEVEL,
58  name="Battery level",
59  native_unit_of_measurement=PERCENTAGE,
60  device_class=SensorDeviceClass.BATTERY,
61  state_class=SensorStateClass.MEASUREMENT,
62  entity_category=EntityCategory.DIAGNOSTIC,
63  native_value=lambda value: int(float(str(value).strip("%"))),
64  ),
66  key=OverkizState.CORE_BATTERY,
67  name="Battery",
68  entity_category=EntityCategory.DIAGNOSTIC,
69  icon="mdi:battery",
70  device_class=SensorDeviceClass.ENUM,
71  options=["full", "normal", "medium", "low", "verylow"],
72  translation_key="battery",
73  ),
75  key=OverkizState.CORE_RSSI_LEVEL,
76  name="RSSI level",
77  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
78  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
79  state_class=SensorStateClass.MEASUREMENT,
80  native_value=lambda value: round(cast(float, value)),
81  entity_category=EntityCategory.DIAGNOSTIC,
82  entity_registry_enabled_default=False,
83  ),
85  key=OverkizState.CORE_EXPECTED_NUMBER_OF_SHOWER,
86  name="Expected number of shower",
87  icon="mdi:shower-head",
88  state_class=SensorStateClass.MEASUREMENT,
89  ),
91  key=OverkizState.CORE_NUMBER_OF_SHOWER_REMAINING,
92  name="Number of shower remaining",
93  icon="mdi:shower-head",
94  state_class=SensorStateClass.MEASUREMENT,
95  ),
96  # V40 is measured in litres (L) and shows the amount of warm (mixed) water
97  # with a temperature of 40 C, which can be drained from
98  # a switched off electric water heater.
100  key=OverkizState.CORE_V40_WATER_VOLUME_ESTIMATION,
101  name="Water volume estimation at 40 °C",
102  icon="mdi:water",
103  native_unit_of_measurement=UnitOfVolume.LITERS,
104  device_class=SensorDeviceClass.VOLUME_STORAGE,
105  entity_registry_enabled_default=False,
106  state_class=SensorStateClass.MEASUREMENT,
107  ),
109  key=OverkizState.CORE_WATER_CONSUMPTION,
110  name="Water consumption",
111  icon="mdi:water",
112  native_unit_of_measurement=UnitOfVolume.LITERS,
113  device_class=SensorDeviceClass.WATER,
114  state_class=SensorStateClass.TOTAL_INCREASING,
115  ),
117  key=OverkizState.IO_OUTLET_ENGINE,
118  name="Outlet engine",
119  icon="mdi:fan-chevron-down",
120  native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
121  state_class=SensorStateClass.MEASUREMENT,
122  ),
124  key=OverkizState.IO_INLET_ENGINE,
125  name="Inlet engine",
126  icon="mdi:fan-chevron-up",
127  native_unit_of_measurement=UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
128  state_class=SensorStateClass.MEASUREMENT,
129  ),
131  key=OverkizState.HLRRWIFI_ROOM_TEMPERATURE,
132  name="Room temperature",
133  device_class=SensorDeviceClass.TEMPERATURE,
134  state_class=SensorStateClass.MEASUREMENT,
135  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
136  ),
138  key=OverkizState.IO_MIDDLE_WATER_TEMPERATURE,
139  name="Middle water temperature",
140  device_class=SensorDeviceClass.TEMPERATURE,
141  state_class=SensorStateClass.MEASUREMENT,
142  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
143  ),
145  key=OverkizState.CORE_FOSSIL_ENERGY_CONSUMPTION,
146  name="Fossil energy consumption",
147  ),
149  key=OverkizState.CORE_GAS_CONSUMPTION,
150  name="Gas consumption",
151  ),
153  key=OverkizState.CORE_THERMAL_ENERGY_CONSUMPTION,
154  name="Thermal energy consumption",
155  ),
156  # LightSensor/LuminanceSensor
158  key=OverkizState.CORE_LUMINANCE,
159  name="Luminance",
160  device_class=SensorDeviceClass.ILLUMINANCE,
161  # core:MeasuredValueType = core:LuminanceInLux
162  native_unit_of_measurement=LIGHT_LUX,
163  state_class=SensorStateClass.MEASUREMENT,
164  ),
165  # ElectricitySensor/CumulativeElectricPowerConsumptionSensor
167  key=OverkizState.CORE_ELECTRIC_ENERGY_CONSUMPTION,
168  name="Electric energy consumption",
169  device_class=SensorDeviceClass.ENERGY,
170  # core:MeasuredValueType = core:ElectricalEnergyInWh
171  # (not for modbus:YutakiV2DHWElectricalEnergyConsumptionComponent)
172  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
173  # core:MeasurementCategory attribute = electric/overall
174  state_class=SensorStateClass.TOTAL_INCREASING,
175  ),
177  key=OverkizState.CORE_ELECTRIC_POWER_CONSUMPTION,
178  name="Electric power consumption",
179  device_class=SensorDeviceClass.POWER,
180  # core:MeasuredValueType = core:ElectricalEnergyInWh
181  # (not for modbus:YutakiV2DHWElectricalEnergyConsumptionComponent)
182  native_unit_of_measurement=UnitOfPower.WATT,
183  state_class=SensorStateClass.MEASUREMENT,
184  ),
186  key=OverkizState.MODBUSLINK_POWER_HEAT_ELECTRICAL,
187  name="Electric power consumption",
188  device_class=SensorDeviceClass.POWER,
189  native_unit_of_measurement=UnitOfPower.WATT,
190  state_class=SensorStateClass.MEASUREMENT,
191  ),
193  key=OverkizState.CORE_CONSUMPTION_TARIFF1,
194  name="Consumption tariff 1",
195  device_class=SensorDeviceClass.ENERGY,
196  # core:MeasuredValueType = core:ElectricalEnergyInWh
197  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
198  entity_registry_enabled_default=False,
199  state_class=SensorStateClass.MEASUREMENT,
200  ),
202  key=OverkizState.CORE_CONSUMPTION_TARIFF2,
203  name="Consumption tariff 2",
204  device_class=SensorDeviceClass.ENERGY,
205  # core:MeasuredValueType = core:ElectricalEnergyInWh
206  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
207  entity_registry_enabled_default=False,
208  state_class=SensorStateClass.MEASUREMENT,
209  ),
211  key=OverkizState.CORE_CONSUMPTION_TARIFF3,
212  name="Consumption tariff 3",
213  device_class=SensorDeviceClass.ENERGY,
214  # core:MeasuredValueType = core:ElectricalEnergyInWh
215  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
216  entity_registry_enabled_default=False,
217  state_class=SensorStateClass.MEASUREMENT,
218  ),
220  key=OverkizState.CORE_CONSUMPTION_TARIFF4,
221  name="Consumption tariff 4",
222  device_class=SensorDeviceClass.ENERGY,
223  # core:MeasuredValueType = core:ElectricalEnergyInWh
224  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
225  entity_registry_enabled_default=False,
226  state_class=SensorStateClass.MEASUREMENT,
227  ),
229  key=OverkizState.CORE_CONSUMPTION_TARIFF5,
230  name="Consumption tariff 5",
231  device_class=SensorDeviceClass.ENERGY,
232  # core:MeasuredValueType = core:ElectricalEnergyInWh
233  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
234  entity_registry_enabled_default=False,
235  state_class=SensorStateClass.MEASUREMENT,
236  ),
238  key=OverkizState.CORE_CONSUMPTION_TARIFF6,
239  name="Consumption tariff 6",
240  device_class=SensorDeviceClass.ENERGY,
241  # core:MeasuredValueType = core:ElectricalEnergyInWh
242  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
243  entity_registry_enabled_default=False,
244  state_class=SensorStateClass.MEASUREMENT,
245  ),
247  key=OverkizState.CORE_CONSUMPTION_TARIFF7,
248  name="Consumption tariff 7",
249  device_class=SensorDeviceClass.ENERGY,
250  # core:MeasuredValueType = core:ElectricalEnergyInWh
251  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
252  entity_registry_enabled_default=False,
253  state_class=SensorStateClass.MEASUREMENT,
254  ),
256  key=OverkizState.CORE_CONSUMPTION_TARIFF8,
257  name="Consumption tariff 8",
258  device_class=SensorDeviceClass.ENERGY,
259  # core:MeasuredValueType = core:ElectricalEnergyInWh
260  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
261  entity_registry_enabled_default=False,
262  state_class=SensorStateClass.MEASUREMENT,
263  ),
265  key=OverkizState.CORE_CONSUMPTION_TARIFF9,
266  name="Consumption tariff 9",
267  device_class=SensorDeviceClass.ENERGY,
268  # core:MeasuredValueType = core:ElectricalEnergyInWh
269  native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
270  entity_registry_enabled_default=False,
271  state_class=SensorStateClass.MEASUREMENT,
272  ),
273  # HumiditySensor/RelativeHumiditySensor
275  key=OverkizState.CORE_RELATIVE_HUMIDITY,
276  name="Relative humidity",
277  native_value=lambda value: round(cast(float, value), 2),
278  device_class=SensorDeviceClass.HUMIDITY,
279  # core:MeasuredValueType = core:RelativeValueInPercentage
280  native_unit_of_measurement=PERCENTAGE,
281  state_class=SensorStateClass.MEASUREMENT,
282  ),
283  # TemperatureSensor/TemperatureSensor
285  key=OverkizState.CORE_TEMPERATURE,
286  name="Temperature",
287  native_value=lambda value: round(cast(float, value), 2),
288  device_class=SensorDeviceClass.TEMPERATURE,
289  # core:MeasuredValueType = core:TemperatureInCelcius
290  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
291  state_class=SensorStateClass.MEASUREMENT,
292  ),
293  # WeatherSensor/WeatherForecastSensor
295  key=OverkizState.CORE_WEATHER_STATUS,
296  name="Weather status",
297  device_class=SensorDeviceClass.TEMPERATURE,
298  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
299  state_class=SensorStateClass.MEASUREMENT,
300  ),
302  key=OverkizState.CORE_MINIMUM_TEMPERATURE,
303  name="Minimum temperature",
304  device_class=SensorDeviceClass.TEMPERATURE,
305  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
306  state_class=SensorStateClass.MEASUREMENT,
307  ),
309  key=OverkizState.CORE_MAXIMUM_TEMPERATURE,
310  name="Maximum temperature",
311  device_class=SensorDeviceClass.TEMPERATURE,
312  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
313  state_class=SensorStateClass.MEASUREMENT,
314  ),
315  # AirSensor/COSensor
317  key=OverkizState.CORE_CO_CONCENTRATION,
318  name="CO concentration",
319  device_class=SensorDeviceClass.CO,
320  native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
321  state_class=SensorStateClass.MEASUREMENT,
322  ),
323  # AirSensor/CO2Sensor
325  key=OverkizState.CORE_CO2_CONCENTRATION,
326  name="CO2 concentration",
327  device_class=SensorDeviceClass.CO2,
328  native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
329  state_class=SensorStateClass.MEASUREMENT,
330  ),
331  # SunSensor/SunEnergySensor
333  key=OverkizState.CORE_SUN_ENERGY,
334  name="Sun energy",
335  native_value=lambda value: round(cast(float, value), 2),
336  icon="mdi:solar-power",
337  state_class=SensorStateClass.MEASUREMENT,
338  ),
339  # WindSensor/WindSpeedSensor
341  key=OverkizState.CORE_WIND_SPEED,
342  name="Wind speed",
343  native_value=lambda value: round(cast(float, value), 2),
344  icon="mdi:weather-windy",
345  state_class=SensorStateClass.MEASUREMENT,
346  ),
347  # SmokeSensor/SmokeSensor
349  key=OverkizState.IO_SENSOR_ROOM,
350  name="Sensor room",
351  device_class=SensorDeviceClass.ENUM,
352  options=["clean", "dirty"],
353  entity_category=EntityCategory.DIAGNOSTIC,
354  icon="mdi:spray-bottle",
355  translation_key="sensor_room",
356  ),
358  key=OverkizState.IO_PRIORITY_LOCK_ORIGINATOR,
359  name="Priority lock originator",
360  icon="mdi:lock",
361  entity_registry_enabled_default=False,
362  translation_key="priority_lock_originator",
363  native_value=lambda value: OVERKIZ_STATE_TO_TRANSLATION.get(
364  cast(str, value), cast(str, value)
365  ),
366  ),
368  key=OverkizState.CORE_PRIORITY_LOCK_TIMER,
369  name="Priority lock timer",
370  icon="mdi:lock-clock",
371  native_unit_of_measurement=UnitOfTime.SECONDS,
372  entity_registry_enabled_default=False,
373  ),
375  key=OverkizState.CORE_DISCRETE_RSSI_LEVEL,
376  name="Discrete RSSI level",
377  entity_registry_enabled_default=False,
378  entity_category=EntityCategory.DIAGNOSTIC,
379  icon="mdi:wifi",
380  device_class=SensorDeviceClass.ENUM,
381  options=["verylow", "low", "normal", "good"],
382  translation_key="discrete_rssi_level",
383  ),
385  key=OverkizState.CORE_SENSOR_DEFECT,
386  name="Sensor defect",
387  entity_registry_enabled_default=False,
388  entity_category=EntityCategory.DIAGNOSTIC,
389  translation_key="sensor_defect",
390  native_value=lambda value: OVERKIZ_STATE_TO_TRANSLATION.get(
391  cast(str, value), cast(str, value)
392  ),
393  ),
394  # DomesticHotWaterProduction/WaterHeatingSystem
396  key=OverkizState.IO_HEAT_PUMP_OPERATING_TIME,
397  name="Heat pump operating time",
398  device_class=SensorDeviceClass.DURATION,
399  entity_category=EntityCategory.DIAGNOSTIC,
400  native_unit_of_measurement=UnitOfTime.SECONDS,
401  ),
403  key=OverkizState.IO_ELECTRIC_BOOSTER_OPERATING_TIME,
404  name="Electric booster operating time",
405  device_class=SensorDeviceClass.DURATION,
406  native_unit_of_measurement=UnitOfTime.SECONDS,
407  entity_category=EntityCategory.DIAGNOSTIC,
408  ),
410  key=OverkizState.CORE_BOTTOM_TANK_WATER_TEMPERATURE,
411  name="Bottom tank water temperature",
412  device_class=SensorDeviceClass.TEMPERATURE,
413  state_class=SensorStateClass.MEASUREMENT,
414  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
415  ),
417  key=OverkizState.CORE_CONTROL_WATER_TARGET_TEMPERATURE,
418  name="Control water target temperature",
419  device_class=SensorDeviceClass.TEMPERATURE,
420  state_class=SensorStateClass.MEASUREMENT,
421  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
422  ),
424  key=OverkizState.CORE_REMAINING_HOT_WATER,
425  name="Warm water remaining",
426  device_class=SensorDeviceClass.VOLUME,
427  state_class=SensorStateClass.MEASUREMENT,
428  native_unit_of_measurement=UnitOfVolume.LITERS,
429  ),
430  # Cover
432  key=OverkizState.CORE_TARGET_CLOSURE,
433  name="Target closure",
434  native_unit_of_measurement=PERCENTAGE,
435  entity_registry_enabled_default=False,
436  ),
437  # ThreeWayWindowHandle/WindowHandle
439  key=OverkizState.CORE_THREE_WAY_HANDLE_DIRECTION,
440  name="Three way handle direction",
441  device_class=SensorDeviceClass.ENUM,
442  options=["open", "tilt", "closed"],
443  translation_key="three_way_handle_direction",
444  ),
445  # Hitachi air to air heatpump outdoor temperature sensors (HLRRWIFI protocol)
447  key=OverkizState.HLRRWIFI_OUTDOOR_TEMPERATURE,
448  name="Outdoor temperature",
449  device_class=SensorDeviceClass.TEMPERATURE,
450  state_class=SensorStateClass.MEASUREMENT,
451  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
452  ),
453  # Hitachi air to air heatpump outdoor temperature sensors (OVP protocol)
455  key=OverkizState.OVP_OUTDOOR_TEMPERATURE,
456  name="Outdoor temperature",
457  device_class=SensorDeviceClass.TEMPERATURE,
458  state_class=SensorStateClass.MEASUREMENT,
459  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
460  ),
461 ]
462 
463 SUPPORTED_STATES = {description.key: description for description in SENSOR_DESCRIPTIONS}
464 
465 
467  hass: HomeAssistant,
468  entry: ConfigEntry,
469  async_add_entities: AddEntitiesCallback,
470 ) -> None:
471  """Set up the Overkiz sensors from a config entry."""
472  data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id]
473  entities: list[SensorEntity] = []
474 
475  for device in data.coordinator.data.values():
476  if device.widget == UIWidget.HOMEKIT_STACK:
477  entities.append(
479  device.device_url,
480  data.coordinator,
481  )
482  )
483 
484  if (
485  device.widget in IGNORED_OVERKIZ_DEVICES
486  or device.ui_class in IGNORED_OVERKIZ_DEVICES
487  ):
488  continue
489 
490  entities.extend(
492  device.device_url,
493  data.coordinator,
494  description,
495  )
496  for state in device.definition.states
497  if (description := SUPPORTED_STATES.get(state.qualified_name))
498  )
499 
500  async_add_entities(entities)
501 
502 
504  """Representation of an Overkiz Sensor."""
505 
506  entity_description: OverkizSensorDescription
507 
508  @property
509  def native_value(self) -> StateType:
510  """Return the value of the sensor."""
511  state = self.devicedevice.states.get(self.entity_descriptionentity_description.key)
512 
513  if (
514  state is None
515  or state.value is None
516  # It seems that in some cases we return `None` if state.value is falsy.
517  # This is probably incorrect and should be fixed in a follow up PR.
518  # To ensure measurement sensors do not get an `unknown` state on
519  # a falsy value (e.g. 0 or 0.0) we also check the state_class.
520  or self.state_classstate_class != SensorStateClass.MEASUREMENT
521  and not state.value
522  ):
523  return None
524 
525  # Transform the value with a lambda function
526  if self.entity_descriptionentity_description.native_value:
527  return self.entity_descriptionentity_description.native_value(state.value)
528 
529  if isinstance(state.value, (dict, list)):
530  return None
531 
532  return state.value
533 
534  @property
535  def native_unit_of_measurement(self) -> str | None:
536  """Return the unit of measurement."""
537  if (
538  not (default_unit := self.entity_descriptionentity_description.native_unit_of_measurement)
539  or not (state := self.devicedevice.states.get(self.entity_descriptionentity_description.key))
540  or not state.value
541  ):
542  return default_unit
543 
544  attrs = self.devicedevice.attributes
545  if (unit := attrs[f"{state.name}MeasuredValueType"]) and (
546  unit_value := unit.value_as_str
547  ):
548  return OVERKIZ_UNIT_TO_HA.get(unit_value, default_unit)
549 
550  if (unit := attrs[OverkizAttribute.CORE_MEASURED_VALUE_TYPE]) and (
551  unit_value := unit.value_as_str
552  ):
553  return OVERKIZ_UNIT_TO_HA.get(unit_value, default_unit)
554 
555  return default_unit
556 
557 
559  """Representation of an Overkiz HomeKit Setup Code."""
560 
561  _attr_icon = "mdi:shield-home"
562  _attr_entity_category = EntityCategory.DIAGNOSTIC
563 
564  def __init__(
565  self, device_url: str, coordinator: OverkizDataUpdateCoordinator
566  ) -> None:
567  """Initialize the device."""
568  super().__init__(device_url, coordinator)
569  self._attr_name_attr_name_attr_name = "HomeKit setup code"
570 
571  @property
572  def native_value(self) -> str | None:
573  """Return the value of the sensor."""
574  if state := self.devicedevice.attributes.get(OverkizAttribute.HOMEKIT_SETUP_CODE):
575  return cast(str, state.value)
576  return None
577 
578  @property
579  def device_info(self) -> DeviceInfo:
580  """Return device registry information for this entity."""
581  # By default this sensor will be listed at a virtual HomekitStack device,
582  # but it makes more sense to show this at the gateway device
583  # in the entity registry.
584  return DeviceInfo(
585  identifiers={(DOMAIN, self.executorexecutor.get_gateway_id())},
586  )
None __init__(self, str device_url, OverkizDataUpdateCoordinator coordinator)
Definition: sensor.py:566
SensorStateClass|str|None state_class(self)
Definition: __init__.py:342
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:470