diff --git a/homeassistant/components/overkiz/climate_entities/__init__.py b/homeassistant/components/overkiz/climate_entities/__init__.py index 737ea342c40..32fae234be1 100644 --- a/homeassistant/components/overkiz/climate_entities/__init__.py +++ b/homeassistant/components/overkiz/climate_entities/__init__.py @@ -3,12 +3,14 @@ from pyoverkiz.enums.ui import UIWidget from .atlantic_electrical_heater import AtlanticElectricalHeater from .atlantic_electrical_towel_dryer import AtlanticElectricalTowelDryer +from .atlantic_heat_recovery_ventilation import AtlanticHeatRecoveryVentilation from .atlantic_pass_apc_zone_control import AtlanticPassAPCZoneControl from .somfy_thermostat import SomfyThermostat WIDGET_TO_CLIMATE_ENTITY = { UIWidget.ATLANTIC_ELECTRICAL_HEATER: AtlanticElectricalHeater, UIWidget.ATLANTIC_ELECTRICAL_TOWEL_DRYER: AtlanticElectricalTowelDryer, + UIWidget.ATLANTIC_HEAT_RECOVERY_VENTILATION: AtlanticHeatRecoveryVentilation, UIWidget.ATLANTIC_PASS_APC_ZONE_CONTROL: AtlanticPassAPCZoneControl, UIWidget.SOMFY_THERMOSTAT: SomfyThermostat, } diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py b/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py new file mode 100644 index 00000000000..f28db995350 --- /dev/null +++ b/homeassistant/components/overkiz/climate_entities/atlantic_heat_recovery_ventilation.py @@ -0,0 +1,176 @@ +"""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, + ClimateEntityFeature, + HVACMode, +) +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()} + +TEMPERATURE_SENSOR_DEVICE_INDEX = 4 + + +class AtlanticHeatRecoveryVentilation(OverkizEntity, ClimateEntity): + """Representation of a AtlanticHeatRecoveryVentilation device.""" + + _attr_fan_modes = [*FAN_MODES_TO_OVERKIZ] + _attr_hvac_mode = HVACMode.FAN_ONLY + _attr_hvac_modes = [HVACMode.FAN_ONLY] + _attr_preset_modes = [PRESET_AUTO, PRESET_PROG, PRESET_MANUAL] + _attr_temperature_unit = TEMP_CELSIUS + _attr_supported_features = ( + ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.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( + TEMPERATURE_SENSOR_DEVICE_INDEX + ) + + @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 + + 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.""" + ventilation_configuration = self.executor.select_state( + OverkizState.IO_VENTILATION_CONFIGURATION_MODE + ) + + if ventilation_configuration == OverkizCommandParam.COMFORT: + return PRESET_AUTO + + if ventilation_configuration == OverkizCommandParam.STANDARD: + return PRESET_MANUAL + + 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 + + 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/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index 9091cd35998..d98709ba2b6 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -63,6 +63,7 @@ OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform | None] = { UIWidget.ALARM_PANEL_CONTROLLER: Platform.ALARM_CONTROL_PANEL, # widgetName, uiClass is Alarm (not supported) UIWidget.ATLANTIC_ELECTRICAL_HEATER: 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_ZONE_CONTROL: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) UIWidget.DOMESTIC_HOT_WATER_TANK: Platform.SWITCH, # widgetName, uiClass is WaterHeatingSystem (not supported) UIWidget.MY_FOX_ALARM_CONTROLLER: Platform.ALARM_CONTROL_PANEL, # widgetName, uiClass is Alarm (not supported)