diff --git a/custom_components/tahoma/climate.py b/custom_components/tahoma/climate.py index 47c98f44..07291dc2 100644 --- a/custom_components/tahoma/climate.py +++ b/custom_components/tahoma/climate.py @@ -14,6 +14,9 @@ from .climate_devices.atlantic_electrical_towel_dryer import ( AtlanticElectricalTowelDryer, ) +from .climate_devices.atlantic_heat_recovery_ventilation import ( + AtlanticHeatRecoveryVentilation, +) from .climate_devices.atlantic_pass_apc_heating_and_cooling_zone import ( AtlanticPassAPCHeatingAndCoolingZone, ) @@ -36,6 +39,7 @@ UIWidget.ATLANTIC_ELECTRICAL_HEATER: AtlanticElectricalHeater, UIWidget.ATLANTIC_ELECTRICAL_HEATER_WITH_ADJUSTABLE_TEMPERATURE_SETPOINT: AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint, UIWidget.ATLANTIC_ELECTRICAL_TOWEL_DRYER: AtlanticElectricalTowelDryer, + UIWidget.ATLANTIC_HEAT_RECOVERY_VENTILATION: AtlanticHeatRecoveryVentilation, UIWidget.ATLANTIC_PASS_APC_DHW: AtlanticPassAPCDHW, UIWidget.ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE: AtlanticPassAPCHeatingAndCoolingZone, UIWidget.ATLANTIC_PASS_APC_ZONE_CONTROL: AtlanticPassAPCZoneControl, diff --git a/custom_components/tahoma/climate_devices/atlantic_heat_recovery_ventilation.py b/custom_components/tahoma/climate_devices/atlantic_heat_recovery_ventilation.py new file mode 100644 index 00000000..eb25b3f9 --- /dev/null +++ b/custom_components/tahoma/climate_devices/atlantic_heat_recovery_ventilation.py @@ -0,0 +1,174 @@ +"""Support for AtlanticHeatRecoveryVentilation.""" +from __future__ import annotations + +from typing import cast + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState + +from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate.const import ( + FAN_AUTO, + HVAC_MODE_FAN_ONLY, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, +) +from homeassistant.const import TEMP_CELSIUS + +from ..coordinator import OverkizDataUpdateCoordinator +from ..entity import OverkizEntity + +FAN_BOOST = "home_boost" +FAN_KITCHEN = "kitchen_boost" +FAN_AWAY = "away" +FAN_BYPASS = "bypass_boost" + +PRESET_AUTO = "auto" +PRESET_PROG = "prog" +PRESET_MANUAL = "manual" + +OVERKIZ_TO_FAN_MODES: dict[str, str] = { + OverkizCommandParam.AUTO: FAN_AUTO, + OverkizCommandParam.AWAY: FAN_AWAY, + OverkizCommandParam.BOOST: FAN_BOOST, + OverkizCommandParam.HIGH: FAN_KITCHEN, + "": FAN_BYPASS, +} + +FAN_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_FAN_MODES.items()} + + +class AtlanticHeatRecoveryVentilation(OverkizEntity, ClimateEntity): + """Representation of a AtlanticHeatRecoveryVentilation device.""" + + _attr_fan_modes = [*FAN_MODES_TO_OVERKIZ] + _attr_hvac_modes = [HVAC_MODE_FAN_ONLY] + _attr_preset_modes = [PRESET_AUTO, PRESET_PROG, PRESET_MANUAL] + _attr_temperature_unit = TEMP_CELSIUS + _attr_supported_features = SUPPORT_PRESET_MODE | SUPPORT_FAN_MODE + + def __init__( + self, device_url: str, coordinator: OverkizDataUpdateCoordinator + ) -> None: + """Init method.""" + super().__init__(device_url, coordinator) + self.temperature_device = self.executor.linked_device(4) + + @property + def current_temperature(self) -> float | None: + """Return the current temperature.""" + if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + return cast(float, temperature.value) + + return None + + @property + def hvac_mode(self) -> str: + """Return hvac operation ie. heat, cool mode.""" + return HVAC_MODE_FAN_ONLY + + async def async_set_hvac_mode(self, hvac_mode: str) -> None: + """Not implemented since there is only one hvac_mode.""" + + @property + def preset_mode(self) -> str | None: + """Return the current preset mode, e.g., auto, smart, interval, favorite.""" + ventilation_configuration = self.executor.select_state( + OverkizState.IO_VENTILATION_CONFIGURATION_MODE + ) + ventilation_mode = cast( + dict, self.executor.select_state(OverkizState.IO_VENTILATION_MODE) + ) + prog = ventilation_mode.get(OverkizCommandParam.PROG) + + if prog == OverkizCommandParam.ON: + return PRESET_PROG + + if ventilation_configuration == OverkizCommandParam.COMFORT: + return PRESET_AUTO + + if ventilation_configuration == OverkizCommandParam.STANDARD: + return PRESET_MANUAL + + return None + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set the preset mode of the fan.""" + if preset_mode == PRESET_AUTO: + await self.executor.async_execute_command( + OverkizCommand.SET_VENTILATION_CONFIGURATION_MODE, + OverkizCommandParam.COMFORT, + ) + await self._set_ventilation_mode(prog=OverkizCommandParam.OFF) + + if preset_mode == PRESET_PROG: + await self.executor.async_execute_command( + OverkizCommand.SET_VENTILATION_CONFIGURATION_MODE, + OverkizCommandParam.STANDARD, + ) + await self._set_ventilation_mode(prog=OverkizCommandParam.ON) + + if preset_mode == PRESET_MANUAL: + await self.executor.async_execute_command( + OverkizCommand.SET_VENTILATION_CONFIGURATION_MODE, + OverkizCommandParam.STANDARD, + ) + await self._set_ventilation_mode(prog=OverkizCommandParam.OFF) + + await self.executor.async_execute_command( + OverkizCommand.REFRESH_VENTILATION_STATE, + ) + await self.executor.async_execute_command( + OverkizCommand.REFRESH_VENTILATION_CONFIGURATION_MODE, + ) + + @property + def fan_mode(self) -> str | None: + """Return the fan setting.""" + ventilation_mode = cast( + dict, self.executor.select_state(OverkizState.IO_VENTILATION_MODE) + ) + cooling = ventilation_mode.get(OverkizCommandParam.COOLING) + + if cooling == OverkizCommandParam.ON: + return FAN_BYPASS + + return OVERKIZ_TO_FAN_MODES[ + cast(str, self.executor.select_state(OverkizState.IO_AIR_DEMAND_MODE)) + ] + + async def async_set_fan_mode(self, fan_mode: str) -> None: + """Set new target fan mode.""" + if fan_mode == FAN_BYPASS: + await self.executor.async_execute_command( + OverkizCommand.SET_AIR_DEMAND_MODE, OverkizCommandParam.AUTO + ) + await self._set_ventilation_mode(cooling=OverkizCommandParam.ON) + else: + await self._set_ventilation_mode(cooling=OverkizCommandParam.OFF) + await self.executor.async_execute_command( + OverkizCommand.SET_AIR_DEMAND_MODE, FAN_MODES_TO_OVERKIZ[fan_mode] + ) + + await self.executor.async_execute_command( + OverkizCommand.REFRESH_VENTILATION_STATE, + ) + + async def _set_ventilation_mode( + self, + cooling: str | None = None, + prog: str | None = None, + ) -> None: + """Execute ventilation mode command with all parameters.""" + ventilation_mode = cast( + dict, self.executor.select_state(OverkizState.IO_VENTILATION_MODE) + ) + + if cooling: + ventilation_mode[OverkizCommandParam.COOLING] = cooling + + if prog: + ventilation_mode[OverkizCommandParam.PROG] = prog + + await self.executor.async_execute_command( + OverkizCommand.SET_VENTILATION_MODE, ventilation_mode + ) diff --git a/custom_components/tahoma/const.py b/custom_components/tahoma/const.py index 348ef360..a22e3e57 100644 --- a/custom_components/tahoma/const.py +++ b/custom_components/tahoma/const.py @@ -62,6 +62,7 @@ UIWidget.ATLANTIC_ELECTRICAL_HEATER: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) UIWidget.ATLANTIC_ELECTRICAL_HEATER_WITH_ADJUSTABLE_TEMPERATURE_SETPOINT: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) UIWidget.ATLANTIC_ELECTRICAL_TOWEL_DRYER: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) + UIWidget.ATLANTIC_HEAT_RECOVERY_VENTILATION: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) UIWidget.ATLANTIC_PASS_APC_DHW: Platform.CLIMATE, # widgetName, uiClass is WaterHeatingSystem (not supported) UIWidget.ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) UIWidget.ATLANTIC_PASS_APC_ZONE_CONTROL: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) diff --git a/custom_components/tahoma/sensor.py b/custom_components/tahoma/sensor.py index 7c01f01f..90cf378b 100644 --- a/custom_components/tahoma/sensor.py +++ b/custom_components/tahoma/sensor.py @@ -104,7 +104,7 @@ class OverkizSensorDescription(SensorEntityDescription): key=OverkizState.IO_OUTLET_ENGINE, name="Outlet Engine", icon="mdi:fan-chevron-down", - native_unit_of_measurement=VOLUME_LITERS, + native_unit_of_measurement=VOLUME_FLOW_RATE_CUBIC_METERS_PER_HOUR, state_class=SensorStateClass.MEASUREMENT, ), OverkizSensorDescription(