From 195643d916354919b2bd10b84d2c4115fd0ce1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexey=20ALERT=20Rubash=D1=91ff?= Date: Thu, 27 Jun 2024 23:05:58 +0300 Subject: [PATCH] Improve AtlanticDomesticHotWaterProductionMBLComponent support in Overkiz (#114178) * add overkiz AtlanticDHW support Adds support of Overkiz water heater entity selection based on device controllable_name Adds support of Atlantic water heater based on Atlantic Steatite Cube WI-FI VM 150 S4CS 2400W Adds more Overkiz water heater binary_sensors, numbers, and sensors * Changed class annotation * min_temp and max_temp as properties * reverted binary_sensors, number, sensor to make separate PRs * Update homeassistant/components/overkiz/water_heater_entities/atlantic_dhw.py Co-authored-by: Mick Vleeshouwer * Update homeassistant/components/overkiz/water_heater_entities/atlantic_dhw.py Co-authored-by: Mick Vleeshouwer * Update homeassistant/components/overkiz/water_heater_entities/atlantic_dhw.py Co-authored-by: Mick Vleeshouwer * Update homeassistant/components/overkiz/water_heater.py Co-authored-by: Mick Vleeshouwer * Update homeassistant/components/overkiz/water_heater_entities/atlantic_dhw.py Co-authored-by: Mick Vleeshouwer * Update homeassistant/components/overkiz/water_heater_entities/atlantic_dhw.py Co-authored-by: Mick Vleeshouwer * review fixes, typos, and pylint * review fix * review fix * ruff * temperature properties changed to constructor attributes * logger removed * constants usage consistency * redundant mapping removed * Update homeassistant/components/overkiz/water_heater_entities/atlantic_dhw.py Co-authored-by: Mick Vleeshouwer * boost mode method annotation typo * removed away mode for atlantic dwh * absence and boost mode attributes now support 'prog' state * heating status bugfix * electrical consumption sensor * warm water remaining volume sensor * away mode reintroduced * mypy check * boost plus state support * Update homeassistant/components/overkiz/sensor.py Co-authored-by: Mick Vleeshouwer * sensors reverted to separate them into their own PR * check away and boost modes on before switching them off * atlantic_dhw renamed to atlantic_domestic_hot_water_production * annotation changed * AtlanticDomesticHotWaterProductionMBLComponent file renamed, annotation change reverted --------- Co-authored-by: Mick Vleeshouwer --- .../components/overkiz/binary_sensor.py | 9 +- .../components/overkiz/water_heater.py | 29 ++- .../overkiz/water_heater_entities/__init__.py | 7 + ...stic_hot_water_production_mlb_component.py | 182 ++++++++++++++++++ 4 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 homeassistant/components/overkiz/water_heater_entities/atlantic_domestic_hot_water_production_mlb_component.py diff --git a/homeassistant/components/overkiz/binary_sensor.py b/homeassistant/components/overkiz/binary_sensor.py index c37afc9cb0c..8ea86e03e8c 100644 --- a/homeassistant/components/overkiz/binary_sensor.py +++ b/homeassistant/components/overkiz/binary_sensor.py @@ -109,17 +109,20 @@ BINARY_SENSOR_DESCRIPTIONS: list[OverkizBinarySensorDescription] = [ key=OverkizState.CORE_HEATING_STATUS, name="Heating status", device_class=BinarySensorDeviceClass.HEAT, - value_fn=lambda state: state == OverkizCommandParam.ON, + value_fn=lambda state: cast(str, state).lower() + in (OverkizCommandParam.ON, OverkizCommandParam.HEATING), ), OverkizBinarySensorDescription( key=OverkizState.MODBUSLINK_DHW_ABSENCE_MODE, name="Absence mode", - value_fn=lambda state: state == OverkizCommandParam.ON, + value_fn=lambda state: state + in (OverkizCommandParam.ON, OverkizCommandParam.PROG), ), OverkizBinarySensorDescription( key=OverkizState.MODBUSLINK_DHW_BOOST_MODE, name="Boost mode", - value_fn=lambda state: state == OverkizCommandParam.ON, + value_fn=lambda state: state + in (OverkizCommandParam.ON, OverkizCommandParam.PROG), ), ] diff --git a/homeassistant/components/overkiz/water_heater.py b/homeassistant/components/overkiz/water_heater.py index c76f6d5099f..99bfb279e4c 100644 --- a/homeassistant/components/overkiz/water_heater.py +++ b/homeassistant/components/overkiz/water_heater.py @@ -9,7 +9,11 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import HomeAssistantOverkizData from .const import DOMAIN -from .water_heater_entities import WIDGET_TO_WATER_HEATER_ENTITY +from .entity import OverkizEntity +from .water_heater_entities import ( + CONTROLLABLE_NAME_TO_WATER_HEATER_ENTITY, + WIDGET_TO_WATER_HEATER_ENTITY, +) async def async_setup_entry( @@ -19,11 +23,20 @@ async def async_setup_entry( ) -> None: """Set up the Overkiz DHW from a config entry.""" data: HomeAssistantOverkizData = hass.data[DOMAIN][entry.entry_id] + entities: list[OverkizEntity] = [] - async_add_entities( - WIDGET_TO_WATER_HEATER_ENTITY[device.widget]( - device.device_url, data.coordinator - ) - for device in data.platforms[Platform.WATER_HEATER] - if device.widget in WIDGET_TO_WATER_HEATER_ENTITY - ) + for device in data.platforms[Platform.WATER_HEATER]: + if device.controllable_name in CONTROLLABLE_NAME_TO_WATER_HEATER_ENTITY: + entities.append( + CONTROLLABLE_NAME_TO_WATER_HEATER_ENTITY[device.controllable_name]( + device.device_url, data.coordinator + ) + ) + elif device.widget in WIDGET_TO_WATER_HEATER_ENTITY: + entities.append( + WIDGET_TO_WATER_HEATER_ENTITY[device.widget]( + device.device_url, data.coordinator + ) + ) + + async_add_entities(entities) diff --git a/homeassistant/components/overkiz/water_heater_entities/__init__.py b/homeassistant/components/overkiz/water_heater_entities/__init__.py index 6f6539ef659..fdc41f213c6 100644 --- a/homeassistant/components/overkiz/water_heater_entities/__init__.py +++ b/homeassistant/components/overkiz/water_heater_entities/__init__.py @@ -2,6 +2,9 @@ from pyoverkiz.enums.ui import UIWidget +from .atlantic_domestic_hot_water_production_mlb_component import ( + AtlanticDomesticHotWaterProductionMBLComponent, +) from .atlantic_pass_apc_dhw import AtlanticPassAPCDHW from .domestic_hot_water_production import DomesticHotWaterProduction from .hitachi_dhw import HitachiDHW @@ -11,3 +14,7 @@ WIDGET_TO_WATER_HEATER_ENTITY = { UIWidget.DOMESTIC_HOT_WATER_PRODUCTION: DomesticHotWaterProduction, UIWidget.HITACHI_DHW: HitachiDHW, } + +CONTROLLABLE_NAME_TO_WATER_HEATER_ENTITY = { + "modbuslink:AtlanticDomesticHotWaterProductionMBLComponent": AtlanticDomesticHotWaterProductionMBLComponent, +} diff --git a/homeassistant/components/overkiz/water_heater_entities/atlantic_domestic_hot_water_production_mlb_component.py b/homeassistant/components/overkiz/water_heater_entities/atlantic_domestic_hot_water_production_mlb_component.py new file mode 100644 index 00000000000..de995a2bd1a --- /dev/null +++ b/homeassistant/components/overkiz/water_heater_entities/atlantic_domestic_hot_water_production_mlb_component.py @@ -0,0 +1,182 @@ +"""Support for AtlanticDomesticHotWaterProductionMBLComponent.""" + +from typing import Any, cast + +from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState + +from homeassistant.components.water_heater import ( + STATE_ECO, + STATE_OFF, + STATE_PERFORMANCE, + WaterHeaterEntity, + WaterHeaterEntityFeature, +) +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature + +from .. import OverkizDataUpdateCoordinator +from ..entity import OverkizEntity + + +class AtlanticDomesticHotWaterProductionMBLComponent(OverkizEntity, WaterHeaterEntity): + """Representation of AtlanticDomesticHotWaterProductionMBLComponent (modbuslink).""" + + _attr_temperature_unit = UnitOfTemperature.CELSIUS + _attr_supported_features = ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.OPERATION_MODE + | WaterHeaterEntityFeature.AWAY_MODE + | WaterHeaterEntityFeature.ON_OFF + ) + _attr_operation_list = [ + OverkizCommandParam.PERFORMANCE, + OverkizCommandParam.ECO, + OverkizCommandParam.MANUAL, + ] + + def __init__( + self, device_url: str, coordinator: OverkizDataUpdateCoordinator + ) -> None: + """Init method.""" + super().__init__(device_url, coordinator) + self._attr_max_temp = cast( + float, + self.executor.select_state( + OverkizState.CORE_MAXIMAL_TEMPERATURE_MANUAL_MODE + ), + ) + self._attr_min_temp = cast( + float, + self.executor.select_state( + OverkizState.CORE_MINIMAL_TEMPERATURE_MANUAL_MODE + ), + ) + + @property + def current_temperature(self) -> float: + """Return the current temperature.""" + return cast( + float, + self.executor.select_state( + OverkizState.MODBUSLINK_MIDDLE_WATER_TEMPERATURE + ), + ) + + @property + def target_temperature(self) -> float: + """Return the temperature corresponding to the PRESET.""" + return cast( + float, + self.executor.select_state(OverkizState.CORE_WATER_TARGET_TEMPERATURE), + ) + + async def async_set_temperature(self, **kwargs: Any) -> None: + """Set new temperature.""" + temperature = kwargs[ATTR_TEMPERATURE] + await self.executor.async_execute_command( + OverkizCommand.SET_TARGET_DHW_TEMPERATURE, temperature + ) + + @property + def is_boost_mode_on(self) -> bool: + """Return true if boost mode is on.""" + return self.executor.select_state(OverkizState.MODBUSLINK_DHW_BOOST_MODE) in ( + OverkizCommandParam.ON, + OverkizCommandParam.PROG, + ) + + @property + def is_eco_mode_on(self) -> bool: + """Return true if eco mode is on.""" + return self.executor.select_state(OverkizState.MODBUSLINK_DHW_MODE) in ( + OverkizCommandParam.MANUAL_ECO_ACTIVE, + OverkizCommandParam.AUTO_MODE, + ) + + @property + def is_away_mode_on(self) -> bool: + """Return true if away mode is on.""" + return ( + self.executor.select_state(OverkizState.MODBUSLINK_DHW_ABSENCE_MODE) + == OverkizCommandParam.ON + ) + + @property + def current_operation(self) -> str: + """Return current operation.""" + if self.is_away_mode_on: + return STATE_OFF + + if self.is_boost_mode_on: + return STATE_PERFORMANCE + + if self.is_eco_mode_on: + return STATE_ECO + + if ( + cast(str, self.executor.select_state(OverkizState.MODBUSLINK_DHW_MODE)) + == OverkizCommandParam.MANUAL_ECO_INACTIVE + ): + return OverkizCommandParam.MANUAL + + return STATE_OFF + + async def async_set_operation_mode(self, operation_mode: str) -> None: + """Set new operation mode.""" + if operation_mode in (STATE_PERFORMANCE, OverkizCommandParam.BOOST): + if self.is_away_mode_on: + await self.async_turn_away_mode_off() + await self.async_turn_boost_mode_on() + elif operation_mode in ( + OverkizCommandParam.ECO, + OverkizCommandParam.MANUAL_ECO_ACTIVE, + ): + if self.is_away_mode_on: + await self.async_turn_away_mode_off() + if self.is_boost_mode_on: + await self.async_turn_boost_mode_off() + await self.executor.async_execute_command( + OverkizCommand.SET_DHW_MODE, OverkizCommandParam.AUTO_MODE + ) + elif operation_mode in ( + OverkizCommandParam.MANUAL, + OverkizCommandParam.MANUAL_ECO_INACTIVE, + ): + if self.is_away_mode_on: + await self.async_turn_away_mode_off() + if self.is_boost_mode_on: + await self.async_turn_boost_mode_off() + await self.executor.async_execute_command( + OverkizCommand.SET_DHW_MODE, OverkizCommandParam.MANUAL_ECO_INACTIVE + ) + else: + if self.is_away_mode_on: + await self.async_turn_away_mode_off() + if self.is_boost_mode_on: + await self.async_turn_boost_mode_off() + await self.executor.async_execute_command( + OverkizCommand.SET_DHW_MODE, operation_mode + ) + + async def async_turn_away_mode_on(self) -> None: + """Turn away mode on.""" + await self.executor.async_execute_command( + OverkizCommand.SET_ABSENCE_MODE, OverkizCommandParam.ON + ) + + async def async_turn_away_mode_off(self) -> None: + """Turn away mode off.""" + await self.executor.async_execute_command( + OverkizCommand.SET_ABSENCE_MODE, OverkizCommandParam.OFF + ) + + async def async_turn_boost_mode_on(self) -> None: + """Turn boost mode on.""" + await self.executor.async_execute_command( + OverkizCommand.SET_BOOST_MODE, OverkizCommandParam.ON + ) + + async def async_turn_boost_mode_off(self) -> None: + """Turn boost mode off.""" + await self.executor.async_execute_command( + OverkizCommand.SET_BOOST_MODE, OverkizCommandParam.OFF + )