Issue 338 limit regulation over valve to avoid drowning battery of the TRV (#356)

* With testus ok

* Clean TPI algo

* Commenet failed testu

* Documentation

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
Jean-Marc Collin
2024-01-21 19:36:44 +01:00
committed by GitHub
parent 26844593b1
commit 8cbd81012c
11 changed files with 335 additions and 42 deletions

View File

@@ -145,20 +145,6 @@ def get_tz(hass: HomeAssistant):
class BaseThermostat(ClimateEntity, RestoreEntity):
"""Representation of a base class for all Versatile Thermostat device."""
# The list of VersatileThermostat entities
_hass: HomeAssistant
_last_temperature_measure: datetime
_last_ext_temperature_measure: datetime
_total_energy: float
_overpowering_state: bool
_window_state: bool
_motion_state: bool
_presence_state: bool
_window_auto_state: bool
_window_bypass_state: bool
_underlyings: list[UnderlyingEntity]
_last_change_time: datetime
_entity_component_unrecorded_attributes = (
ClimateEntity._entity_component_unrecorded_attributes.union(
frozenset(

View File

@@ -166,6 +166,8 @@ STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
]
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=0.5): vol.Coerce(float),
vol.Optional(CONF_AUTO_REGULATION_PERIOD_MIN, default=5): cv.positive_int,
}
)

View File

@@ -78,7 +78,7 @@
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be sent",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
@@ -313,7 +313,7 @@
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be sent",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"

View File

@@ -1,13 +1,13 @@
# pylint: disable=line-too-long
""" A climate over switch classe """
import logging
from datetime import timedelta
from datetime import timedelta, datetime
from homeassistant.helpers.event import (
async_track_state_change_event,
async_track_time_interval,
)
from homeassistant.core import callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.components.climate import HVACMode
from .base_thermostat import BaseThermostat
@@ -18,6 +18,9 @@ from .const import (
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
# This is not really self-regulation but regulation here
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
overrides,
)
@@ -44,15 +47,23 @@ class ThermostatOverValve(BaseThermostat):
"function",
"tpi_coef_int",
"tpi_coef_ext",
"auto_regulation_dpercent",
"auto_regulation_period_min",
"last_calculation_timestamp",
}
)
)
)
# Useless for now
# def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
# """Initialize the thermostat over switch."""
# super().__init__(hass, unique_id, name, config_entry)
def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
"""Initialize the thermostat over switch."""
self._valve_open_percent: int = 0
self._last_calculation_timestamp: datetime = None
self._auto_regulation_dpercent: float = None
self._auto_regulation_period_min: int = None
# Call to super must be done after initialization because it calls post_init at the end
super().__init__(hass, unique_id, name, config_entry)
@property
def is_over_valve(self) -> bool:
@@ -65,13 +76,25 @@ class ThermostatOverValve(BaseThermostat):
if self._hvac_mode == HVACMode.OFF:
return 0
else:
return round(max(0, min(self.proportional_algorithm.on_percent, 1)) * 100)
return self._valve_open_percent
@overrides
def post_init(self, config_entry):
"""Initialize the Thermostat"""
super().post_init(config_entry)
self._auto_regulation_dpercent = (
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
else 0.0
)
self._auto_regulation_period_min = (
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
else 0
)
self._prop_algorithm = PropAlgorithm(
self._proportional_function,
self._tpi_coef_int,
@@ -164,6 +187,17 @@ class ThermostatOverValve(BaseThermostat):
self._attr_extra_state_attributes["function"] = self._proportional_function
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
self._attr_extra_state_attributes[
"auto_regulation_dpercent"
] = self._auto_regulation_dpercent
self._attr_extra_state_attributes[
"auto_regulation_period_min"
] = self._auto_regulation_period_min
self._attr_extra_state_attributes["last_calculation_timestamp"] = (
self._last_calculation_timestamp.astimezone(self._current_tz).isoformat()
if self._last_calculation_timestamp
else None
)
self.async_write_ha_state()
_LOGGER.debug(
@@ -177,7 +211,21 @@ class ThermostatOverValve(BaseThermostat):
"""A utility function to force the calculation of a the algo and
update the custom attributes and write the state
"""
_LOGGER.debug("%s - recalculate all", self)
_LOGGER.debug("%s - recalculate the open percent", self)
# For testing purpose. Should call _set_now() before
now = self.now
if self._last_calculation_timestamp is not None:
period = (now - self._last_calculation_timestamp).total_seconds() / 60
if period < self._auto_regulation_period_min:
_LOGGER.info(
"%s - do not calculate TPI because regulation_period (%d) is not exceeded",
self,
period,
)
return
self._prop_algorithm.calculate(
self._target_temp,
self._cur_temp,
@@ -185,9 +233,34 @@ class ThermostatOverValve(BaseThermostat):
self._hvac_mode == HVACMode.COOL,
)
new_valve_percent = round(
max(0, min(self.proportional_algorithm.on_percent, 1)) * 100
)
dpercent = new_valve_percent - self.valve_open_percent
if (
dpercent >= -1 * self._auto_regulation_dpercent
and dpercent < self._auto_regulation_dpercent
):
_LOGGER.debug(
"%s - do not calculate TPI because regulation_dpercent (%.1f) is not exceeded",
self,
dpercent,
)
return
if self._valve_open_percent == new_valve_percent:
_LOGGER.debug("%s - no change in valve_open_percent.", self)
return
self._valve_open_percent = new_valve_percent
for under in self._underlyings:
under.set_valve_open_percent()
self._last_calculation_timestamp = now
self.update_custom_attributes()
self.async_write_ha_state()

View File

@@ -78,7 +78,7 @@
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be sent",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
@@ -313,7 +313,7 @@
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be sent",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"

View File

@@ -78,7 +78,7 @@
"valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la température cible",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
@@ -325,7 +325,7 @@
"valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la consigne",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"

View File

@@ -765,7 +765,7 @@ class UnderlyingValve(UnderlyingEntity):
await self.send_percent_open()
async def turn_on(self):
"""Nothing to do for Valve because it cannot be turned off"""
"""Nothing to do for Valve because it cannot be turned on"""
self.set_valve_open_percent()
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool: