core/homeassistant/components/overkiz/sensor.py

436 lines
16 KiB
Python

"""Support for Overkiz sensors."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import cast
from pyoverkiz.enums import OverkizAttribute, OverkizState, UIWidget
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
ENERGY_WATT_HOUR,
LIGHT_LUX,
PERCENTAGE,
POWER_WATT,
SIGNAL_STRENGTH_DECIBELS,
TEMP_CELSIUS,
TIME_SECONDS,
VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR,
VOLUME_LITERS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import HomeAssistantOverkizData
from .const import DOMAIN, IGNORED_OVERKIZ_DEVICES
from .coordinator import OverkizDataUpdateCoordinator
from .entity import OverkizDescriptiveEntity, OverkizEntity
@dataclass
class OverkizSensorDescription(SensorEntityDescription):
"""Class to describe an Overkiz sensor."""
native_value: Callable[[str | int | float], str | int | float] | None = None
SENSOR_DESCRIPTIONS: list[OverkizSensorDescription] = [
OverkizSensorDescription(
key=OverkizState.CORE_BATTERY_LEVEL,
name="Battery Level",
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
OverkizSensorDescription(
key=OverkizState.CORE_BATTERY,
name="Battery",
native_value=lambda value: str(value).capitalize(),
entity_category=EntityCategory.DIAGNOSTIC,
),
OverkizSensorDescription(
key=OverkizState.CORE_RSSI_LEVEL,
name="RSSI Level",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
state_class=SensorStateClass.MEASUREMENT,
native_value=lambda value: round(float(value)),
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
),
OverkizSensorDescription(
key=OverkizState.CORE_EXPECTED_NUMBER_OF_SHOWER,
name="Expected Number Of Shower",
icon="mdi:shower-head",
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_NUMBER_OF_SHOWER_REMAINING,
name="Number of Shower Remaining",
icon="mdi:shower-head",
state_class=SensorStateClass.MEASUREMENT,
),
# V40 is measured in litres (L) and shows the amount of warm (mixed) water with a temperature of 40 C, which can be drained from a switched off electric water heater.
OverkizSensorDescription(
key=OverkizState.CORE_V40_WATER_VOLUME_ESTIMATION,
name="Water Volume Estimation at 40 °C",
icon="mdi:water",
native_unit_of_measurement=VOLUME_LITERS,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_WATER_CONSUMPTION,
name="Water Consumption",
icon="mdi:water",
native_unit_of_measurement=VOLUME_LITERS,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.IO_OUTLET_ENGINE,
name="Outlet Engine",
icon="mdi:fan-chevron-down",
native_unit_of_measurement=VOLUME_LITERS,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.IO_INLET_ENGINE,
name="Inlet Engine",
icon="mdi:fan-chevron-up",
native_unit_of_measurement=VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.HLRRWIFI_ROOM_TEMPERATURE,
name="Room Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=TEMP_CELSIUS,
),
OverkizSensorDescription(
key=OverkizState.IO_MIDDLE_WATER_TEMPERATURE,
name="Middle Water Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=TEMP_CELSIUS,
),
OverkizSensorDescription(
key=OverkizState.CORE_FOSSIL_ENERGY_CONSUMPTION,
name="Fossil Energy Consumption",
),
OverkizSensorDescription(
key=OverkizState.CORE_GAS_CONSUMPTION,
name="Gas Consumption",
),
OverkizSensorDescription(
key=OverkizState.CORE_THERMAL_ENERGY_CONSUMPTION,
name="Thermal Energy Consumption",
),
# LightSensor/LuminanceSensor
OverkizSensorDescription(
key=OverkizState.CORE_LUMINANCE,
name="Luminance",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX, # core:MeasuredValueType = core:LuminanceInLux
state_class=SensorStateClass.MEASUREMENT,
),
# ElectricitySensor/CumulativeElectricPowerConsumptionSensor
OverkizSensorDescription(
key=OverkizState.CORE_ELECTRIC_ENERGY_CONSUMPTION,
name="Electric Energy Consumption",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh (not for modbus:YutakiV2DHWElectricalEnergyConsumptionComponent)
state_class=SensorStateClass.TOTAL_INCREASING, # core:MeasurementCategory attribute = electric/overall
),
OverkizSensorDescription(
key=OverkizState.CORE_ELECTRIC_POWER_CONSUMPTION,
name="Electric Power Consumption",
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=POWER_WATT, # core:MeasuredValueType = core:ElectricalEnergyInWh (not for modbus:YutakiV2DHWElectricalEnergyConsumptionComponent)
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF1,
name="Consumption Tariff 1",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF2,
name="Consumption Tariff 2",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF3,
name="Consumption Tariff 3",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF4,
name="Consumption Tariff 4",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF5,
name="Consumption Tariff 5",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF6,
name="Consumption Tariff 6",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF7,
name="Consumption Tariff 7",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF8,
name="Consumption Tariff 8",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_CONSUMPTION_TARIFF9,
name="Consumption Tariff 9",
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=ENERGY_WATT_HOUR, # core:MeasuredValueType = core:ElectricalEnergyInWh
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
),
# HumiditySensor/RelativeHumiditySensor
OverkizSensorDescription(
key=OverkizState.CORE_RELATIVE_HUMIDITY,
name="Relative Humidity",
native_value=lambda value: round(float(value), 2),
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE, # core:MeasuredValueType = core:RelativeValueInPercentage
state_class=SensorStateClass.MEASUREMENT,
),
# TemperatureSensor/TemperatureSensor
OverkizSensorDescription(
key=OverkizState.CORE_TEMPERATURE,
name="Temperature",
native_value=lambda value: round(float(value), 2),
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS, # core:MeasuredValueType = core:TemperatureInCelcius
state_class=SensorStateClass.MEASUREMENT,
),
# WeatherSensor/WeatherForecastSensor
OverkizSensorDescription(
key=OverkizState.CORE_WEATHER_STATUS,
name="Weather Status",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_MINIMUM_TEMPERATURE,
name="Minimum Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
OverkizSensorDescription(
key=OverkizState.CORE_MAXIMUM_TEMPERATURE,
name="Maximum Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
# AirSensor/COSensor
OverkizSensorDescription(
key=OverkizState.CORE_CO_CONCENTRATION,
name="CO Concentration",
device_class=SensorDeviceClass.CO,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
# AirSensor/CO2Sensor
OverkizSensorDescription(
key=OverkizState.CORE_CO2_CONCENTRATION,
name="CO2 Concentration",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
# SunSensor/SunEnergySensor
OverkizSensorDescription(
key=OverkizState.CORE_SUN_ENERGY,
name="Sun Energy",
native_value=lambda value: round(float(value), 2),
icon="mdi:solar-power",
state_class=SensorStateClass.MEASUREMENT,
),
# WindSensor/WindSpeedSensor
OverkizSensorDescription(
key=OverkizState.CORE_WIND_SPEED,
name="Wind Speed",
native_value=lambda value: round(float(value), 2),
icon="mdi:weather-windy",
state_class=SensorStateClass.MEASUREMENT,
),
# SmokeSensor/SmokeSensor
OverkizSensorDescription(
key=OverkizState.IO_SENSOR_ROOM,
name="Sensor Room",
native_value=lambda value: str(value).capitalize(),
entity_registry_enabled_default=False,
),
OverkizSensorDescription(
key=OverkizState.IO_PRIORITY_LOCK_ORIGINATOR,
name="Priority Lock Originator",
native_value=lambda value: str(value).capitalize(),
icon="mdi:lock",
entity_registry_enabled_default=False,
),
OverkizSensorDescription(
key=OverkizState.CORE_PRIORITY_LOCK_TIMER,
name="Priority Lock Timer",
icon="mdi:lock-clock",
native_unit_of_measurement=TIME_SECONDS,
entity_registry_enabled_default=False,
),
OverkizSensorDescription(
key=OverkizState.CORE_DISCRETE_RSSI_LEVEL,
name="Discrete RSSI Level",
entity_registry_enabled_default=False,
native_value=lambda value: str(value).capitalize(),
entity_category=EntityCategory.DIAGNOSTIC,
),
# DomesticHotWaterProduction/WaterHeatingSystem
OverkizSensorDescription(
key=OverkizState.IO_HEAT_PUMP_OPERATING_TIME,
name="Heat Pump Operating Time",
),
OverkizSensorDescription(
key=OverkizState.IO_ELECTRIC_BOOSTER_OPERATING_TIME,
name="Electric Booster Operating Time",
),
]
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Overkiz sensors from a config entry."""
data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id]
entities: list[SensorEntity] = []
key_supported_states = {
description.key: description for description in SENSOR_DESCRIPTIONS
}
for device in data.coordinator.data.values():
if device.widget == UIWidget.HOMEKIT_STACK:
entities.append(
OverkizHomeKitSetupCodeSensor(
device.device_url,
data.coordinator,
)
)
if (
device.widget in IGNORED_OVERKIZ_DEVICES
or device.ui_class in IGNORED_OVERKIZ_DEVICES
):
continue
for state in device.definition.states:
if description := key_supported_states.get(state.qualified_name):
entities.append(
OverkizStateSensor(
device.device_url,
data.coordinator,
description,
)
)
async_add_entities(entities)
class OverkizStateSensor(OverkizDescriptiveEntity, SensorEntity):
"""Representation of an Overkiz Sensor."""
entity_description: OverkizSensorDescription
@property
def native_value(self) -> StateType:
"""Return the value of the sensor."""
state = self.device.states.get(self.entity_description.key)
if not state:
return None
# Transform the value with a lambda function
if self.entity_description.native_value:
return self.entity_description.native_value(state.value)
return cast(str, state.value)
class OverkizHomeKitSetupCodeSensor(OverkizEntity, SensorEntity):
"""Representation of an Overkiz HomeKit Setup Code."""
_attr_icon = "mdi:shield-home"
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self, device_url: str, coordinator: OverkizDataUpdateCoordinator
) -> None:
"""Initialize the device."""
super().__init__(device_url, coordinator)
self._attr_name = "HomeKit Setup Code"
@property
def native_value(self) -> str | None:
"""Return the value of the sensor."""
if state := self.device.attributes.get(OverkizAttribute.HOMEKIT_SETUP_CODE):
return cast(str, state.value)
return None
@property
def device_info(self) -> DeviceInfo:
"""Return device registry information for this entity."""
# By default this sensor will be listed at a virtual HomekitStack device,
# but it makes more sense to show this at the gateway device in the entity registry.
return {
"identifiers": {(DOMAIN, self.executor.get_gateway_id())},
}