diff --git a/homeassistant/components/overkiz/climate_entities/__init__.py b/homeassistant/components/overkiz/climate_entities/__init__.py index 32fae234be128a..359f7629a7b11d 100644 --- a/homeassistant/components/overkiz/climate_entities/__init__.py +++ b/homeassistant/components/overkiz/climate_entities/__init__.py @@ -4,6 +4,9 @@ from .atlantic_electrical_heater import AtlanticElectricalHeater from .atlantic_electrical_towel_dryer import AtlanticElectricalTowelDryer from .atlantic_heat_recovery_ventilation import AtlanticHeatRecoveryVentilation +from .atlantic_pass_apc_heating_and_cooling_zone import ( + AtlanticPassAPCHeatingAndCoolingZone, +) from .atlantic_pass_apc_zone_control import AtlanticPassAPCZoneControl from .somfy_thermostat import SomfyThermostat @@ -11,6 +14,7 @@ UIWidget.ATLANTIC_ELECTRICAL_HEATER: AtlanticElectricalHeater, UIWidget.ATLANTIC_ELECTRICAL_TOWEL_DRYER: AtlanticElectricalTowelDryer, UIWidget.ATLANTIC_HEAT_RECOVERY_VENTILATION: AtlanticHeatRecoveryVentilation, + UIWidget.ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE: AtlanticPassAPCHeatingAndCoolingZone, UIWidget.ATLANTIC_PASS_APC_ZONE_CONTROL: AtlanticPassAPCZoneControl, UIWidget.SOMFY_THERMOSTAT: SomfyThermostat, } diff --git a/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_and_cooling_zone.py b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_and_cooling_zone.py new file mode 100644 index 00000000000000..efdbf94d9f75fe --- /dev/null +++ b/homeassistant/components/overkiz/climate_entities/atlantic_pass_apc_heating_and_cooling_zone.py @@ -0,0 +1,203 @@ +"""Support for Atlantic Pass APC Heating And Cooling Zone Control.""" +from __future__ import annotations + +from typing import Any, cast + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState + +from homeassistant.components.climate import ( + PRESET_AWAY, + PRESET_COMFORT, + PRESET_ECO, + PRESET_HOME, + PRESET_SLEEP, + ClimateEntity, + ClimateEntityFeature, + HVACMode, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS + +from ..coordinator import OverkizDataUpdateCoordinator +from ..entity import OverkizEntity + +OVERKIZ_TO_HVAC_MODE: dict[str, str] = { + OverkizCommandParam.AUTO: HVACMode.AUTO, + OverkizCommandParam.ECO: HVACMode.AUTO, + OverkizCommandParam.MANU: HVACMode.HEAT, + OverkizCommandParam.HEATING: HVACMode.HEAT, + OverkizCommandParam.STOP: HVACMode.OFF, + OverkizCommandParam.INTERNAL_SCHEDULING: HVACMode.AUTO, + OverkizCommandParam.COMFORT: HVACMode.HEAT, +} + +HVAC_MODE_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_HVAC_MODE.items()} + +OVERKIZ_TO_PRESET_MODES: dict[str, str] = { + OverkizCommandParam.OFF: PRESET_ECO, + OverkizCommandParam.STOP: PRESET_ECO, + OverkizCommandParam.MANU: PRESET_COMFORT, + OverkizCommandParam.COMFORT: PRESET_COMFORT, + OverkizCommandParam.ABSENCE: PRESET_AWAY, + OverkizCommandParam.ECO: PRESET_ECO, + OverkizCommandParam.INTERNAL_SCHEDULING: PRESET_HOME, +} + +PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_PRESET_MODES.items()} + +OVERKIZ_TO_PROFILE_MODES: dict[str, str] = { + OverkizCommandParam.OFF: PRESET_SLEEP, + OverkizCommandParam.STOP: PRESET_SLEEP, + OverkizCommandParam.ECO: PRESET_ECO, + OverkizCommandParam.ABSENCE: PRESET_AWAY, + OverkizCommandParam.MANU: PRESET_COMFORT, + OverkizCommandParam.DEROGATION: PRESET_COMFORT, + OverkizCommandParam.COMFORT: PRESET_COMFORT, +} + +OVERKIZ_TEMPERATURE_STATE_BY_PROFILE: dict[str, str] = { + OverkizCommandParam.ECO: OverkizState.CORE_ECO_HEATING_TARGET_TEMPERATURE, + OverkizCommandParam.COMFORT: OverkizState.CORE_COMFORT_HEATING_TARGET_TEMPERATURE, + OverkizCommandParam.DEROGATION: OverkizState.CORE_DEROGATED_TARGET_TEMPERATURE, +} + + +class AtlanticPassAPCHeatingAndCoolingZone(OverkizEntity, ClimateEntity): + """Representation of Atlantic Pass APC Heating And Cooling Zone Control.""" + + _attr_hvac_modes = [*HVAC_MODE_TO_OVERKIZ] + _attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ] + _attr_supported_features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + _attr_temperature_unit = TEMP_CELSIUS + + def __init__( + self, device_url: str, coordinator: OverkizDataUpdateCoordinator + ) -> None: + """Init method.""" + super().__init__(device_url, coordinator) + + # Temperature sensor use the same base_device_url and use the n+1 index + self.temperature_device = self.executor.linked_device( + int(self.index_device_url) + 1 + ) + + @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 OVERKIZ_TO_HVAC_MODE[ + cast(str, self.executor.select_state(OverkizState.IO_PASS_APC_HEATING_MODE)) + ] + + @property + def current_heating_profile(self) -> str: + """Return current heating profile.""" + return cast( + str, + self.executor.select_state(OverkizState.IO_PASS_APC_HEATING_PROFILE), + ) + + async def async_set_heating_mode(self, mode: str) -> None: + """Set new heating mode and refresh states.""" + await self.executor.async_execute_command( + OverkizCommand.SET_PASS_APC_HEATING_MODE, mode + ) + + if self.current_heating_profile == OverkizCommandParam.DEROGATION: + # If current mode is in derogation, disable it + await self.executor.async_execute_command( + OverkizCommand.SET_DEROGATION_ON_OFF_STATE, OverkizCommandParam.OFF + ) + + # We also needs to execute these 2 commands to make it work correctly + await self.executor.async_execute_command( + OverkizCommand.REFRESH_PASS_APC_HEATING_MODE + ) + await self.executor.async_execute_command( + OverkizCommand.REFRESH_PASS_APC_HEATING_PROFILE + ) + + async def async_set_hvac_mode(self, hvac_mode: str) -> None: + """Set new target hvac mode.""" + await self.async_set_heating_mode(HVAC_MODE_TO_OVERKIZ[hvac_mode]) + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode.""" + await self.async_set_heating_mode(PRESET_MODES_TO_OVERKIZ[preset_mode]) + + @property + def preset_mode(self) -> str: + """Return the current preset mode, e.g., home, away, temp.""" + heating_mode = cast( + str, self.executor.select_state(OverkizState.IO_PASS_APC_HEATING_MODE) + ) + + if heating_mode == OverkizCommandParam.INTERNAL_SCHEDULING: + # In Internal scheduling, it could be comfort or eco + return OVERKIZ_TO_PROFILE_MODES[ + cast( + str, + self.executor.select_state( + OverkizState.IO_PASS_APC_HEATING_PROFILE + ), + ) + ] + + return OVERKIZ_TO_PRESET_MODES[heating_mode] + + @property + def target_temperature(self) -> float: + """Return hvac target temperature.""" + current_heating_profile = self.current_heating_profile + if current_heating_profile in OVERKIZ_TEMPERATURE_STATE_BY_PROFILE: + return cast( + float, + self.executor.select_state( + OVERKIZ_TEMPERATURE_STATE_BY_PROFILE[current_heating_profile] + ), + ) + return cast( + float, self.executor.select_state(OverkizState.CORE_TARGET_TEMPERATURE) + ) + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new temperature.""" + temperature = kwargs[ATTR_TEMPERATURE] + + if self.hvac_mode == HVACMode.AUTO: + await self.executor.async_execute_command( + OverkizCommand.SET_COMFORT_HEATING_TARGET_TEMPERATURE, + temperature, + ) + await self.executor.async_execute_command( + OverkizCommand.REFRESH_COMFORT_HEATING_TARGET_TEMPERATURE + ) + await self.executor.async_execute_command( + OverkizCommand.REFRESH_TARGET_TEMPERATURE + ) + else: + await self.executor.async_execute_command( + OverkizCommand.SET_DEROGATED_TARGET_TEMPERATURE, + temperature, + ) + await self.executor.async_execute_command( + OverkizCommand.SET_DEROGATION_ON_OFF_STATE, + OverkizCommandParam.ON, + ) + await self.executor.async_execute_command( + OverkizCommand.REFRESH_TARGET_TEMPERATURE + ) + await self.executor.async_execute_command( + OverkizCommand.REFRESH_PASS_APC_HEATING_MODE + ) + await self.executor.async_execute_command( + OverkizCommand.REFRESH_PASS_APC_HEATING_PROFILE + ) diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index d98709ba2b6b79..70477bbcdb2d8d 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -64,6 +64,7 @@ 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_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) 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) diff --git a/homeassistant/components/overkiz/entity.py b/homeassistant/components/overkiz/entity.py index c17f30393fc9be..85e5a3fdf571fe 100644 --- a/homeassistant/components/overkiz/entity.py +++ b/homeassistant/components/overkiz/entity.py @@ -27,7 +27,10 @@ def __init__( """Initialize the device.""" super().__init__(coordinator) self.device_url = device_url - self.base_device_url, *_ = self.device_url.split("#") + split_device_url = self.device_url.split("#") + self.base_device_url = split_device_url[0] + if len(split_device_url) == 2: + self.index_device_url = split_device_url[1] self.executor = OverkizExecutor(device_url, coordinator) self._attr_assumed_state = not self.device.states