Compare commits
3 Commits
5.4.0-beta
...
5.4.0.alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe0e57231 | ||
|
|
de47a3ffe1 | ||
|
|
85dcac9530 |
7
copy-to-forum.txt
Normal file
7
copy-to-forum.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Before copying to forum you need to replace relative images by this command into VSCode:
|
||||||
|
|
||||||
|
Search :
|
||||||
|
\(images/(.*).png\)
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
(https://github.com/jmcollin78/versatile_thermostat/blob/main/images/$1.png?raw=true)
|
||||||
@@ -143,6 +143,7 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
|
|||||||
mode="dropdown",
|
mode="dropdown",
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
vol.Optional(CONF_AUTO_REGULATION_USE_DEVICE_TEMP, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ CONF_AUTO_REGULATION_STRONG = "auto_regulation_strong"
|
|||||||
CONF_AUTO_REGULATION_EXPERT = "auto_regulation_expert"
|
CONF_AUTO_REGULATION_EXPERT = "auto_regulation_expert"
|
||||||
CONF_AUTO_REGULATION_DTEMP = "auto_regulation_dtemp"
|
CONF_AUTO_REGULATION_DTEMP = "auto_regulation_dtemp"
|
||||||
CONF_AUTO_REGULATION_PERIOD_MIN = "auto_regulation_periode_min"
|
CONF_AUTO_REGULATION_PERIOD_MIN = "auto_regulation_periode_min"
|
||||||
|
CONF_AUTO_REGULATION_USE_DEVICE_TEMP = "auto_regulation_use_device_temp"
|
||||||
CONF_INVERSE_SWITCH = "inverse_switch_command"
|
CONF_INVERSE_SWITCH = "inverse_switch_command"
|
||||||
CONF_AUTO_FAN_MODE = "auto_fan_mode"
|
CONF_AUTO_FAN_MODE = "auto_fan_mode"
|
||||||
CONF_AUTO_FAN_NONE = "auto_fan_none"
|
CONF_AUTO_FAN_NONE = "auto_fan_none"
|
||||||
@@ -255,6 +256,7 @@ ALL_CONF = (
|
|||||||
CONF_AUTO_REGULATION_MODE,
|
CONF_AUTO_REGULATION_MODE,
|
||||||
CONF_AUTO_REGULATION_DTEMP,
|
CONF_AUTO_REGULATION_DTEMP,
|
||||||
CONF_AUTO_REGULATION_PERIOD_MIN,
|
CONF_AUTO_REGULATION_PERIOD_MIN,
|
||||||
|
CONF_AUTO_REGULATION_USE_DEVICE_TEMP,
|
||||||
CONF_INVERSE_SWITCH,
|
CONF_INVERSE_SWITCH,
|
||||||
CONF_AUTO_FAN_MODE,
|
CONF_AUTO_FAN_MODE,
|
||||||
CONF_USE_MAIN_CENTRAL_CONFIG,
|
CONF_USE_MAIN_CENTRAL_CONFIG,
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ class PITemperatureRegulator:
|
|||||||
self.accumulated_error = 0
|
self.accumulated_error = 0
|
||||||
|
|
||||||
def calculate_regulated_temperature(
|
def calculate_regulated_temperature(
|
||||||
self, internal_temp: float, external_temp: float
|
self, room_temp: float, external_temp: float
|
||||||
): # pylint: disable=unused-argument
|
): # pylint: disable=unused-argument
|
||||||
"""Calculate a new target_temp given some temperature"""
|
"""Calculate a new target_temp given some temperature"""
|
||||||
if internal_temp is None:
|
if room_temp is None:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Temporarily skipping the self-regulation algorithm while the configured sensor for room temperature is unavailable"
|
"Temporarily skipping the self-regulation algorithm while the configured sensor for room temperature is unavailable"
|
||||||
)
|
)
|
||||||
@@ -68,7 +68,7 @@ class PITemperatureRegulator:
|
|||||||
return self.target_temp
|
return self.target_temp
|
||||||
|
|
||||||
# Calculate the error factor (P)
|
# Calculate the error factor (P)
|
||||||
error = self.target_temp - internal_temp
|
error = self.target_temp - room_temp
|
||||||
|
|
||||||
# Calculate the sum of error (I)
|
# Calculate the sum of error (I)
|
||||||
self.accumulated_error += error
|
self.accumulated_error += error
|
||||||
@@ -83,22 +83,16 @@ class PITemperatureRegulator:
|
|||||||
offset = self.kp * error + self.ki * self.accumulated_error
|
offset = self.kp * error + self.ki * self.accumulated_error
|
||||||
|
|
||||||
# Calculate the exterior offset
|
# Calculate the exterior offset
|
||||||
# For Maia tests - use the internal_temp vs external_temp and not target_temp - external_temp
|
offset_ext = self.k_ext * (room_temp - external_temp)
|
||||||
offset_ext = self.k_ext * (internal_temp - external_temp)
|
|
||||||
|
|
||||||
# Capping of offset_ext
|
# Capping of offset
|
||||||
total_offset = offset + offset_ext
|
total_offset = offset + offset_ext
|
||||||
total_offset = min(self.offset_max, max(-self.offset_max, total_offset))
|
total_offset = min(self.offset_max, max(-self.offset_max, total_offset))
|
||||||
|
|
||||||
# If temperature is near the target_temp, reset the accumulated_error
|
|
||||||
# Issue #199 - don't reset the accumulation error
|
|
||||||
# if abs(error) < self.stabilization_threshold:
|
|
||||||
# _LOGGER.debug("Stabilisation")
|
|
||||||
# self.accumulated_error = 0
|
|
||||||
|
|
||||||
result = round(self.target_temp + total_offset, 1)
|
result = round(self.target_temp + total_offset, 1)
|
||||||
|
|
||||||
_LOGGER.debug(
|
# TODO Change to debug after experimental
|
||||||
|
_LOGGER.info(
|
||||||
"PITemperatureRegulator - Error: %.2f accumulated_error: %.2f offset: %.2f offset_ext: %.2f target_tem: %.1f regulatedTemp: %.1f",
|
"PITemperatureRegulator - Error: %.2f accumulated_error: %.2f offset: %.2f offset_ext: %.2f target_tem: %.1f regulatedTemp: %.1f",
|
||||||
error,
|
error,
|
||||||
self.accumulated_error,
|
self.accumulated_error,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"auto_regulation_mode": "Self-regulation",
|
"auto_regulation_mode": "Self-regulation",
|
||||||
"auto_regulation_dtemp": "Regulation threshold",
|
"auto_regulation_dtemp": "Regulation threshold",
|
||||||
"auto_regulation_periode_min": "Regulation minimal period",
|
"auto_regulation_periode_min": "Regulation minimal period",
|
||||||
|
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
|
||||||
"inverse_switch_command": "Inverse switch command",
|
"inverse_switch_command": "Inverse switch command",
|
||||||
"auto_fan_mode": " Auto fan mode"
|
"auto_fan_mode": " Auto fan mode"
|
||||||
},
|
},
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) 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",
|
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||||
|
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation",
|
||||||
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
|
"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"
|
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
|
||||||
}
|
}
|
||||||
@@ -294,6 +296,7 @@
|
|||||||
"auto_regulation_mode": "Self-regulation",
|
"auto_regulation_mode": "Self-regulation",
|
||||||
"auto_regulation_dtemp": "Regulation threshold",
|
"auto_regulation_dtemp": "Regulation threshold",
|
||||||
"auto_regulation_periode_min": "Regulation minimal period",
|
"auto_regulation_periode_min": "Regulation minimal period",
|
||||||
|
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
|
||||||
"inverse_switch_command": "Inverse switch command",
|
"inverse_switch_command": "Inverse switch command",
|
||||||
"auto_fan_mode": " Auto fan mode"
|
"auto_fan_mode": " Auto fan mode"
|
||||||
},
|
},
|
||||||
@@ -315,6 +318,7 @@
|
|||||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) 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",
|
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||||
|
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation",
|
||||||
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
|
"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"
|
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from .const import (
|
|||||||
CONF_AUTO_REGULATION_EXPERT,
|
CONF_AUTO_REGULATION_EXPERT,
|
||||||
CONF_AUTO_REGULATION_DTEMP,
|
CONF_AUTO_REGULATION_DTEMP,
|
||||||
CONF_AUTO_REGULATION_PERIOD_MIN,
|
CONF_AUTO_REGULATION_PERIOD_MIN,
|
||||||
|
CONF_AUTO_REGULATION_USE_DEVICE_TEMP,
|
||||||
CONF_AUTO_FAN_MODE,
|
CONF_AUTO_FAN_MODE,
|
||||||
CONF_AUTO_FAN_NONE,
|
CONF_AUTO_FAN_NONE,
|
||||||
CONF_AUTO_FAN_LOW,
|
CONF_AUTO_FAN_LOW,
|
||||||
@@ -90,6 +91,7 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
"current_auto_fan_mode",
|
"current_auto_fan_mode",
|
||||||
"auto_activated_fan_mode",
|
"auto_activated_fan_mode",
|
||||||
"auto_deactivated_fan_mode",
|
"auto_deactivated_fan_mode",
|
||||||
|
"auto_regulation_use_device_temp",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -192,8 +194,45 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
|
|
||||||
self._last_regulation_change = now
|
self._last_regulation_change = now
|
||||||
for under in self._underlyings:
|
for under in self._underlyings:
|
||||||
|
# issue 348 - use device temperature if configured as offset
|
||||||
|
offset_temp = 0
|
||||||
|
device_temp = 0
|
||||||
|
if (
|
||||||
|
# regulation can use the device_temp
|
||||||
|
self.auto_regulation_use_device_temp
|
||||||
|
# and we have access to the device temp
|
||||||
|
and (device_temp := under.underlying_current_temperature) is not None
|
||||||
|
# and target is not reach (ie we need regulation)
|
||||||
|
and (
|
||||||
|
(
|
||||||
|
self.hvac_mode == HVACMode.COOL
|
||||||
|
and self.target_temperature < self.current_temperature
|
||||||
|
)
|
||||||
|
or (
|
||||||
|
self.hvac_mode == HVACMode.HEAT
|
||||||
|
and self.target_temperature > self.current_temperature
|
||||||
|
)
|
||||||
|
)
|
||||||
|
):
|
||||||
|
offset_temp = self.current_temperature - device_temp
|
||||||
|
|
||||||
|
if self.hvac_mode == HVACMode.COOL:
|
||||||
|
target_temp = self.regulated_target_temp - offset_temp
|
||||||
|
else:
|
||||||
|
target_temp = self.regulated_target_temp + offset_temp
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - the device offset temp for regulation is %.2f - internal temp is %.2f. Nes target is %.2f",
|
||||||
|
self,
|
||||||
|
offset_temp,
|
||||||
|
device_temp,
|
||||||
|
target_temp,
|
||||||
|
)
|
||||||
|
|
||||||
await under.set_temperature(
|
await under.set_temperature(
|
||||||
self.regulated_target_temp, self._attr_max_temp, self._attr_min_temp
|
target_temp,
|
||||||
|
self._attr_max_temp,
|
||||||
|
self._attr_min_temp,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _send_auto_fan_mode(self):
|
async def _send_auto_fan_mode(self):
|
||||||
@@ -284,6 +323,10 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
else CONF_AUTO_FAN_NONE
|
else CONF_AUTO_FAN_NONE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._auto_regulation_use_device_temp = config_entry.get(
|
||||||
|
CONF_AUTO_REGULATION_USE_DEVICE_TEMP, False
|
||||||
|
)
|
||||||
|
|
||||||
def choose_auto_regulation_mode(self, auto_regulation_mode: str):
|
def choose_auto_regulation_mode(self, auto_regulation_mode: str):
|
||||||
"""Choose or change the regulation mode"""
|
"""Choose or change the regulation mode"""
|
||||||
self._auto_regulation_mode = auto_regulation_mode
|
self._auto_regulation_mode = auto_regulation_mode
|
||||||
@@ -492,6 +535,10 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
"auto_deactivated_fan_mode"
|
"auto_deactivated_fan_mode"
|
||||||
] = self._auto_deactivated_fan_mode
|
] = self._auto_deactivated_fan_mode
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"auto_regulation_use_device_temp"
|
||||||
|
] = self.auto_regulation_use_device_temp
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - Calling update_custom_attributes: %s",
|
"%s - Calling update_custom_attributes: %s",
|
||||||
@@ -770,6 +817,11 @@ class ThermostatOverClimate(BaseThermostat):
|
|||||||
"""Get the auto fan mode"""
|
"""Get the auto fan mode"""
|
||||||
return self._auto_fan_mode
|
return self._auto_fan_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auto_regulation_use_device_temp(self) -> bool | None:
|
||||||
|
"""Returns the value of parameter auto_regulation_use_device_temp"""
|
||||||
|
return self._auto_regulation_use_device_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def regulated_target_temp(self) -> float | None:
|
def regulated_target_temp(self) -> float | None:
|
||||||
"""Get the regulated target temperature"""
|
"""Get the regulated target temperature"""
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"auto_regulation_mode": "Self-regulation",
|
"auto_regulation_mode": "Self-regulation",
|
||||||
"auto_regulation_dtemp": "Regulation threshold",
|
"auto_regulation_dtemp": "Regulation threshold",
|
||||||
"auto_regulation_periode_min": "Regulation minimal period",
|
"auto_regulation_periode_min": "Regulation minimal period",
|
||||||
|
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
|
||||||
"inverse_switch_command": "Inverse switch command",
|
"inverse_switch_command": "Inverse switch command",
|
||||||
"auto_fan_mode": " Auto fan mode"
|
"auto_fan_mode": " Auto fan mode"
|
||||||
},
|
},
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) 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",
|
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||||
|
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation",
|
||||||
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
|
"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"
|
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
|
||||||
}
|
}
|
||||||
@@ -294,6 +296,7 @@
|
|||||||
"auto_regulation_mode": "Self-regulation",
|
"auto_regulation_mode": "Self-regulation",
|
||||||
"auto_regulation_dtemp": "Regulation threshold",
|
"auto_regulation_dtemp": "Regulation threshold",
|
||||||
"auto_regulation_periode_min": "Regulation minimal period",
|
"auto_regulation_periode_min": "Regulation minimal period",
|
||||||
|
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
|
||||||
"inverse_switch_command": "Inverse switch command",
|
"inverse_switch_command": "Inverse switch command",
|
||||||
"auto_fan_mode": " Auto fan mode"
|
"auto_fan_mode": " Auto fan mode"
|
||||||
},
|
},
|
||||||
@@ -315,6 +318,7 @@
|
|||||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) 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",
|
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||||
|
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation",
|
||||||
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
|
"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"
|
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
"auto_regulation_mode": "Auto-régulation",
|
"auto_regulation_mode": "Auto-régulation",
|
||||||
"auto_regulation_dtemp": "Seuil de régulation",
|
"auto_regulation_dtemp": "Seuil de régulation",
|
||||||
"auto_regulation_periode_min": "Période minimale de régulation",
|
"auto_regulation_periode_min": "Période minimale de régulation",
|
||||||
|
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent",
|
||||||
"inverse_switch_command": "Inverser la commande",
|
"inverse_switch_command": "Inverser la commande",
|
||||||
"auto_fan_mode": " Auto ventilation mode"
|
"auto_fan_mode": " Auto ventilation mode"
|
||||||
},
|
},
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
"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 ° (ou % pour les valves) en-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",
|
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
||||||
|
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
||||||
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
|
"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"
|
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
||||||
}
|
}
|
||||||
@@ -306,6 +308,7 @@
|
|||||||
"auto_regulation_mode": "Auto-regulation",
|
"auto_regulation_mode": "Auto-regulation",
|
||||||
"auto_regulation_dtemp": "Seuil de régulation",
|
"auto_regulation_dtemp": "Seuil de régulation",
|
||||||
"auto_regulation_periode_min": "Période minimale de régulation",
|
"auto_regulation_periode_min": "Période minimale de régulation",
|
||||||
|
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent",
|
||||||
"inverse_switch_command": "Inverser la commande",
|
"inverse_switch_command": "Inverser la commande",
|
||||||
"auto_fan_mode": " Auto fan mode"
|
"auto_fan_mode": " Auto fan mode"
|
||||||
},
|
},
|
||||||
@@ -327,6 +330,7 @@
|
|||||||
"auto_regulation_mode": "Ajustement automatique de la consigne",
|
"auto_regulation_mode": "Ajustement automatique de la consigne",
|
||||||
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-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",
|
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
||||||
|
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
||||||
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
|
"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"
|
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -567,6 +567,7 @@ class UnderlyingClimate(UnderlyingEntity):
|
|||||||
"""Set the target temperature"""
|
"""Set the target temperature"""
|
||||||
if not self.is_initialized:
|
if not self.is_initialized:
|
||||||
return
|
return
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
ATTR_ENTITY_ID: self._entity_id,
|
ATTR_ENTITY_ID: self._entity_id,
|
||||||
"temperature": self.cap_sent_value(temperature),
|
"temperature": self.cap_sent_value(temperature),
|
||||||
@@ -671,6 +672,18 @@ class UnderlyingClimate(UnderlyingEntity):
|
|||||||
return False
|
return False
|
||||||
return self._underlying_climate.is_aux_heat
|
return self._underlying_climate.is_aux_heat
|
||||||
|
|
||||||
|
@property
|
||||||
|
def underlying_current_temperature(self) -> float | None:
|
||||||
|
"""Get the underlying current_temperature if it exists
|
||||||
|
and if initialized"""
|
||||||
|
if not self.is_initialized:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not hasattr(self._underlying_climate, "current_temperature"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self._underlying_climate.current_temperature
|
||||||
|
|
||||||
def turn_aux_heat_on(self) -> None:
|
def turn_aux_heat_on(self) -> None:
|
||||||
"""Turn auxiliary heater on."""
|
"""Turn auxiliary heater on."""
|
||||||
if not self.is_initialized:
|
if not self.is_initialized:
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ from .const import ( # pylint: disable=unused-import
|
|||||||
MOCK_TH_OVER_CLIMATE_MAIN_CONFIG,
|
MOCK_TH_OVER_CLIMATE_MAIN_CONFIG,
|
||||||
MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG,
|
MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG,
|
||||||
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG,
|
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG,
|
||||||
|
MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG,
|
||||||
MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG,
|
MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG,
|
||||||
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG,
|
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG,
|
||||||
MOCK_TH_OVER_SWITCH_TPI_CONFIG,
|
MOCK_TH_OVER_SWITCH_TPI_CONFIG,
|
||||||
@@ -110,6 +111,15 @@ PARTIAL_CLIMATE_CONFIG = (
|
|||||||
| MOCK_ADVANCED_CONFIG
|
| MOCK_ADVANCED_CONFIG
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP = (
|
||||||
|
MOCK_TH_OVER_CLIMATE_USER_CONFIG
|
||||||
|
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
|
||||||
|
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
|
||||||
|
| MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG
|
||||||
|
| MOCK_PRESETS_CONFIG
|
||||||
|
| MOCK_ADVANCED_CONFIG
|
||||||
|
)
|
||||||
|
|
||||||
PARTIAL_CLIMATE_NOT_REGULATED_CONFIG = (
|
PARTIAL_CLIMATE_NOT_REGULATED_CONFIG = (
|
||||||
MOCK_TH_OVER_CLIMATE_USER_CONFIG
|
MOCK_TH_OVER_CLIMATE_USER_CONFIG
|
||||||
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
|
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
|
||||||
@@ -313,6 +323,10 @@ class MockClimate(ClimateEntity):
|
|||||||
"""Set the HVACaction"""
|
"""Set the HVACaction"""
|
||||||
self._attr_hvac_action = hvac_action
|
self._attr_hvac_action = hvac_action
|
||||||
|
|
||||||
|
def set_current_temperature(self, current_temperature):
|
||||||
|
"""Set the current_temperature"""
|
||||||
|
self._attr_current_temperature = current_temperature
|
||||||
|
|
||||||
|
|
||||||
class MockUnavailableClimate(ClimateEntity):
|
class MockUnavailableClimate(ClimateEntity):
|
||||||
"""A Mock Climate class used for Underlying climate mode"""
|
"""A Mock Climate class used for Underlying climate mode"""
|
||||||
|
|||||||
@@ -108,6 +108,17 @@ MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
|
|||||||
CONF_AUTO_REGULATION_DTEMP: 0.5,
|
CONF_AUTO_REGULATION_DTEMP: 0.5,
|
||||||
CONF_AUTO_REGULATION_PERIOD_MIN: 2,
|
CONF_AUTO_REGULATION_PERIOD_MIN: 2,
|
||||||
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH,
|
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH,
|
||||||
|
CONF_AUTO_REGULATION_USE_DEVICE_TEMP: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG = {
|
||||||
|
CONF_CLIMATE: "climate.mock_climate",
|
||||||
|
CONF_AC_MODE: False,
|
||||||
|
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
||||||
|
CONF_AUTO_REGULATION_DTEMP: 0.1,
|
||||||
|
CONF_AUTO_REGULATION_PERIOD_MIN: 2,
|
||||||
|
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH,
|
||||||
|
CONF_AUTO_REGULATION_USE_DEVICE_TEMP: True,
|
||||||
}
|
}
|
||||||
|
|
||||||
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG = {
|
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
|
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
|
||||||
|
|
||||||
""" Test the normal start of a Thermostat """
|
""" Test the normal start of a Thermostat """
|
||||||
from unittest.mock import patch # , call
|
from unittest.mock import patch, call
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -71,6 +71,7 @@ async def test_over_climate_regulation(
|
|||||||
assert entity.name == "TheOverClimateMockName"
|
assert entity.name == "TheOverClimateMockName"
|
||||||
assert entity.is_over_climate is True
|
assert entity.is_over_climate is True
|
||||||
assert entity.is_regulated is True
|
assert entity.is_regulated is True
|
||||||
|
assert entity.auto_regulation_use_device_temp is False
|
||||||
assert entity.hvac_mode is HVACMode.OFF
|
assert entity.hvac_mode is HVACMode.OFF
|
||||||
assert entity.hvac_action is HVACAction.OFF
|
assert entity.hvac_action is HVACAction.OFF
|
||||||
assert entity.target_temperature == entity.min_temp
|
assert entity.target_temperature == entity.min_temp
|
||||||
@@ -374,3 +375,168 @@ async def test_over_climate_regulation_limitations(
|
|||||||
assert (
|
assert (
|
||||||
entity.regulated_target_temp == 17 + 1.5
|
entity.regulated_target_temp == 17 + 1.5
|
||||||
) # 0.7 without round_to_nearest
|
) # 0.7 without round_to_nearest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
|
async def test_over_climate_regulation_use_device_temp(
|
||||||
|
hass: HomeAssistant, skip_hass_states_is_state, skip_send_event
|
||||||
|
):
|
||||||
|
"""Test the regulation of an over climate thermostat"""
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="TheOverClimateMockName",
|
||||||
|
unique_id="uniqueId",
|
||||||
|
# This is include a medium regulation
|
||||||
|
data=PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP,
|
||||||
|
)
|
||||||
|
|
||||||
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
|
now: datetime = datetime.now(tz=tz)
|
||||||
|
|
||||||
|
fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {})
|
||||||
|
|
||||||
|
assert fake_underlying_climate.current_temperature == 15
|
||||||
|
|
||||||
|
# Creates the regulated VTherm over climate
|
||||||
|
# change temperature so that the heating will start
|
||||||
|
event_timestamp = now - timedelta(minutes=10)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||||
|
return_value=event_timestamp,
|
||||||
|
), patch(
|
||||||
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
|
return_value=fake_underlying_climate,
|
||||||
|
):
|
||||||
|
entity: ThermostatOverClimate = await create_thermostat(
|
||||||
|
hass, entry, "climate.theoverclimatemockname"
|
||||||
|
)
|
||||||
|
assert entity
|
||||||
|
assert isinstance(entity, ThermostatOverClimate)
|
||||||
|
|
||||||
|
assert entity.name == "TheOverClimateMockName"
|
||||||
|
assert entity.is_over_climate is True
|
||||||
|
assert entity.is_regulated is True
|
||||||
|
assert entity.auto_regulation_use_device_temp is True
|
||||||
|
|
||||||
|
# 1. Activate the heating by changing HVACMode and temperature
|
||||||
|
# Select a hvacmode, presence and preset
|
||||||
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
|
assert entity.hvac_mode is HVACMode.HEAT
|
||||||
|
assert entity.regulated_target_temp == entity.min_temp
|
||||||
|
|
||||||
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||||
|
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
||||||
|
|
||||||
|
# 2. set manual target temp (at now - 7) -> no regulation should occurs
|
||||||
|
# room temp is 18
|
||||||
|
# target is 16
|
||||||
|
# internal heater temp is 15
|
||||||
|
fake_underlying_climate.set_current_temperature(15)
|
||||||
|
event_timestamp = now - timedelta(minutes=7)
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||||
|
return_value=event_timestamp,
|
||||||
|
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
|
await entity.async_set_temperature(temperature=16)
|
||||||
|
|
||||||
|
fake_underlying_climate.set_hvac_action(
|
||||||
|
HVACAction.HEATING
|
||||||
|
) # simulate under heating
|
||||||
|
assert entity.hvac_action == HVACAction.HEATING
|
||||||
|
assert entity.preset_mode == PRESET_NONE # Manual mode
|
||||||
|
|
||||||
|
# the regulated temperature should be higher
|
||||||
|
assert entity.regulated_target_temp < entity.target_temperature
|
||||||
|
# The calcul is the following: 16 + (16 - 18) x 0.4 (strong) + 0 x ki - 1 (device offset)
|
||||||
|
assert (
|
||||||
|
entity.regulated_target_temp == 15
|
||||||
|
) # round(16 + (16 - 18) * 0.4 + 0 * 0.08)
|
||||||
|
assert entity.hvac_action == HVACAction.HEATING
|
||||||
|
|
||||||
|
mock_service_call.assert_has_calls(
|
||||||
|
[
|
||||||
|
call.service_call(
|
||||||
|
"climate",
|
||||||
|
"set_temperature",
|
||||||
|
{
|
||||||
|
"entity_id": "climate.mock_climate",
|
||||||
|
# because device offset is -3 but not used because target is reach
|
||||||
|
"temperature": 15.0,
|
||||||
|
"target_temp_high": 30,
|
||||||
|
"target_temp_low": 15,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. change temperature so that the regulated temperature should slow down
|
||||||
|
# room temp is 15
|
||||||
|
# target is 18
|
||||||
|
# internal heater temp is 13
|
||||||
|
fake_underlying_climate.set_current_temperature(13)
|
||||||
|
await entity.async_set_temperature(temperature=18)
|
||||||
|
await send_ext_temperature_change_event(entity, 9, event_timestamp)
|
||||||
|
|
||||||
|
event_timestamp = now - timedelta(minutes=5)
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||||
|
return_value=event_timestamp,
|
||||||
|
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
|
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||||
|
|
||||||
|
# the regulated temperature should be under (device offset is -2)
|
||||||
|
assert entity.regulated_target_temp > entity.target_temperature
|
||||||
|
assert entity.regulated_target_temp == 19.4 # 18 + 1.4
|
||||||
|
|
||||||
|
mock_service_call.assert_has_calls(
|
||||||
|
[
|
||||||
|
call.service_call(
|
||||||
|
"climate",
|
||||||
|
"set_temperature",
|
||||||
|
{
|
||||||
|
"entity_id": "climate.mock_climate",
|
||||||
|
"temperature": 21.4, # 19.4 + 2
|
||||||
|
"target_temp_high": 30,
|
||||||
|
"target_temp_low": 15,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. In cool mode
|
||||||
|
# room temp is 25
|
||||||
|
# target is 23
|
||||||
|
# internal heater temp is 27
|
||||||
|
await entity.async_set_hvac_mode(HVACMode.COOL)
|
||||||
|
await entity.async_set_temperature(temperature=23)
|
||||||
|
fake_underlying_climate.set_current_temperature(27)
|
||||||
|
await send_ext_temperature_change_event(entity, 30, event_timestamp)
|
||||||
|
|
||||||
|
event_timestamp = now - timedelta(minutes=3)
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||||
|
return_value=event_timestamp,
|
||||||
|
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
|
await send_temperature_change_event(entity, 25, event_timestamp)
|
||||||
|
|
||||||
|
# the regulated temperature should be upper (device offset is +2)
|
||||||
|
assert entity.regulated_target_temp < entity.target_temperature
|
||||||
|
assert entity.regulated_target_temp == 22.9
|
||||||
|
|
||||||
|
mock_service_call.assert_has_calls(
|
||||||
|
[
|
||||||
|
call.service_call(
|
||||||
|
"climate",
|
||||||
|
"set_temperature",
|
||||||
|
{
|
||||||
|
"entity_id": "climate.mock_climate",
|
||||||
|
"temperature": 24.9, # 22.9 + 2° of offset
|
||||||
|
"target_temp_high": 30,
|
||||||
|
"target_temp_low": 15,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user