diff --git a/homeassistant/components/toon/binary_sensor.py b/homeassistant/components/toon/binary_sensor.py index 9983dc4bee6..30f5459c175 100644 --- a/homeassistant/components/toon/binary_sensor.py +++ b/homeassistant/components/toon/binary_sensor.py @@ -1,27 +1,25 @@ """Support for Toon binary sensors.""" from __future__ import annotations -from homeassistant.components.binary_sensor import BinarySensorEntity +from dataclasses import dataclass + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, + BinarySensorEntityDescription, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import ( - ATTR_DEFAULT_ENABLED, - ATTR_DEVICE_CLASS, - ATTR_ICON, - ATTR_INVERTED, - ATTR_MEASUREMENT, - ATTR_NAME, - ATTR_SECTION, - BINARY_SENSOR_ENTITIES, - DOMAIN, -) +from .const import DOMAIN from .coordinator import ToonDataUpdateCoordinator from .models import ( ToonBoilerDeviceEntity, ToonBoilerModuleDeviceEntity, ToonDisplayDeviceEntity, ToonEntity, + ToonRequiredKeysMixin, ) @@ -31,64 +29,51 @@ async def async_setup_entry( """Set up a Toon binary sensor based on a config entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - sensors = [ - ToonBoilerModuleBinarySensor( - coordinator, key="thermostat_info_boiler_connected_None" - ), - ToonDisplayBinarySensor(coordinator, key="thermostat_program_overridden"), + entities = [ + description.cls(coordinator, description) + for description in BINARY_SENSOR_ENTITIES ] - if coordinator.data.thermostat.have_opentherm_boiler: - sensors.extend( + entities.extend( [ - ToonBoilerBinarySensor(coordinator, key=key) - for key in ( - "thermostat_info_ot_communication_error_0", - "thermostat_info_error_found_255", - "thermostat_info_burner_info_None", - "thermostat_info_burner_info_1", - "thermostat_info_burner_info_2", - "thermostat_info_burner_info_3", - ) + description.cls(coordinator, description) + for description in BINARY_SENSOR_ENTITIES_BOILER ] ) - async_add_entities(sensors, True) + async_add_entities(entities, True) class ToonBinarySensor(ToonEntity, BinarySensorEntity): """Defines an Toon binary sensor.""" - def __init__(self, coordinator: ToonDataUpdateCoordinator, *, key: str) -> None: + entity_description: ToonBinarySensorEntityDescription + + def __init__( + self, + coordinator: ToonDataUpdateCoordinator, + description: ToonBinarySensorEntityDescription, + ) -> None: """Initialize the Toon sensor.""" super().__init__(coordinator) - self.key = key + self.entity_description = description - sensor = BINARY_SENSOR_ENTITIES[key] - self._attr_name = sensor[ATTR_NAME] - self._attr_icon = sensor.get(ATTR_ICON) - self._attr_entity_registry_enabled_default = sensor.get( - ATTR_DEFAULT_ENABLED, True - ) - self._attr_device_class = sensor.get(ATTR_DEVICE_CLASS) self._attr_unique_id = ( # This unique ID is a bit ugly and contains unneeded information. # It is here for legacy / backward compatible reasons. - f"{DOMAIN}_{coordinator.data.agreement.agreement_id}_binary_sensor_{key}" + f"{DOMAIN}_{coordinator.data.agreement.agreement_id}_binary_sensor_{description.key}" ) @property def is_on(self) -> bool | None: """Return the status of the binary sensor.""" - section = getattr( - self.coordinator.data, BINARY_SENSOR_ENTITIES[self.key][ATTR_SECTION] - ) - value = getattr(section, BINARY_SENSOR_ENTITIES[self.key][ATTR_MEASUREMENT]) + section = getattr(self.coordinator.data, self.entity_description.section) + value = getattr(section, self.entity_description.measurement) if value is None: return None - if BINARY_SENSOR_ENTITIES[self.key].get(ATTR_INVERTED, False): + if self.entity_description.inverted: return not value return value @@ -104,3 +89,96 @@ class ToonDisplayBinarySensor(ToonBinarySensor, ToonDisplayDeviceEntity): class ToonBoilerModuleBinarySensor(ToonBinarySensor, ToonBoilerModuleDeviceEntity): """Defines a Boiler module binary sensor.""" + + +@dataclass +class ToonBinarySensorRequiredKeysMixin(ToonRequiredKeysMixin): + """Mixin for binary sensor required keys.""" + + cls: type[ToonBinarySensor] + + +@dataclass +class ToonBinarySensorEntityDescription( + BinarySensorEntityDescription, ToonBinarySensorRequiredKeysMixin +): + """Describes Toon binary sensor entity.""" + + inverted: bool = False + + +BINARY_SENSOR_ENTITIES = ( + ToonBinarySensorEntityDescription( + key="thermostat_info_boiler_connected_None", + name="Boiler Module Connection", + section="thermostat", + measurement="boiler_module_connected", + device_class=DEVICE_CLASS_CONNECTIVITY, + entity_registry_enabled_default=False, + cls=ToonBoilerModuleBinarySensor, + ), + ToonBinarySensorEntityDescription( + key="thermostat_program_overridden", + name="Thermostat Program Override", + section="thermostat", + measurement="program_overridden", + icon="mdi:gesture-tap", + cls=ToonDisplayBinarySensor, + ), +) + +BINARY_SENSOR_ENTITIES_BOILER: tuple[ToonBinarySensorEntityDescription, ...] = ( + ToonBinarySensorEntityDescription( + key="thermostat_info_burner_info_1", + name="Boiler Heating", + section="thermostat", + measurement="heating", + icon="mdi:fire", + entity_registry_enabled_default=False, + cls=ToonBoilerBinarySensor, + ), + ToonBinarySensorEntityDescription( + key="thermostat_info_burner_info_2", + name="Hot Tap Water", + section="thermostat", + measurement="hot_tapwater", + icon="mdi:water-pump", + cls=ToonBoilerBinarySensor, + ), + ToonBinarySensorEntityDescription( + key="thermostat_info_burner_info_3", + name="Boiler Preheating", + section="thermostat", + measurement="pre_heating", + icon="mdi:fire", + entity_registry_enabled_default=False, + cls=ToonBoilerBinarySensor, + ), + ToonBinarySensorEntityDescription( + key="thermostat_info_burner_info_None", + name="Boiler Burner", + section="thermostat", + measurement="burner", + icon="mdi:fire", + cls=ToonBoilerBinarySensor, + ), + ToonBinarySensorEntityDescription( + key="thermostat_info_error_found_255", + name="Boiler Status", + section="thermostat", + measurement="error_found", + device_class=DEVICE_CLASS_PROBLEM, + icon="mdi:alert", + cls=ToonBoilerBinarySensor, + ), + ToonBinarySensorEntityDescription( + key="thermostat_info_ot_communication_error_0", + name="OpenTherm Connection", + section="thermostat", + measurement="opentherm_communication_error", + device_class=DEVICE_CLASS_PROBLEM, + icon="mdi:check-network-outline", + entity_registry_enabled_default=False, + cls=ToonBoilerBinarySensor, + ), +) diff --git a/homeassistant/components/toon/const.py b/homeassistant/components/toon/const.py index 678b3400b88..bf70c54e5e0 100644 --- a/homeassistant/components/toon/const.py +++ b/homeassistant/components/toon/const.py @@ -1,31 +1,6 @@ """Constants for the Toon integration.""" from datetime import timedelta -from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_CONNECTIVITY, - DEVICE_CLASS_PROBLEM, -) -from homeassistant.components.sensor import ( - ATTR_STATE_CLASS, - DEVICE_CLASS_ENERGY, - DEVICE_CLASS_POWER, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, - STATE_CLASS_TOTAL_INCREASING, -) -from homeassistant.const import ( - ATTR_DEVICE_CLASS, - ATTR_ICON, - ATTR_NAME, - ATTR_UNIT_OF_MEASUREMENT, - DEVICE_CLASS_GAS, - ENERGY_KILO_WATT_HOUR, - PERCENTAGE, - POWER_WATT, - TEMP_CELSIUS, - VOLUME_CUBIC_METERS, -) - DOMAIN = "toon" CONF_AGREEMENT = "agreement" @@ -41,332 +16,3 @@ CURRENCY_EUR = "EUR" VOLUME_CM3 = "CM3" VOLUME_LHOUR = "L/H" VOLUME_LMIN = "L/MIN" - -ATTR_DEFAULT_ENABLED = "default_enabled" -ATTR_INVERTED = "inverted" -ATTR_MEASUREMENT = "measurement" -ATTR_SECTION = "section" - -BINARY_SENSOR_ENTITIES = { - "thermostat_info_boiler_connected_None": { - ATTR_NAME: "Boiler Module Connection", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "boiler_module_connected", - ATTR_DEVICE_CLASS: DEVICE_CLASS_CONNECTIVITY, - ATTR_DEFAULT_ENABLED: False, - }, - "thermostat_info_burner_info_1": { - ATTR_NAME: "Boiler Heating", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "heating", - ATTR_ICON: "mdi:fire", - ATTR_DEFAULT_ENABLED: False, - }, - "thermostat_info_burner_info_2": { - ATTR_NAME: "Hot Tap Water", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "hot_tapwater", - ATTR_ICON: "mdi:water-pump", - }, - "thermostat_info_burner_info_3": { - ATTR_NAME: "Boiler Preheating", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "pre_heating", - ATTR_ICON: "mdi:fire", - ATTR_DEFAULT_ENABLED: False, - }, - "thermostat_info_burner_info_None": { - ATTR_NAME: "Boiler Burner", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "burner", - ATTR_ICON: "mdi:fire", - }, - "thermostat_info_error_found_255": { - ATTR_NAME: "Boiler Status", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "error_found", - ATTR_DEVICE_CLASS: DEVICE_CLASS_PROBLEM, - ATTR_ICON: "mdi:alert", - }, - "thermostat_info_ot_communication_error_0": { - ATTR_NAME: "OpenTherm Connection", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "opentherm_communication_error", - ATTR_DEVICE_CLASS: DEVICE_CLASS_PROBLEM, - ATTR_ICON: "mdi:check-network-outline", - ATTR_DEFAULT_ENABLED: False, - }, - "thermostat_program_overridden": { - ATTR_NAME: "Thermostat Program Override", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "program_overridden", - ATTR_ICON: "mdi:gesture-tap", - }, -} - -SENSOR_ENTITIES = { - "current_display_temperature": { - ATTR_NAME: "Temperature", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "current_display_temperature", - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS, - ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, - ATTR_DEFAULT_ENABLED: False, - ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, - }, - "gas_average": { - ATTR_NAME: "Average Gas Usage", - ATTR_SECTION: "gas_usage", - ATTR_MEASUREMENT: "average", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_CM3, - ATTR_ICON: "mdi:gas-cylinder", - }, - "gas_average_daily": { - ATTR_NAME: "Average Daily Gas Usage", - ATTR_SECTION: "gas_usage", - ATTR_MEASUREMENT: "day_average", - ATTR_DEVICE_CLASS: DEVICE_CLASS_GAS, - ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, - ATTR_DEFAULT_ENABLED: False, - }, - "gas_daily_usage": { - ATTR_NAME: "Gas Usage Today", - ATTR_SECTION: "gas_usage", - ATTR_MEASUREMENT: "day_usage", - ATTR_DEVICE_CLASS: DEVICE_CLASS_GAS, - ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, - }, - "gas_daily_cost": { - ATTR_NAME: "Gas Cost Today", - ATTR_SECTION: "gas_usage", - ATTR_MEASUREMENT: "day_cost", - ATTR_UNIT_OF_MEASUREMENT: CURRENCY_EUR, - ATTR_ICON: "mdi:gas-cylinder", - }, - "gas_meter_reading": { - ATTR_NAME: "Gas Meter", - ATTR_SECTION: "gas_usage", - ATTR_MEASUREMENT: "meter", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, - ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, - ATTR_DEVICE_CLASS: DEVICE_CLASS_GAS, - ATTR_DEFAULT_ENABLED: False, - }, - "gas_value": { - ATTR_NAME: "Current Gas Usage", - ATTR_SECTION: "gas_usage", - ATTR_MEASUREMENT: "current", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_CM3, - ATTR_ICON: "mdi:gas-cylinder", - }, - "power_average": { - ATTR_NAME: "Average Power Usage", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "average", - ATTR_UNIT_OF_MEASUREMENT: POWER_WATT, - ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER, - ATTR_DEFAULT_ENABLED: False, - }, - "power_average_daily": { - ATTR_NAME: "Average Daily Energy Usage", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "day_average", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_DEFAULT_ENABLED: False, - }, - "power_daily_cost": { - ATTR_NAME: "Energy Cost Today", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "day_cost", - ATTR_UNIT_OF_MEASUREMENT: CURRENCY_EUR, - ATTR_ICON: "mdi:power-plug", - }, - "power_daily_value": { - ATTR_NAME: "Energy Usage Today", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "day_usage", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - }, - "power_meter_reading": { - ATTR_NAME: "Electricity Meter Feed IN Tariff 1", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "meter_high", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, - ATTR_DEFAULT_ENABLED: False, - }, - "power_meter_reading_low": { - ATTR_NAME: "Electricity Meter Feed IN Tariff 2", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "meter_low", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, - ATTR_DEFAULT_ENABLED: False, - }, - "power_value": { - ATTR_NAME: "Current Power Usage", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "current", - ATTR_UNIT_OF_MEASUREMENT: POWER_WATT, - ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER, - ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, - }, - "solar_meter_reading_produced": { - ATTR_NAME: "Electricity Meter Feed OUT Tariff 1", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "meter_produced_high", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, - ATTR_DEFAULT_ENABLED: False, - }, - "solar_meter_reading_low_produced": { - ATTR_NAME: "Electricity Meter Feed OUT Tariff 2", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "meter_produced_low", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, - ATTR_DEFAULT_ENABLED: False, - }, - "solar_value": { - ATTR_NAME: "Current Solar Power Production", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "current_solar", - ATTR_UNIT_OF_MEASUREMENT: POWER_WATT, - ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER, - ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, - }, - "solar_maximum": { - ATTR_NAME: "Max Solar Power Production Today", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "day_max_solar", - ATTR_UNIT_OF_MEASUREMENT: POWER_WATT, - ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER, - }, - "solar_produced": { - ATTR_NAME: "Solar Power Production to Grid", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "current_produced", - ATTR_UNIT_OF_MEASUREMENT: POWER_WATT, - ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER, - ATTR_STATE_CLASS: ATTR_MEASUREMENT, - }, - "power_usage_day_produced_solar": { - ATTR_NAME: "Solar Energy Produced Today", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "day_produced_solar", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - }, - "power_usage_day_to_grid_usage": { - ATTR_NAME: "Energy Produced To Grid Today", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "day_to_grid_usage", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_DEFAULT_ENABLED: False, - }, - "power_usage_day_from_grid_usage": { - ATTR_NAME: "Energy Usage From Grid Today", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "day_from_grid_usage", - ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, - ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, - ATTR_DEFAULT_ENABLED: False, - }, - "solar_average_produced": { - ATTR_NAME: "Average Solar Power Production to Grid", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "average_produced", - ATTR_UNIT_OF_MEASUREMENT: POWER_WATT, - ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER, - ATTR_DEFAULT_ENABLED: False, - }, - "thermostat_info_current_modulation_level": { - ATTR_NAME: "Boiler Modulation Level", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "current_modulation_level", - ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE, - ATTR_ICON: "mdi:percent", - ATTR_DEFAULT_ENABLED: False, - ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, - }, - "power_usage_current_covered_by_solar": { - ATTR_NAME: "Current Power Usage Covered By Solar", - ATTR_SECTION: "power_usage", - ATTR_MEASUREMENT: "current_covered_by_solar", - ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE, - ATTR_ICON: "mdi:solar-power", - ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, - }, - "water_average": { - ATTR_NAME: "Average Water Usage", - ATTR_SECTION: "water_usage", - ATTR_MEASUREMENT: "average", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_LMIN, - ATTR_ICON: "mdi:water", - ATTR_DEFAULT_ENABLED: False, - }, - "water_average_daily": { - ATTR_NAME: "Average Daily Water Usage", - ATTR_SECTION: "water_usage", - ATTR_MEASUREMENT: "day_average", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, - ATTR_ICON: "mdi:water", - ATTR_DEFAULT_ENABLED: False, - }, - "water_daily_usage": { - ATTR_NAME: "Water Usage Today", - ATTR_SECTION: "water_usage", - ATTR_MEASUREMENT: "day_usage", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, - ATTR_ICON: "mdi:water", - ATTR_DEFAULT_ENABLED: False, - }, - "water_meter_reading": { - ATTR_NAME: "Water Meter", - ATTR_SECTION: "water_usage", - ATTR_MEASUREMENT: "meter", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_CUBIC_METERS, - ATTR_ICON: "mdi:water", - ATTR_DEFAULT_ENABLED: False, - ATTR_STATE_CLASS: STATE_CLASS_TOTAL_INCREASING, - }, - "water_value": { - ATTR_NAME: "Current Water Usage", - ATTR_SECTION: "water_usage", - ATTR_MEASUREMENT: "current", - ATTR_UNIT_OF_MEASUREMENT: VOLUME_LMIN, - ATTR_ICON: "mdi:water-pump", - ATTR_DEFAULT_ENABLED: False, - ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, - }, - "water_daily_cost": { - ATTR_NAME: "Water Cost Today", - ATTR_SECTION: "water_usage", - ATTR_MEASUREMENT: "day_cost", - ATTR_UNIT_OF_MEASUREMENT: CURRENCY_EUR, - ATTR_ICON: "mdi:water-pump", - ATTR_DEFAULT_ENABLED: False, - }, -} - -SWITCH_ENTITIES = { - "thermostat_holiday_mode": { - ATTR_NAME: "Holiday Mode", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "holiday_mode", - ATTR_ICON: "mdi:airport", - }, - "thermostat_program": { - ATTR_NAME: "Thermostat Program", - ATTR_SECTION: "thermostat", - ATTR_MEASUREMENT: "program", - ATTR_ICON: "mdi:calendar-clock", - }, -} diff --git a/homeassistant/components/toon/models.py b/homeassistant/components/toon/models.py index 7fb45af4d53..a95a8f622a8 100644 --- a/homeassistant/components/toon/models.py +++ b/homeassistant/components/toon/models.py @@ -1,6 +1,8 @@ """DataUpdate Coordinator, and base Entity and Device models for Toon.""" from __future__ import annotations +from dataclasses import dataclass + from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -115,3 +117,11 @@ class ToonBoilerDeviceEntity(ToonEntity): "identifiers": {(DOMAIN, agreement_id, "boiler")}, "via_device": (DOMAIN, agreement_id, "boiler_module"), } + + +@dataclass +class ToonRequiredKeysMixin: + """Mixin for required keys.""" + + section: str + measurement: str diff --git a/homeassistant/components/toon/sensor.py b/homeassistant/components/toon/sensor.py index 4522e34943c..cf7546c3fa6 100644 --- a/homeassistant/components/toon/sensor.py +++ b/homeassistant/components/toon/sensor.py @@ -1,21 +1,29 @@ """Support for Toon sensors.""" from __future__ import annotations -from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntity +from dataclasses import dataclass + +from homeassistant.components.sensor import ( + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + SensorEntity, + SensorEntityDescription, +) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + ENERGY_KILO_WATT_HOUR, + PERCENTAGE, + POWER_WATT, + TEMP_CELSIUS, + VOLUME_CUBIC_METERS, +) from homeassistant.core import HomeAssistant -from .const import ( - ATTR_DEFAULT_ENABLED, - ATTR_DEVICE_CLASS, - ATTR_ICON, - ATTR_MEASUREMENT, - ATTR_NAME, - ATTR_SECTION, - ATTR_UNIT_OF_MEASUREMENT, - DOMAIN, - SENSOR_ENTITIES, -) +from .const import CURRENCY_EUR, DOMAIN, VOLUME_CM3, VOLUME_LMIN from .coordinator import ToonDataUpdateCoordinator from .models import ( ToonBoilerDeviceEntity, @@ -23,6 +31,7 @@ from .models import ( ToonElectricityMeterDeviceEntity, ToonEntity, ToonGasMeterDeviceEntity, + ToonRequiredKeysMixin, ToonSolarDeviceEntity, ToonWaterMeterDeviceEntity, ) @@ -34,112 +43,54 @@ async def async_setup_entry( """Set up Toon sensors based on a config entry.""" coordinator = hass.data[DOMAIN][entry.entry_id] - sensors = [ - ToonElectricityMeterDeviceSensor(coordinator, key=key) - for key in ( - "power_average_daily", - "power_average", - "power_daily_cost", - "power_daily_value", - "power_meter_reading_low", - "power_meter_reading", - "power_value", - "solar_meter_reading_low_produced", - "solar_meter_reading_produced", - ) + entities = [ + description.cls(coordinator, description) for description in SENSOR_ENTITIES ] - sensors.extend( - [ToonDisplayDeviceSensor(coordinator, key="current_display_temperature")] - ) - - sensors.extend( - [ - ToonGasMeterDeviceSensor(coordinator, key=key) - for key in ( - "gas_average_daily", - "gas_average", - "gas_daily_cost", - "gas_daily_usage", - "gas_meter_reading", - "gas_value", - ) - ] - ) - - sensors.extend( - [ - ToonWaterMeterDeviceSensor(coordinator, key=key) - for key in ( - "water_average_daily", - "water_average", - "water_daily_cost", - "water_daily_usage", - "water_meter_reading", - "water_value", - ) - ] - ) - if coordinator.data.agreement.is_toon_solar: - sensors.extend( + entities.extend( [ - ToonSolarDeviceSensor(coordinator, key=key) - for key in ( - "solar_value", - "solar_maximum", - "solar_produced", - "solar_average_produced", - "power_usage_day_produced_solar", - "power_usage_day_from_grid_usage", - "power_usage_day_to_grid_usage", - "power_usage_current_covered_by_solar", - ) + description.cls(coordinator, description) + for description in SENSOR_ENTITIES_SOLAR ] ) if coordinator.data.thermostat.have_opentherm_boiler: - sensors.extend( + entities.extend( [ - ToonBoilerDeviceSensor( - coordinator, key="thermostat_info_current_modulation_level" - ) + description.cls(coordinator, description) + for description in SENSOR_ENTITIES_BOILER ] ) - async_add_entities(sensors, True) + async_add_entities(entities, True) class ToonSensor(ToonEntity, SensorEntity): """Defines a Toon sensor.""" - def __init__(self, coordinator: ToonDataUpdateCoordinator, *, key: str) -> None: + entity_description: ToonSensorEntityDescription + + def __init__( + self, + coordinator: ToonDataUpdateCoordinator, + description: ToonSensorEntityDescription, + ) -> None: """Initialize the Toon sensor.""" - self.key = key + self.entity_description = description super().__init__(coordinator) - sensor = SENSOR_ENTITIES[key] - self._attr_entity_registry_enabled_default = sensor.get( - ATTR_DEFAULT_ENABLED, True - ) - self._attr_icon = sensor.get(ATTR_ICON) - self._attr_name = sensor[ATTR_NAME] - self._attr_state_class = sensor.get(ATTR_STATE_CLASS) - self._attr_native_unit_of_measurement = sensor[ATTR_UNIT_OF_MEASUREMENT] - self._attr_device_class = sensor.get(ATTR_DEVICE_CLASS) self._attr_unique_id = ( # This unique ID is a bit ugly and contains unneeded information. # It is here for legacy / backward compatible reasons. - f"{DOMAIN}_{coordinator.data.agreement.agreement_id}_sensor_{key}" + f"{DOMAIN}_{coordinator.data.agreement.agreement_id}_sensor_{description.key}" ) @property def native_value(self) -> str | None: """Return the state of the sensor.""" - section = getattr( - self.coordinator.data, SENSOR_ENTITIES[self.key][ATTR_SECTION] - ) - return getattr(section, SENSOR_ENTITIES[self.key][ATTR_MEASUREMENT]) + section = getattr(self.coordinator.data, self.entity_description.section) + return getattr(section, self.entity_description.measurement) class ToonElectricityMeterDeviceSensor(ToonSensor, ToonElectricityMeterDeviceEntity): @@ -164,3 +115,336 @@ class ToonBoilerDeviceSensor(ToonSensor, ToonBoilerDeviceEntity): class ToonDisplayDeviceSensor(ToonSensor, ToonDisplayDeviceEntity): """Defines a Display sensor.""" + + +@dataclass +class ToonSensorRequiredKeysMixin(ToonRequiredKeysMixin): + """Mixin for sensor required keys.""" + + cls: type[ToonSensor] + + +@dataclass +class ToonSensorEntityDescription(SensorEntityDescription, ToonSensorRequiredKeysMixin): + """Describes Toon sensor entity.""" + + +SENSOR_ENTITIES: tuple[ToonSensorEntityDescription, ...] = ( + ToonSensorEntityDescription( + key="current_display_temperature", + name="Temperature", + section="thermostat", + measurement="current_display_temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + entity_registry_enabled_default=False, + state_class=STATE_CLASS_MEASUREMENT, + cls=ToonDisplayDeviceSensor, + ), + ToonSensorEntityDescription( + key="gas_average", + name="Average Gas Usage", + section="gas_usage", + measurement="average", + native_unit_of_measurement=VOLUME_CM3, + icon="mdi:gas-cylinder", + cls=ToonGasMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="gas_average_daily", + name="Average Daily Gas Usage", + section="gas_usage", + measurement="day_average", + device_class=DEVICE_CLASS_GAS, + native_unit_of_measurement=VOLUME_CUBIC_METERS, + entity_registry_enabled_default=False, + cls=ToonGasMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="gas_daily_usage", + name="Gas Usage Today", + section="gas_usage", + measurement="day_usage", + device_class=DEVICE_CLASS_GAS, + native_unit_of_measurement=VOLUME_CUBIC_METERS, + cls=ToonGasMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="gas_daily_cost", + name="Gas Cost Today", + section="gas_usage", + measurement="day_cost", + native_unit_of_measurement=CURRENCY_EUR, + icon="mdi:gas-cylinder", + cls=ToonGasMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="gas_meter_reading", + name="Gas Meter", + section="gas_usage", + measurement="meter", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=DEVICE_CLASS_GAS, + entity_registry_enabled_default=False, + cls=ToonGasMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="gas_value", + name="Current Gas Usage", + section="gas_usage", + measurement="current", + native_unit_of_measurement=VOLUME_CM3, + icon="mdi:gas-cylinder", + cls=ToonGasMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_average", + name="Average Power Usage", + section="power_usage", + measurement="average", + native_unit_of_measurement=POWER_WATT, + device_class=DEVICE_CLASS_POWER, + entity_registry_enabled_default=False, + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_average_daily", + name="Average Daily Energy Usage", + section="power_usage", + measurement="day_average", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + entity_registry_enabled_default=False, + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_daily_cost", + name="Energy Cost Today", + section="power_usage", + measurement="day_cost", + native_unit_of_measurement=CURRENCY_EUR, + icon="mdi:power-plug", + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_daily_value", + name="Energy Usage Today", + section="power_usage", + measurement="day_usage", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_meter_reading", + name="Electricity Meter Feed IN Tariff 1", + section="power_usage", + measurement="meter_high", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + entity_registry_enabled_default=False, + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_meter_reading_low", + name="Electricity Meter Feed IN Tariff 2", + section="power_usage", + measurement="meter_low", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + entity_registry_enabled_default=False, + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_value", + name="Current Power Usage", + section="power_usage", + measurement="current", + native_unit_of_measurement=POWER_WATT, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="solar_meter_reading_produced", + name="Electricity Meter Feed OUT Tariff 1", + section="power_usage", + measurement="meter_produced_high", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + entity_registry_enabled_default=False, + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="solar_meter_reading_low_produced", + name="Electricity Meter Feed OUT Tariff 2", + section="power_usage", + measurement="meter_produced_low", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + entity_registry_enabled_default=False, + cls=ToonElectricityMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="water_average", + name="Average Water Usage", + section="water_usage", + measurement="average", + native_unit_of_measurement=VOLUME_LMIN, + icon="mdi:water", + entity_registry_enabled_default=False, + cls=ToonWaterMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="water_average_daily", + name="Average Daily Water Usage", + section="water_usage", + measurement="day_average", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + icon="mdi:water", + entity_registry_enabled_default=False, + cls=ToonWaterMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="water_daily_usage", + name="Water Usage Today", + section="water_usage", + measurement="day_usage", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + icon="mdi:water", + entity_registry_enabled_default=False, + cls=ToonWaterMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="water_meter_reading", + name="Water Meter", + section="water_usage", + measurement="meter", + native_unit_of_measurement=VOLUME_CUBIC_METERS, + icon="mdi:water", + entity_registry_enabled_default=False, + state_class=STATE_CLASS_TOTAL_INCREASING, + cls=ToonWaterMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="water_value", + name="Current Water Usage", + section="water_usage", + measurement="current", + native_unit_of_measurement=VOLUME_LMIN, + icon="mdi:water-pump", + entity_registry_enabled_default=False, + state_class=STATE_CLASS_MEASUREMENT, + cls=ToonWaterMeterDeviceSensor, + ), + ToonSensorEntityDescription( + key="water_daily_cost", + name="Water Cost Today", + section="water_usage", + measurement="day_cost", + native_unit_of_measurement=CURRENCY_EUR, + icon="mdi:water-pump", + entity_registry_enabled_default=False, + cls=ToonWaterMeterDeviceSensor, + ), +) + +SENSOR_ENTITIES_SOLAR: tuple[ToonSensorEntityDescription, ...] = ( + ToonSensorEntityDescription( + key="solar_value", + name="Current Solar Power Production", + section="power_usage", + measurement="current_solar", + native_unit_of_measurement=POWER_WATT, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + cls=ToonSolarDeviceSensor, + ), + ToonSensorEntityDescription( + key="solar_maximum", + name="Max Solar Power Production Today", + section="power_usage", + measurement="day_max_solar", + native_unit_of_measurement=POWER_WATT, + device_class=DEVICE_CLASS_POWER, + cls=ToonSolarDeviceSensor, + ), + ToonSensorEntityDescription( + key="solar_produced", + name="Solar Power Production to Grid", + section="power_usage", + measurement="current_produced", + native_unit_of_measurement=POWER_WATT, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + cls=ToonSolarDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_usage_day_produced_solar", + name="Solar Energy Produced Today", + section="power_usage", + measurement="day_produced_solar", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + cls=ToonSolarDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_usage_day_to_grid_usage", + name="Energy Produced To Grid Today", + section="power_usage", + measurement="day_to_grid_usage", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + entity_registry_enabled_default=False, + cls=ToonSolarDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_usage_day_from_grid_usage", + name="Energy Usage From Grid Today", + section="power_usage", + measurement="day_from_grid_usage", + native_unit_of_measurement=ENERGY_KILO_WATT_HOUR, + device_class=DEVICE_CLASS_ENERGY, + entity_registry_enabled_default=False, + cls=ToonSolarDeviceSensor, + ), + ToonSensorEntityDescription( + key="solar_average_produced", + name="Average Solar Power Production to Grid", + section="power_usage", + measurement="average_produced", + native_unit_of_measurement=POWER_WATT, + device_class=DEVICE_CLASS_POWER, + entity_registry_enabled_default=False, + cls=ToonSolarDeviceSensor, + ), + ToonSensorEntityDescription( + key="power_usage_current_covered_by_solar", + name="Current Power Usage Covered By Solar", + section="power_usage", + measurement="current_covered_by_solar", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:solar-power", + state_class=STATE_CLASS_MEASUREMENT, + cls=ToonSolarDeviceSensor, + ), +) + +SENSOR_ENTITIES_BOILER: tuple[ToonSensorEntityDescription, ...] = ( + ToonSensorEntityDescription( + key="thermostat_info_current_modulation_level", + name="Boiler Modulation Level", + section="thermostat", + measurement="current_modulation_level", + native_unit_of_measurement=PERCENTAGE, + icon="mdi:percent", + entity_registry_enabled_default=False, + state_class=STATE_CLASS_MEASUREMENT, + cls=ToonBoilerDeviceSensor, + ), +) diff --git a/homeassistant/components/toon/switch.py b/homeassistant/components/toon/switch.py index 06ca9c6631b..de68b35befd 100644 --- a/homeassistant/components/toon/switch.py +++ b/homeassistant/components/toon/switch.py @@ -1,4 +1,7 @@ """Support for Toon switches.""" +from __future__ import annotations + +from dataclasses import dataclass from typing import Any from toonapi import ( @@ -8,21 +11,14 @@ from toonapi import ( PROGRAM_STATE_ON, ) -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import ( - ATTR_ICON, - ATTR_MEASUREMENT, - ATTR_NAME, - ATTR_SECTION, - DOMAIN, - SWITCH_ENTITIES, -) +from .const import DOMAIN from .coordinator import ToonDataUpdateCoordinator from .helpers import toon_exception_handler -from .models import ToonDisplayDeviceEntity, ToonEntity +from .models import ToonDisplayDeviceEntity, ToonEntity, ToonRequiredKeysMixin async def async_setup_entry( @@ -32,39 +28,38 @@ async def async_setup_entry( coordinator = hass.data[DOMAIN][entry.entry_id] async_add_entities( - [ToonProgramSwitch(coordinator), ToonHolidayModeSwitch(coordinator)] + [description.cls(coordinator, description) for description in SWITCH_ENTITIES] ) class ToonSwitch(ToonEntity, SwitchEntity): """Defines an Toon switch.""" - def __init__(self, coordinator: ToonDataUpdateCoordinator, *, key: str) -> None: + entity_description: ToonSwitchEntityDescription + + def __init__( + self, + coordinator: ToonDataUpdateCoordinator, + description: ToonSwitchEntityDescription, + ) -> None: """Initialize the Toon switch.""" - self.key = key + self.entity_description = description super().__init__(coordinator) - switch = SWITCH_ENTITIES[key] - self._attr_icon = switch[ATTR_ICON] - self._attr_name = switch[ATTR_NAME] - self._attr_unique_id = f"{coordinator.data.agreement.agreement_id}_{key}" + self._attr_unique_id = ( + f"{coordinator.data.agreement.agreement_id}_{description.key}" + ) @property def is_on(self) -> bool: """Return the status of the binary sensor.""" - section = getattr( - self.coordinator.data, SWITCH_ENTITIES[self.key][ATTR_SECTION] - ) - return getattr(section, SWITCH_ENTITIES[self.key][ATTR_MEASUREMENT]) + section = getattr(self.coordinator.data, self.entity_description.section) + return getattr(section, self.entity_description.measurement) class ToonProgramSwitch(ToonSwitch, ToonDisplayDeviceEntity): """Defines a Toon program switch.""" - def __init__(self, coordinator: ToonDataUpdateCoordinator) -> None: - """Initialize the Toon program switch.""" - super().__init__(coordinator, key="thermostat_program") - @toon_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the Toon program switch.""" @@ -83,10 +78,6 @@ class ToonProgramSwitch(ToonSwitch, ToonDisplayDeviceEntity): class ToonHolidayModeSwitch(ToonSwitch, ToonDisplayDeviceEntity): """Defines a Toon Holiday mode switch.""" - def __init__(self, coordinator: ToonDataUpdateCoordinator) -> None: - """Initialize the Toon holiday switch.""" - super().__init__(coordinator, key="thermostat_holiday_mode") - @toon_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the Toon holiday mode switch.""" @@ -100,3 +91,35 @@ class ToonHolidayModeSwitch(ToonSwitch, ToonDisplayDeviceEntity): await self.coordinator.toon.set_active_state( ACTIVE_STATE_HOLIDAY, PROGRAM_STATE_OFF ) + + +@dataclass +class ToonSwitchRequiredKeysMixin(ToonRequiredKeysMixin): + """Mixin for switch required keys.""" + + cls: type[ToonSwitch] + + +@dataclass +class ToonSwitchEntityDescription(SwitchEntityDescription, ToonSwitchRequiredKeysMixin): + """Describes Toon switch entity.""" + + +SWITCH_ENTITIES: tuple[ToonSwitchEntityDescription, ...] = ( + ToonSwitchEntityDescription( + key="thermostat_holiday_mode", + name="Holiday Mode", + section="thermostat", + measurement="holiday_mode", + icon="mdi:airport", + cls=ToonHolidayModeSwitch, + ), + ToonSwitchEntityDescription( + key="thermostat_program", + name="Thermostat Program", + section="thermostat", + measurement="program", + icon="mdi:calendar-clock", + cls=ToonProgramSwitch, + ), +)