Add regulation limitations

This commit is contained in:
Jean-Marc Collin
2023-10-31 09:07:38 +00:00
parent 49377de248
commit 9d099e3169
13 changed files with 245 additions and 103 deletions

View File

@@ -1219,7 +1219,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self.async_control_heating(force=True)
async def _async_internal_set_temperature(self, temperature):
"""Set the target temperature and the target temperature of underlying climate if any"""
"""Set the target temperature and the target temperature of underlying climate if any
For testing purpose you can pass an event_timestamp.
"""
self._target_temp = temperature
return
@@ -2247,7 +2249,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self.async_control_heating()
self.update_custom_attributes()
#PR - Adding Window ByPass
async def service_set_window_bypass_state(self, window_bypass):
"""Called by a service call:
service: versatile_thermostat.set_window_bypass

View File

@@ -24,12 +24,12 @@ from .const import (
SERVICE_SET_PRESENCE,
SERVICE_SET_PRESET_TEMPERATURE,
SERVICE_SET_SECURITY,
#PR - Adding Window ByPass
SERVICE_SET_WINDOW_BYPASS,
SERVICE_SET_AUTO_REGULATION_MODE,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE,
CONF_THERMOSTAT_VALVE
)
from .thermostat_switch import ThermostatOverSwitch
@@ -99,7 +99,6 @@ async def async_setup_entry(
"service_set_security",
)
#PR - Adding Window ByPass
platform.async_register_entity_service(
SERVICE_SET_WINDOW_BYPASS,
{
@@ -108,3 +107,11 @@ async def async_setup_entry(
},
"service_set_window_bypass_state",
)
platform.async_register_entity_service(
SERVICE_SET_AUTO_REGULATION_MODE,
{
vol.Required("auto_regulation_mode"): vol.In(["None", "Light", "Medium", "Strong"]),
},
"service_set_auto_regulation_mode",
)

View File

@@ -1,18 +1,34 @@
""" Some usefull commons class """
import logging
from datetime import timedelta
from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant, callback, Event
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.event import async_track_state_change_event, async_call_later
from homeassistant.util import dt as dt_util
from .base_thermostat import BaseThermostat
from .const import DOMAIN, DEVICE_MANUFACTURER
_LOGGER = logging.getLogger(__name__)
def get_tz(hass: HomeAssistant):
"""Get the current timezone"""
return dt_util.get_time_zone(hass.config.time_zone)
class NowClass:
""" For testing purpose only"""
@staticmethod
def get_now(hass: HomeAssistant) -> datetime:
""" A test function to get the now.
For testing purpose this method can be overriden to get a specific
timestamp
"""
return datetime.now( get_tz(hass))
class VersatileThermostatBaseEntity(Entity):
"""A base class for all entities"""
@@ -98,7 +114,7 @@ class VersatileThermostatBaseEntity(Entity):
await try_find_climate(None)
@callback
async def async_my_climate_changed(self, event: Event):
async def async_my_climate_changed(self, event: Event): # pylint: disable=unused-argument
"""Called when my climate have change
This method aims to be overriden to take the status change
"""

View File

@@ -102,6 +102,8 @@ from .const import (
CONF_AUTO_REGULATION_MODES,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
UnknownEntity,
WindowOpenDetectionMethod,
)
@@ -264,6 +266,9 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
options=CONF_AUTO_REGULATION_MODES, translation_key="auto_regulation_mode"
)
),
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

@@ -88,6 +88,8 @@ CONF_AUTO_REGULATION_NONE= "auto_regulation_none"
CONF_AUTO_REGULATION_LIGHT= "auto_regulation_light"
CONF_AUTO_REGULATION_MEDIUM= "auto_regulation_medium"
CONF_AUTO_REGULATION_STRONG= "auto_regulation_strong"
CONF_AUTO_REGULATION_DTEMP="auto_regulation_dtemp"
CONF_AUTO_REGULATION_PERIOD_MIN="auto_regulation_periode_min"
CONF_PRESETS = {
p: f"{p}_temp"
@@ -189,7 +191,9 @@ ALL_CONF = (
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_AUTO_REGULATION_MODE
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN
]
+ CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES
@@ -210,8 +214,8 @@ SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
SERVICE_SET_SECURITY = "set_security"
#PR - Adding Window ByPass
SERVICE_SET_WINDOW_BYPASS = "set_window_bypass"
SERVICE_SET_AUTO_REGULATION_MODE = "set_auto_regulation_mode"
DEFAULT_SECURITY_MIN_ON_PERCENT = 0.5
DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1

View File

@@ -137,4 +137,25 @@ set_window_bypass:
advanced: false
default: true
selector:
boolean:
boolean:
set_auto_regulation_mode:
name: Set Auto Regulation mode
description: Change the mode of self-regulation (only for VTherm over climate)
target:
entity:
integration: versatile_thermostat
fields:
auto_regulation_mode:
name: Auto regulation mode
description: Possible values
required: true
advanced: false
default: true
selector:
select:
options:
- "None"
- "Light"
- "Medium"
- "Strong"

View File

@@ -39,7 +39,9 @@
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation"
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -56,7 +58,9 @@
"valve_entity2_id": "2nd valve number entity id",
"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_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
}
},
"tpi": {
@@ -202,7 +206,9 @@
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation"
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -219,7 +225,9 @@
"valve_entity2_id": "2nd valve number entity id",
"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_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
}
},
"tpi": {

View File

@@ -1,13 +1,14 @@
# pylint: disable=line-too-long
""" A climate over switch classe """
import logging
from datetime import timedelta
from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.event import async_track_state_change_event, async_track_time_interval
from homeassistant.components.climate import HVACAction, HVACMode
from .commons import NowClass
from .base_thermostat import BaseThermostat
from .pi_algorithm import PITemperatureRegulator
@@ -22,6 +23,8 @@ from .const import (
CONF_AUTO_REGULATION_LIGHT,
CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
RegulationParamLight,
RegulationParamMedium,
RegulationParamStrong
@@ -33,9 +36,12 @@ _LOGGER = logging.getLogger(__name__)
class ThermostatOverClimate(BaseThermostat):
"""Representation of a base class for a Versatile Thermostat over a climate"""
_regulation_mode:str = None
_auto_regulation_mode:str = None
_regulation_algo = None
_regulated_target_temp: float = None
_auto_regulation_dtemp: float = None
_auto_regulation_period_min: int = None
_last_regulation_change: datetime = None
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union(frozenset(
{
@@ -48,6 +54,7 @@ class ThermostatOverClimate(BaseThermostat):
# super.__init__ calls post_init at the end. So it must be called after regulation initialization
super().__init__(hass, unique_id, name, entry_infos)
self._regulated_target_temp = self.target_temperature
self._last_regulation_change = NowClass.get_now(hass)
@property
def is_over_climate(self) -> bool:
@@ -84,17 +91,31 @@ class ThermostatOverClimate(BaseThermostat):
async def _send_regulated_temperature(self):
""" Sends the regulated temperature to all underlying """
new_regulated_temp = self._regulation_algo.calculate_regulated_temperature(self.current_temperature, self._cur_ext_temp)
if new_regulated_temp != self._regulated_target_temp:
_LOGGER.info("%s - Regulated temp have changed to %.1f. Resend it to underlyings", self, new_regulated_temp)
self._regulated_target_temp = new_regulated_temp
if not self._regulated_target_temp:
self._regulated_target_temp = self.target_temperature
for under in self._underlyings:
await under.set_temperature(
self.regulated_target_temp, self._attr_max_temp, self._attr_min_temp
)
else:
_LOGGER.debug("%s - No change on regulated temperature (%.1f)", self, self._regulated_target_temp)
new_regulated_temp = self._regulation_algo.calculate_regulated_temperature(self.current_temperature, self._cur_ext_temp)
dtemp = new_regulated_temp - self._regulated_target_temp
if abs(dtemp) < self._auto_regulation_dtemp:
_LOGGER.info("!!!!! %s - dtemp (%.1f) is < %.1f -> forget the regulation send", self, dtemp, self._auto_regulation_dtemp)
return
now:datetime = NowClass.get_now(self._hass)
period = float((now - self._last_regulation_change).total_seconds()) / 60.
if period < self._auto_regulation_period_min:
_LOGGER.debug("%s - period (%.1f) is < %.0f -> forget the regulation send", self, period, self._auto_regulation_period_min)
return
self._regulated_target_temp = new_regulated_temp
_LOGGER.info("!!!!! %s - Regulated temp have changed to %.1f. Resend it to underlyings", self, new_regulated_temp)
self._last_regulation_change = now
for under in self._underlyings:
await under.set_temperature(
self.regulated_target_temp, self._attr_max_temp, self._attr_min_temp
)
@overrides
def post_init(self, entry_infos):
@@ -116,9 +137,17 @@ class ThermostatOverClimate(BaseThermostat):
)
)
self._regulation_mode = entry_infos.get(CONF_AUTO_REGULATION_MODE) if entry_infos.get(CONF_AUTO_REGULATION_MODE) is not None else CONF_AUTO_REGULATION_NONE
self.choose_auto_regulation_mode(
entry_infos.get(CONF_AUTO_REGULATION_MODE) if entry_infos.get(CONF_AUTO_REGULATION_MODE) is not None else CONF_AUTO_REGULATION_NONE
)
if self._regulation_mode == CONF_AUTO_REGULATION_LIGHT:
self._auto_regulation_dtemp = entry_infos.get(CONF_AUTO_REGULATION_DTEMP) if entry_infos.get(CONF_AUTO_REGULATION_DTEMP) is not None else 0.5
self._auto_regulation_period_min = entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) if entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None else 5
def choose_auto_regulation_mode(self, auto_regulation_mode):
""" Choose or change the regulation mode"""
self._auto_regulation_mode = auto_regulation_mode
if self._auto_regulation_mode == CONF_AUTO_REGULATION_LIGHT:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
RegulationParamLight.kp,
@@ -127,7 +156,7 @@ class ThermostatOverClimate(BaseThermostat):
RegulationParamLight.offset_max,
RegulationParamLight.stabilization_threshold,
RegulationParamLight.accumulated_error_threshold)
elif self._regulation_mode == CONF_AUTO_REGULATION_MEDIUM:
elif self._auto_regulation_mode == CONF_AUTO_REGULATION_MEDIUM:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
RegulationParamMedium.kp,
@@ -136,7 +165,7 @@ class ThermostatOverClimate(BaseThermostat):
RegulationParamMedium.offset_max,
RegulationParamMedium.stabilization_threshold,
RegulationParamMedium.accumulated_error_threshold)
elif self._regulation_mode == CONF_AUTO_REGULATION_STRONG:
elif self._auto_regulation_mode == CONF_AUTO_REGULATION_STRONG:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
RegulationParamStrong.kp,
@@ -430,9 +459,9 @@ class ThermostatOverClimate(BaseThermostat):
return ret
@property
def regulation_mode(self):
def auto_regulation_mode(self):
""" Get the regulation mode """
return self._regulation_mode
return self._auto_regulation_mode
@property
def regulated_target_temp(self):
@@ -442,7 +471,7 @@ class ThermostatOverClimate(BaseThermostat):
@property
def is_regulated(self):
""" Check if the ThermostatOverClimate is regulated """
return self.regulation_mode != CONF_AUTO_REGULATION_NONE
return self.auto_regulation_mode != CONF_AUTO_REGULATION_NONE
@property
def hvac_modes(self):
@@ -617,3 +646,24 @@ class ThermostatOverClimate(BaseThermostat):
await under.set_swing_mode(swing_mode)
self._swing_mode = swing_mode
self.async_write_ha_state()
async def service_set_auto_regulation_mode(self, auto_regulation_mode):
"""Called by a service call:
service: versatile_thermostat.set_auto_regulation_mode
data:
auto_regulation_mode: [None | Light | Medium | Strong]
target:
entity_id: climate.thermostat_1
"""
_LOGGER.info("%s - Calling service_set_auto_regulation_mode, auto_regulation_mode: %s", self, auto_regulation_mode)
if auto_regulation_mode == "None":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_NONE)
elif auto_regulation_mode == "Light":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_LIGHT)
elif auto_regulation_mode == "Medium":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_MEDIUM)
elif auto_regulation_mode == "Strong":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_STRONG)
await self._send_regulated_temperature()
self.update_custom_attributes()

View File

@@ -39,7 +39,9 @@
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation"
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -56,7 +58,9 @@
"valve_entity2_id": "2nd valve number entity id",
"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_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
}
},
"tpi": {
@@ -202,7 +206,9 @@
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation"
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -219,7 +225,9 @@
"valve_entity2_id": "2nd valve number entity id",
"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_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
}
},
"tpi": {

View File

@@ -39,7 +39,9 @@
"valve_entity2_id": "2ème valve number",
"valve_entity3_id": "3ème valve number",
"valve_entity4_id": "4ème valve number",
"auto_regulation_mode": "Auto-régulation"
"auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -56,7 +58,9 @@
"valve_entity2_id": "Entity id de la 2ème valve",
"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_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_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation"
}
},
"tpi": {
@@ -203,7 +207,9 @@
"valve_entity2_id": "2ème valve",
"valve_entity3_id": "3ème valve",
"valve_entity4_id": "4ème valve",
"auto_regulation_mode": "Auto-regulation"
"auto_regulation_mode": "Auto-regulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -220,7 +226,9 @@
"valve_entity2_id": "Entity id de la 2ème valve",
"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_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_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation"
}
},
"tpi": {