From b233d248ff69332857f3bfce912dd1aae1cb5157 Mon Sep 17 00:00:00 2001 From: Etienne G Date: Tue, 7 Nov 2023 22:48:41 +1100 Subject: [PATCH] Add support for SomfyHeatingTemperatureInterface in Overkiz integration (#83514) --- .../overkiz/climate_entities/__init__.py | 2 + .../somfy_heating_temperature_interface.py | 179 ++++++++++++++++++ homeassistant/components/overkiz/const.py | 1 + 3 files changed, 182 insertions(+) create mode 100644 homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py diff --git a/homeassistant/components/overkiz/climate_entities/__init__.py b/homeassistant/components/overkiz/climate_entities/__init__.py index 9d54c04422a..b6345dd9b95 100644 --- a/homeassistant/components/overkiz/climate_entities/__init__.py +++ b/homeassistant/components/overkiz/climate_entities/__init__.py @@ -9,6 +9,7 @@ from .atlantic_electrical_towel_dryer import AtlanticElectricalTowelDryer from .atlantic_heat_recovery_ventilation import AtlanticHeatRecoveryVentilation from .atlantic_pass_apc_heating_zone import AtlanticPassAPCHeatingZone from .atlantic_pass_apc_zone_control import AtlanticPassAPCZoneControl +from .somfy_heating_temperature_interface import SomfyHeatingTemperatureInterface from .somfy_thermostat import SomfyThermostat from .valve_heating_temperature_interface import ValveHeatingTemperatureInterface @@ -21,6 +22,7 @@ WIDGET_TO_CLIMATE_ENTITY = { UIWidget.ATLANTIC_PASS_APC_HEATING_AND_COOLING_ZONE: AtlanticPassAPCHeatingZone, UIWidget.ATLANTIC_PASS_APC_HEATING_ZONE: AtlanticPassAPCHeatingZone, UIWidget.ATLANTIC_PASS_APC_ZONE_CONTROL: AtlanticPassAPCZoneControl, + UIWidget.SOMFY_HEATING_TEMPERATURE_INTERFACE: SomfyHeatingTemperatureInterface, UIWidget.SOMFY_THERMOSTAT: SomfyThermostat, UIWidget.VALVE_HEATING_TEMPERATURE_INTERFACE: ValveHeatingTemperatureInterface, } diff --git a/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py b/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py new file mode 100644 index 00000000000..6c3ee3454ce --- /dev/null +++ b/homeassistant/components/overkiz/climate_entities/somfy_heating_temperature_interface.py @@ -0,0 +1,179 @@ +"""Support for Somfy Heating Temperature Interface.""" +from __future__ import annotations + +from typing import Any + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState + +from homeassistant.components.climate import ( + PRESET_AWAY, + PRESET_COMFORT, + PRESET_ECO, + PRESET_NONE, + ClimateEntity, + ClimateEntityFeature, + HVACAction, + HVACMode, +) +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature + +from ..coordinator import OverkizDataUpdateCoordinator +from ..entity import OverkizEntity + +OVERKIZ_TO_PRESET_MODES: dict[str, str] = { + OverkizCommandParam.SECURED: PRESET_AWAY, + OverkizCommandParam.ECO: PRESET_ECO, + OverkizCommandParam.COMFORT: PRESET_COMFORT, + OverkizCommandParam.FREE: PRESET_NONE, +} + +PRESET_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_PRESET_MODES.items()} + +OVERKIZ_TO_HVAC_MODES: dict[str, HVACMode] = { + OverkizCommandParam.AUTO: HVACMode.AUTO, + OverkizCommandParam.MANU: HVACMode.HEAT_COOL, +} + +HVAC_MODES_TO_OVERKIZ = {v: k for k, v in OVERKIZ_TO_HVAC_MODES.items()} + +OVERKIZ_TO_HVAC_ACTION: dict[str, HVACAction] = { + OverkizCommandParam.COOLING: HVACAction.COOLING, + OverkizCommandParam.HEATING: HVACAction.HEATING, +} + +MAP_PRESET_TEMPERATURES: dict[str, str] = { + PRESET_COMFORT: OverkizState.CORE_COMFORT_ROOM_TEMPERATURE, + PRESET_ECO: OverkizState.CORE_ECO_ROOM_TEMPERATURE, + PRESET_AWAY: OverkizState.CORE_SECURED_POSITION_TEMPERATURE, +} + +SETPOINT_MODE_TO_OVERKIZ_COMMAND: dict[str, str] = { + OverkizCommandParam.COMFORT: OverkizCommand.SET_COMFORT_TEMPERATURE, + OverkizCommandParam.ECO: OverkizCommand.SET_ECO_TEMPERATURE, + OverkizCommandParam.SECURED: OverkizCommand.SET_SECURED_POSITION_TEMPERATURE, +} + +TEMPERATURE_SENSOR_DEVICE_INDEX = 2 + + +class SomfyHeatingTemperatureInterface(OverkizEntity, ClimateEntity): + """Representation of Somfy Heating Temperature Interface. + + The thermostat has 3 ways of working: + - Auto: Switch to eco/comfort temperature on a schedule (day/hour of the day) + - Manual comfort: The thermostat use the temperature of the comfort setting (19°C degree by default) + - Manual eco: The thermostat use the temperature of the eco setting (17°C by default) + - Freeze protection: The thermostat use the temperature of the freeze protection (7°C by default) + + There's also the possibility to change the working mode, this can be used to change from a heated + floor to a cooling floor in the summer. + """ + + _attr_temperature_unit = UnitOfTemperature.CELSIUS + _attr_supported_features = ( + ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE + ) + _attr_hvac_modes = [*HVAC_MODES_TO_OVERKIZ] + _attr_preset_modes = [*PRESET_MODES_TO_OVERKIZ] + # Both min and max temp values have been retrieved from the Somfy Application. + _attr_min_temp = 15.0 + _attr_max_temp = 26.0 + + 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 hvac_mode(self) -> HVACMode: + """Return hvac operation i.e. heat, cool mode.""" + state = self.device.states[OverkizState.CORE_ON_OFF] + if state and state.value_as_str == OverkizCommandParam.OFF: + return HVACMode.OFF + + if ( + state := self.device.states[ + OverkizState.OVP_HEATING_TEMPERATURE_INTERFACE_ACTIVE_MODE + ] + ) and state.value_as_str: + return OVERKIZ_TO_HVAC_MODES[state.value_as_str] + + return HVACMode.OFF + + async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Set new target hvac mode.""" + await self.executor.async_execute_command( + OverkizCommand.SET_ACTIVE_MODE, HVAC_MODES_TO_OVERKIZ[hvac_mode] + ) + + @property + def preset_mode(self) -> str | None: + """Return the current preset mode, e.g., home, away, temp.""" + if ( + state := self.device.states[ + OverkizState.OVP_HEATING_TEMPERATURE_INTERFACE_SETPOINT_MODE + ] + ) and state.value_as_str: + return OVERKIZ_TO_PRESET_MODES[state.value_as_str] + return None + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode.""" + await self.executor.async_execute_command( + OverkizCommand.SET_MANU_AND_SET_POINT_MODES, + PRESET_MODES_TO_OVERKIZ[preset_mode], + ) + + @property + def hvac_action(self) -> HVACAction | None: + """Return the current running hvac operation if supported.""" + if ( + current_operation := self.device.states[ + OverkizState.OVP_HEATING_TEMPERATURE_INTERFACE_OPERATING_MODE + ] + ) and current_operation.value_as_str: + return OVERKIZ_TO_HVAC_ACTION[current_operation.value_as_str] + + return None + + @property + def target_temperature(self) -> float | None: + """Return the target temperature.""" + + # Allow to get the current target temperature for the current preset + # The preset can be switched manually or on a schedule (auto). + # This allows to reflect the current target temperature automatically + if not self.preset_mode: + return None + + mode = PRESET_MODES_TO_OVERKIZ[self.preset_mode] + if mode not in MAP_PRESET_TEMPERATURES: + return None + + if state := self.device.states[MAP_PRESET_TEMPERATURES[mode]]: + return state.value_as_float + return None + + @property + def current_temperature(self) -> float | None: + """Return the current temperature.""" + if temperature := self.temperature_device.states[OverkizState.CORE_TEMPERATURE]: + return temperature.value_as_float + return None + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new temperature.""" + temperature = kwargs[ATTR_TEMPERATURE] + + if ( + mode := self.device.states[ + OverkizState.OVP_HEATING_TEMPERATURE_INTERFACE_SETPOINT_MODE + ] + ) and mode.value_as_str: + return await self.executor.async_execute_command( + SETPOINT_MODE_TO_OVERKIZ_COMMAND[mode.value_as_str], temperature + ) diff --git a/homeassistant/components/overkiz/const.py b/homeassistant/components/overkiz/const.py index 102d09a76b1..91346b63ce0 100644 --- a/homeassistant/components/overkiz/const.py +++ b/homeassistant/components/overkiz/const.py @@ -98,6 +98,7 @@ OVERKIZ_DEVICE_TO_PLATFORM: dict[UIClass | UIWidget, Platform | None] = { UIWidget.RTD_OUTDOOR_SIREN: Platform.SWITCH, # widgetName, uiClass is Siren (not supported) UIWidget.RTS_GENERIC: Platform.COVER, # widgetName, uiClass is Generic (not supported) UIWidget.SIREN_STATUS: None, # widgetName, uiClass is Siren (siren) + UIWidget.SOMFY_HEATING_TEMPERATURE_INTERFACE: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) UIWidget.SOMFY_THERMOSTAT: Platform.CLIMATE, # widgetName, uiClass is HeatingSystem (not supported) UIWidget.STATELESS_ALARM_CONTROLLER: Platform.SWITCH, # widgetName, uiClass is Alarm (not supported) UIWidget.STATEFUL_ALARM_CONTROLLER: Platform.ALARM_CONTROL_PANEL, # widgetName, uiClass is Alarm (not supported)