From fdcdf91f954bc7997559033e0e66591be21d87f0 Mon Sep 17 00:00:00 2001 From: Jean-Marc Collin Date: Tue, 19 Nov 2024 06:58:20 +0000 Subject: [PATCH] Calculate offset_calibration as room_temp - local_temp Fix hvac_action calculation --- .../versatile_thermostat/base_thermostat.py | 2 +- .../versatile_thermostat/const.py | 4 +-- .../thermostat_climate.py | 13 +++++--- .../thermostat_sonoff_trvzb.py | 9 ++++- .../versatile_thermostat/underlyings.py | 33 ++++++++++++++++--- .../versatile_thermostat/vtherm_api.py | 8 ++++- 6 files changed, 54 insertions(+), 15 deletions(-) diff --git a/custom_components/versatile_thermostat/base_thermostat.py b/custom_components/versatile_thermostat/base_thermostat.py index c8c40c5..5e0a14c 100644 --- a/custom_components/versatile_thermostat/base_thermostat.py +++ b/custom_components/versatile_thermostat/base_thermostat.py @@ -504,7 +504,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): entry_infos.get(CONF_WINDOW_ACTION) or CONF_WINDOW_TURN_OFF ) - self._max_on_percent = api._max_on_percent + self._max_on_percent = api.max_on_percent _LOGGER.debug( "%s - Creation of a new VersatileThermostat entity: unique_id=%s", diff --git a/custom_components/versatile_thermostat/const.py b/custom_components/versatile_thermostat/const.py index 57cc1c6..8f59636 100644 --- a/custom_components/versatile_thermostat/const.py +++ b/custom_components/versatile_thermostat/const.py @@ -464,9 +464,9 @@ class RegulationParamVeryStrong: kp: float = 0.6 ki: float = 0.1 k_ext: float = 0.2 - offset_max: float = 4 + offset_max: float = 8 stabilization_threshold: float = 0.1 - accumulated_error_threshold: float = 30 + accumulated_error_threshold: float = 80 class EventType(Enum): diff --git a/custom_components/versatile_thermostat/thermostat_climate.py b/custom_components/versatile_thermostat/thermostat_climate.py index 5ad134e..f9bbdca 100644 --- a/custom_components/versatile_thermostat/thermostat_climate.py +++ b/custom_components/versatile_thermostat/thermostat_climate.py @@ -151,15 +151,13 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]): """True if the Thermostat is over_climate""" return True - @property - def hvac_action(self) -> HVACAction | None: - """Returns the current hvac_action by checking all hvac_action of the underlyings""" - + def calculate_hvac_action(self, under_list: list) -> HVACAction | None: + """Calculate an hvac action based on the hvac_action of the list in argument""" # if one not IDLE or OFF -> return it # else if one IDLE -> IDLE # else OFF one_idle = False - for under in self._underlyings: + for under in under_list: if (action := under.hvac_action) not in [ HVACAction.IDLE, HVACAction.OFF, @@ -171,6 +169,11 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]): return HVACAction.IDLE return HVACAction.OFF + @property + def hvac_action(self) -> HVACAction | None: + """Returns the current hvac_action by checking all hvac_action of the underlyings""" + return self.calculate_hvac_action(self._underlyings) + @overrides async def _async_internal_set_temperature(self, temperature: float): """Set the target temperature and the target temperature of underlying climate if any""" diff --git a/custom_components/versatile_thermostat/thermostat_sonoff_trvzb.py b/custom_components/versatile_thermostat/thermostat_sonoff_trvzb.py index 4a00748..ee15ed8 100644 --- a/custom_components/versatile_thermostat/thermostat_sonoff_trvzb.py +++ b/custom_components/versatile_thermostat/thermostat_sonoff_trvzb.py @@ -5,7 +5,7 @@ import logging from datetime import datetime from homeassistant.core import HomeAssistant -from homeassistant.components.climate import HVACMode +from homeassistant.components.climate import HVACMode, HVACAction from .underlyings import UnderlyingSonoffTRVZB @@ -96,6 +96,7 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): offset_calibration_entity_id=offset, opening_degree_entity_id=opening, closing_degree_entity_id=closing, + climate_underlying=self._underlyings[idx], ) self._underlyings_sonoff_trvzb.append(under) @@ -240,3 +241,9 @@ class ThermostatOverSonoffTRVZB(ThermostatOverClimate): return 0 else: return self._valve_open_percent + + @property + def hvac_action(self) -> HVACAction | None: + """Returns the current hvac_action by checking all hvac_action of the _underlyings_sonoff_trvzb""" + + return self.calculate_hvac_action(self._underlyings_sonoff_trvzb) diff --git a/custom_components/versatile_thermostat/underlyings.py b/custom_components/versatile_thermostat/underlyings.py index 8022902..7688148 100644 --- a/custom_components/versatile_thermostat/underlyings.py +++ b/custom_components/versatile_thermostat/underlyings.py @@ -1030,15 +1030,18 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): offset_calibration_entity_id: str, opening_degree_entity_id: str, closing_degree_entity_id: str, + climate_underlying: UnderlyingClimate, ) -> None: """Initialize the underlying Sonoff TRV""" super().__init__(hass, thermostat, opening_degree_entity_id) self._offset_calibration_entity_id = offset_calibration_entity_id self._opening_degree_entity_id = opening_degree_entity_id self._closing_degree_entity_id = closing_degree_entity_id + self._climate_underlying = climate_underlying self._is_min_max_initialized = False self._max_opening_degree = None self._min_offset_calibration = None + self._max_offset_calibration = None async def send_percent_open(self): """Send the percent open to the underlying valve""" @@ -1052,6 +1055,9 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): self._min_offset_calibration = self._hass.states.get( self._offset_calibration_entity_id ).attributes.get("min") + self._max_offset_calibration = self._hass.states.get( + self._offset_calibration_entity_id + ).attributes.get("max") self._is_min_max_initialized = ( self._max_opening_degree is not None @@ -1067,15 +1073,32 @@ class UnderlyingSonoffTRVZB(UnderlyingValve): # Send opening_degree await super().send_percent_open() - # Send closing_degree. TODO 100 hard-coded or take the max of the _closing_degree_entity_id ? + # Send closing_degree. await self._send_value_to_number( self._closing_degree_entity_id, - self._max_opening_degree - self._percent_open, + closing_degree := self._max_opening_degree - self._percent_open, ) - # send offset_calibration to the min value - await self._send_value_to_number( - self._offset_calibration_entity_id, self._min_offset_calibration + # send offset_calibration to the difference between target temp and local temp + offset = 0 + if ( + local_temp := self._climate_underlying.underlying_current_temperature + ) is not None and ( + room_temp := self._thermostat.current_temperature + ) is not None: + offset = min( + self._max_offset_calibration, + max(self._min_offset_calibration, room_temp - local_temp), + ) + + await self._send_value_to_number(self._offset_calibration_entity_id, offset) + + _LOGGER.debug( + "%s - SonoffTRVZB - I have sent offset_calibration=%s opening_degree=%s closing_degree=%s", + self, + offset, + self._percent_open, + closing_degree, ) @property diff --git a/custom_components/versatile_thermostat/vtherm_api.py b/custom_components/versatile_thermostat/vtherm_api.py index e268661..3ae8e8f 100644 --- a/custom_components/versatile_thermostat/vtherm_api.py +++ b/custom_components/versatile_thermostat/vtherm_api.py @@ -179,7 +179,8 @@ class VersatileThermostatAPI(dict): # ): # await entity.init_presets(self.find_central_configuration()) - # A little hack to test if the climate is a VTherm. Cannot use isinstance due to circular dependency of BaseThermostat + # A little hack to test if the climate is a VTherm. Cannot use isinstance + # due to circular dependency of BaseThermostat if ( entity.device_info and entity.device_info.get("model", None) == DOMAIN @@ -249,6 +250,11 @@ class VersatileThermostatAPI(dict): """Get the safety_mode params""" return self._safety_mode + @property + def max_on_percent(self): + """Get the max_open_percent params""" + return self._max_on_percent + @property def central_boiler_entity(self): """Get the central boiler binary_sensor entity"""