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:
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user