Compare commits
29 Commits
7.1.2
...
7.2.0beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43904713ba | ||
|
|
003acfea26 | ||
|
|
c5bbeef217 | ||
|
|
ae94c21e8e | ||
|
|
8cfeb58608 | ||
|
|
f794dd37ca | ||
|
|
4b4a0f80ba | ||
|
|
2fcb22b1eb | ||
|
|
ebc2d8b6ac | ||
|
|
cf4cc32b13 | ||
|
|
2523fc74c2 | ||
|
|
87de91c2c5 | ||
|
|
a3b6f66f1b | ||
|
|
8cf09e5254 | ||
|
|
0b67226666 | ||
|
|
812bb19e10 | ||
|
|
be3012af71 | ||
|
|
05fe2055e2 | ||
|
|
8743872c09 | ||
|
|
c4a1631d29 | ||
|
|
d1ef83f422 | ||
|
|
42ac2b0f98 | ||
|
|
e71d8dba86 | ||
|
|
3e96247b63 | ||
|
|
05e31358a4 | ||
|
|
3af0318c2f | ||
|
|
12b67ba3e0 | ||
|
|
1d675f22c7 | ||
|
|
3fd9ffe93d |
@@ -8,12 +8,13 @@ recorder:
|
||||
domains:
|
||||
- input_boolean
|
||||
- input_number
|
||||
- input_select
|
||||
- switch
|
||||
- climate
|
||||
- sensor
|
||||
- binary_sensor
|
||||
- number
|
||||
- input_select
|
||||
- select
|
||||
- versatile_thermostat
|
||||
|
||||
logger:
|
||||
@@ -243,6 +244,11 @@ climate:
|
||||
heater: input_boolean.fake_valve_sonoff_trvzb2
|
||||
target_sensor: input_number.fake_temperature_sensor1
|
||||
ac_mode: false
|
||||
- platform: generic_thermostat
|
||||
name: Underlying switch climate
|
||||
heater: input_boolean.fake_heater_switch2
|
||||
target_sensor: input_number.fake_temperature_sensor1
|
||||
ac_mode: false
|
||||
|
||||
input_datetime:
|
||||
fake_last_seen:
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
7
.github/ISSUE_TEMPLATE/issue.md
vendored
7
.github/ISSUE_TEMPLATE/issue.md
vendored
@@ -4,7 +4,10 @@ about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
> Please read carefuly this instructions and fill this form before writing an issue. It helps me to help you.
|
||||
# Read this carefully
|
||||
|
||||
> Please read carefully this instructions and fill this form before writing an issue. It helps me to help you.
|
||||
> If you choose to not follow this template, you accept to have no answer from the author. The tag on the issue 'Template not respected' means you don't respect this template. Potentially, you will not have a relevant answer.
|
||||
|
||||
<!-- This template will allow the maintainer to be efficient and post the more accurante response as possible. There is many types / modes / configuration possible, so the analysis can be very tricky. If don't follow this template, your issue could be rejected without any message. Please help me to help you. -->
|
||||
|
||||
@@ -12,7 +15,7 @@ about: Create a report to help us improve
|
||||
|
||||
If you have a simple question or you are not sure this is an issue, don't open an issue but open a new discussion [here](https://github.com/jmcollin78/versatile_thermostat/discussions).
|
||||
|
||||
Check also in the [Troubleshooting](#troubleshooting) paragrah of the README if the aswer is not already given.
|
||||
Check also in the [Troubleshooting] paragrah of the README if the aswer is not already given.
|
||||
|
||||
Issues not containing the minimum requirements will be closed:
|
||||
|
||||
|
||||
16
README-fr.md
16
README-fr.md
@@ -13,15 +13,11 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et une ré
|
||||
|
||||
# Quoi de neuf ?
|
||||

|
||||
> * **Release 6.8**:
|
||||
> * **Release 7.2**:
|
||||
>
|
||||
> Ajout d'une nouvelle méthode de régulation pour les Versatile Thermostat de type `over_climate`. Cette méthode nommée 'Contrôle direct de la vanne' permet de contrôler directement la vanne d'un TRV et éventuellement un décalage pour calibrer le thermomètre interne de votre TRV. Cette nouvelle méthode a été testée avec des Sonoff TRVZB et généralisée pour d'autre type de TRV pour lesquels la vanne est directement commandable via des entités de type `number`.
|
||||
> - Prise en compte native des équipements pilotable via une entité de type `select` (ou `input_select`) ou `climate` pour des _VTherm_ de type `over_switch`. Cette évolution rend obsolète, la création de switch virtuels pour l'intégration des Nodon ou Heaty ou eCosy ... etc. Plus d'informations [ici](documentation/fr/over-switch.md#la-personnalisation-des-commandes).
|
||||
>
|
||||
> Plus d'informations [ici](documentation/fr/over-climate.md) et [ici](documentation/fr/self-regulation.md).
|
||||
>
|
||||
> * **Refonte de la documentation**:
|
||||
>
|
||||
> Avec toutes les évolutions réalisées depuis le début de l'intégration, la documentation nécessitait une profonde re-organisation, c'est chose faite sur cette version. Tous vos retours sur cette nouvelle organisation seront les bienvenus.
|
||||
> - Lien vers la documentation : cette version 7.2 expérimente des liens vers la documentation depuis les pages de configuration. Le lien est accessible via l'icone [](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/over-switch.md#configuration). Elle est expérimentée sur la page de configuration des sous-jacents des _VTherm_ `over_switch`.
|
||||
|
||||
|
||||
# 🍻 Merci pour les bières [buymecoffee](https://www.buymeacoffee.com/jmcollin78) 🍻
|
||||
@@ -35,6 +31,12 @@ Un grand merci à tous mes fournisseurs de bières pour leurs dons et leurs enco
|
||||
|
||||
_AC_ : Air conditionné. Un équipement est AC si il fait du froid. Les températures sont alors inversées : Eco est plus chaud que Confort qui est plus chaud que Boost. Les algorithmes tiennent compte de cette information.
|
||||
|
||||
_EMA_ : Exponential Moving Average. Utilisé pour lisser les mesures de températures de capteur. Elle correspond à une moyenne glissante de la température de la pièce. Elle est utilisée pour calculer la pente de la courbe de température (slope) qui serait trop instable sur la courbe brute.
|
||||
|
||||
_slope_ : la pente de la courbe de température. Elle est mesurée en °(C ou K)/h. Elle est positive si la température augmente et négative si elle diminue. Cette pente est calculée sur l'_EMA_
|
||||
|
||||
_PAC_ : Pompe à chaleur
|
||||
|
||||
# Documentation
|
||||
|
||||
La documentation est maintenant découpée en plusieurs pages pour faciliter la lecture et la recherche d'informations :
|
||||
|
||||
@@ -34,6 +34,12 @@ A big thank you to all my beer sponsors for their donations and encouragements.
|
||||
|
||||
_AC_: Air Conditioning. An AC device cools instead of heats. Temperatures are reversed: Eco is warmer than Comfort, which is warmer than Boost. The algorithms take this information into account.
|
||||
|
||||
_EMA_: Exponential Moving Average. Used to smooth sensor temperature measurements. It represents a moving average of the room's temperature and is used to calculate the slope of the temperature curve, which would be too unstable on the raw data.
|
||||
|
||||
_slope_: The slope of the temperature curve, measured in ° (C or K)/h. It is positive when the temperature increases and negative when it decreases. This slope is calculated based on the _EMA_.
|
||||
|
||||
_PAC_ : Heat pump
|
||||
|
||||
# Documentation
|
||||
|
||||
The documentation is now divided into several pages for easier reading and searching:
|
||||
|
||||
@@ -1031,6 +1031,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
|
||||
return
|
||||
|
||||
# Remove eventual overpoering if we want to turn-off
|
||||
if hvac_mode == HVACMode.OFF and self.power_manager.is_overpowering_detected:
|
||||
await self.power_manager.set_overpowering(False)
|
||||
|
||||
self._hvac_mode = hvac_mode
|
||||
|
||||
# Delegate to all underlying
|
||||
@@ -1046,7 +1050,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
self._hvac_mode in [HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL]
|
||||
and self.preset_mode != PRESET_NONE
|
||||
):
|
||||
if self.preset_mode != PRESET_FROST_PROTECTION:
|
||||
if self.preset_mode != PRESET_FROST_PROTECTION or self._hvac_mode in [HVACMode.HEAT, HVACMode.HEAT_COOL]:
|
||||
await self.async_set_preset_mode_internal(self.preset_mode, True)
|
||||
else:
|
||||
await self.async_set_preset_mode_internal(PRESET_ECO, True, False)
|
||||
@@ -1063,9 +1067,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
save_state()
|
||||
|
||||
@overrides
|
||||
async def async_set_preset_mode(
|
||||
self, preset_mode: str, overwrite_saved_preset=True
|
||||
):
|
||||
async def async_set_preset_mode(self, preset_mode: str, overwrite_saved_preset=True):
|
||||
"""Set new preset mode."""
|
||||
|
||||
# We accept a new preset when:
|
||||
@@ -1093,14 +1095,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
|
||||
return
|
||||
|
||||
await self.async_set_preset_mode_internal(
|
||||
preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset
|
||||
)
|
||||
await self.async_set_preset_mode_internal(preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset)
|
||||
await self.async_control_heating(force=True)
|
||||
|
||||
async def async_set_preset_mode_internal(
|
||||
self, preset_mode: str, force=False, overwrite_saved_preset=True
|
||||
):
|
||||
async def async_set_preset_mode_internal(self, preset_mode: str, force=False, overwrite_saved_preset=True):
|
||||
"""Set new preset mode."""
|
||||
_LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force)
|
||||
if (
|
||||
@@ -1246,12 +1244,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
|
||||
async def async_set_humidity(self, humidity: int):
|
||||
"""Set new target humidity."""
|
||||
_LOGGER.info("%s - Set fan mode: %s", self, humidity)
|
||||
_LOGGER.info("%s - Set humidity: %s", self, humidity)
|
||||
return
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode: str):
|
||||
"""Set new target swing operation."""
|
||||
_LOGGER.info("%s - Set fan mode: %s", self, swing_mode)
|
||||
_LOGGER.info("%s - Set swing mode: %s", self, swing_mode)
|
||||
return
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
@@ -1530,8 +1528,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
is_window_detected = self._window_manager.is_window_detected
|
||||
if new_central_mode == CENTRAL_MODE_AUTO:
|
||||
if not is_window_detected and not first_init:
|
||||
await self.restore_hvac_mode()
|
||||
await self.restore_preset_mode()
|
||||
await self.restore_preset_mode(force=False)
|
||||
await self.restore_hvac_mode(need_control_heating=True)
|
||||
elif is_window_detected and self.hvac_mode == HVACMode.OFF:
|
||||
# do not restore but mark the reason of off with window detection
|
||||
self.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
|
||||
@@ -1569,9 +1567,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
and HVACMode.HEAT in self.hvac_modes
|
||||
):
|
||||
await self.async_set_hvac_mode(HVACMode.HEAT)
|
||||
await self.async_set_preset_mode(
|
||||
PRESET_FROST_PROTECTION, overwrite_saved_preset=False
|
||||
)
|
||||
await self.async_set_preset_mode(PRESET_FROST_PROTECTION, overwrite_saved_preset=False)
|
||||
else:
|
||||
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
|
||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||
@@ -1796,9 +1792,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
# If the changed preset is active, change the current temperature
|
||||
# Issue #119 - reload new preset temperature also in ac mode
|
||||
if preset.startswith(self._attr_preset_mode):
|
||||
await self.async_set_preset_mode_internal(
|
||||
preset.rstrip(PRESET_AC_SUFFIX), force=True
|
||||
)
|
||||
await self.async_set_preset_mode_internal(preset.rstrip(PRESET_AC_SUFFIX), force=True)
|
||||
await self.async_control_heating(force=True)
|
||||
|
||||
async def SERVICE_SET_SAFETY(
|
||||
|
||||
@@ -47,6 +47,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
self._current_max_power: float = None
|
||||
self._power_temp: float = None
|
||||
self._cancel_calculate_shedding_call = None
|
||||
self._started_vtherm_total_power: float = None
|
||||
# Not used now
|
||||
self._last_shedding_date = None
|
||||
|
||||
@@ -71,6 +72,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
and self._power_temp
|
||||
):
|
||||
self._is_configured = True
|
||||
self._started_vtherm_total_power = 0
|
||||
else:
|
||||
_LOGGER.info("Power management is not fully configured and will be deactivated")
|
||||
|
||||
@@ -102,6 +104,8 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
"""Handle power changes."""
|
||||
_LOGGER.debug("Receive new Power event")
|
||||
_LOGGER.debug(event)
|
||||
|
||||
self._started_vtherm_total_power = 0
|
||||
await self.refresh_state()
|
||||
|
||||
@callback
|
||||
@@ -275,6 +279,12 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
vtherms.sort(key=cmp_to_key(cmp_temps))
|
||||
return vtherms
|
||||
|
||||
def add_started_vtherm_total_power(self, started_power: float):
|
||||
"""Add the power into the _started_vtherm_total_power which holds all VTherm started after
|
||||
the last power measurement"""
|
||||
self._started_vtherm_total_power += started_power
|
||||
_LOGGER.debug("%s - started_vtherm_total_power is now %s", self, self._started_vtherm_total_power)
|
||||
|
||||
@property
|
||||
def is_configured(self) -> bool:
|
||||
"""True if the FeatureManager is fully configured"""
|
||||
@@ -305,5 +315,10 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
"""Return the max power sensor entity id"""
|
||||
return self._max_power_sensor_entity_id
|
||||
|
||||
@property
|
||||
def started_vtherm_total_power(self) -> float | None:
|
||||
"""Return the started_vtherm_total_power"""
|
||||
return self._started_vtherm_total_power
|
||||
|
||||
def __str__(self):
|
||||
return "CentralPowerManager"
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
import re
|
||||
import logging
|
||||
import copy
|
||||
from collections.abc import Mapping # pylint: disable=import-error
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
@@ -273,6 +272,34 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
CONF_MIN_OPENING_DEGREES
|
||||
) from exc
|
||||
|
||||
# Check the VSWITCH configuration. There should be the same number of vswitch_on (resp. vswitch_off) than the number of underlying entity
|
||||
if self._infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_SWITCH and step_id == "type":
|
||||
if not self.check_vswitch_configuration(data):
|
||||
raise VirtualSwitchConfigurationIncorrect(CONF_VSWITCH_ON_CMD_LIST)
|
||||
|
||||
def check_vswitch_configuration(self, data) -> bool:
|
||||
"""Check the Virtual switch configuration and return True if the configuration is correct"""
|
||||
nb_under = len(data.get(CONF_UNDERLYING_LIST, []))
|
||||
|
||||
# check format of each command_on
|
||||
for command in data.get(CONF_VSWITCH_ON_CMD_LIST, []) + data.get(CONF_VSWITCH_OFF_CMD_LIST, []):
|
||||
pattern = r"^(?P<command>[a-zA-Z0-9_]+)(?:/(?P<argument>[a-zA-Z0-9_]+)(?::(?P<value>[a-zA-Z0-9_]+))?)?$"
|
||||
if not re.match(pattern, command):
|
||||
return False
|
||||
|
||||
nb_command_on = len(data.get(CONF_VSWITCH_ON_CMD_LIST, []))
|
||||
nb_command_off = len(data.get(CONF_VSWITCH_OFF_CMD_LIST, []))
|
||||
if (nb_command_on == nb_under or nb_command_on == 0) and (nb_command_off == nb_under or nb_command_off == 0):
|
||||
# There is enough command_on and off
|
||||
# Check if one under is not a switch (which have default command).
|
||||
if any(
|
||||
not thermostat_type.startswith(SWITCH_DOMAIN) and not thermostat_type.startswith(INPUT_BOOLEAN_DOMAIN) for thermostat_type in data.get(CONF_UNDERLYING_LIST, [])
|
||||
):
|
||||
if nb_command_on != nb_under or nb_command_off != nb_under:
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_config_complete(self, infos) -> bool:
|
||||
"""True if the config is now complete (ie all mandatory attributes are set)"""
|
||||
is_central_config = (
|
||||
@@ -318,9 +345,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
):
|
||||
return False
|
||||
|
||||
if infos.get(CONF_UNDERLYING_LIST, None) is not None and not infos.get(
|
||||
CONF_UNDERLYING_LIST, None
|
||||
):
|
||||
# checks that at least one underlying is set but not it central configuration
|
||||
if len(infos.get(CONF_UNDERLYING_LIST, [])) < 1:
|
||||
return False
|
||||
|
||||
if (
|
||||
@@ -408,6 +434,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
errors["base"] = "valve_regulation_nb_entities_incorrect"
|
||||
except ValveRegulationMinOpeningDegreesIncorrect as err:
|
||||
errors[str(err)] = "min_opening_degrees_format"
|
||||
except VirtualSwitchConfigurationIncorrect as err:
|
||||
errors["base"] = "vswitch_configuration_incorrect"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
@@ -901,10 +929,42 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
||||
return await self.generic_step("advanced", schema, user_input, next_step)
|
||||
|
||||
async def async_step_finalize(self, _):
|
||||
"""Should be implemented by Leaf classes"""
|
||||
raise HomeAssistantError(
|
||||
"async_finalize not implemented on VersatileThermostat sub-class"
|
||||
)
|
||||
"""Finalize the creation. Should be overriden by underlyings"""
|
||||
if not self._infos[CONF_USE_WINDOW_FEATURE]:
|
||||
self._infos[CONF_USE_WINDOW_CENTRAL_CONFIG] = False
|
||||
if CONF_WINDOW_SENSOR in self._infos:
|
||||
del self._infos[CONF_WINDOW_SENSOR]
|
||||
if CONF_WINDOW_AUTO_CLOSE_THRESHOLD in self._infos:
|
||||
del self._infos[CONF_WINDOW_AUTO_CLOSE_THRESHOLD]
|
||||
if CONF_WINDOW_AUTO_OPEN_THRESHOLD in self._infos:
|
||||
del self._infos[CONF_WINDOW_AUTO_OPEN_THRESHOLD]
|
||||
if CONF_WINDOW_AUTO_MAX_DURATION in self._infos:
|
||||
del self._infos[CONF_WINDOW_AUTO_MAX_DURATION]
|
||||
if not self._infos[CONF_USE_MOTION_FEATURE]:
|
||||
self._infos[CONF_USE_MOTION_CENTRAL_CONFIG] = False
|
||||
if CONF_MOTION_SENSOR in self._infos:
|
||||
del self._infos[CONF_MOTION_SENSOR]
|
||||
if not self._infos[CONF_USE_POWER_FEATURE]:
|
||||
self._infos[CONF_USE_POWER_CENTRAL_CONFIG] = False
|
||||
if CONF_POWER_SENSOR in self._infos:
|
||||
del self._infos[CONF_POWER_SENSOR]
|
||||
if CONF_MAX_POWER_SENSOR in self._infos:
|
||||
del self._infos[CONF_MAX_POWER_SENSOR]
|
||||
if not self._infos[CONF_USE_PRESENCE_FEATURE]:
|
||||
self._infos[CONF_USE_PRESENCE_CENTRAL_CONFIG] = False
|
||||
if CONF_PRESENCE_SENSOR in self._infos:
|
||||
del self._infos[CONF_PRESENCE_SENSOR]
|
||||
if not self._infos[CONF_USE_CENTRAL_BOILER_FEATURE]:
|
||||
if CONF_CENTRAL_BOILER_ACTIVATION_SRV in self._infos:
|
||||
del self._infos[CONF_CENTRAL_BOILER_ACTIVATION_SRV]
|
||||
if CONF_CENTRAL_BOILER_DEACTIVATION_SRV in self._infos:
|
||||
del self._infos[CONF_CENTRAL_BOILER_DEACTIVATION_SRV]
|
||||
if not self._infos[CONF_USE_AUTO_START_STOP_FEATURE]:
|
||||
self._infos[CONF_AUTO_START_STOP_LEVEL] = AUTO_START_STOP_LEVEL_NONE
|
||||
|
||||
# Removes temporary value
|
||||
if COMES_FROM in self._infos:
|
||||
del self._infos[COMES_FROM]
|
||||
|
||||
|
||||
class VersatileThermostatConfigFlow( # pylint: disable=abstract-method
|
||||
@@ -928,9 +988,8 @@ class VersatileThermostatConfigFlow( # pylint: disable=abstract-method
|
||||
async def async_step_finalize(self, _):
|
||||
"""Finalization of the ConfigEntry creation"""
|
||||
_LOGGER.debug("ConfigFlow.async_finalize")
|
||||
# Removes temporary value
|
||||
if COMES_FROM in self._infos:
|
||||
del self._infos[COMES_FROM]
|
||||
await super().async_step_finalize(_)
|
||||
|
||||
return self.async_create_entry(title=self._infos[CONF_NAME], data=self._infos)
|
||||
|
||||
|
||||
@@ -968,37 +1027,13 @@ class VersatileThermostatOptionsFlowHandler(
|
||||
|
||||
async def async_step_finalize(self, _):
|
||||
"""Finalization of the ConfigEntry creation"""
|
||||
if not self._infos[CONF_USE_WINDOW_FEATURE]:
|
||||
self._infos[CONF_USE_WINDOW_CENTRAL_CONFIG] = False
|
||||
self._infos[CONF_WINDOW_SENSOR] = None
|
||||
self._infos[CONF_WINDOW_AUTO_CLOSE_THRESHOLD] = None
|
||||
self._infos[CONF_WINDOW_AUTO_OPEN_THRESHOLD] = None
|
||||
self._infos[CONF_WINDOW_AUTO_MAX_DURATION] = None
|
||||
if not self._infos[CONF_USE_MOTION_FEATURE]:
|
||||
self._infos[CONF_USE_MOTION_CENTRAL_CONFIG] = False
|
||||
self._infos[CONF_MOTION_SENSOR] = None
|
||||
if not self._infos[CONF_USE_POWER_FEATURE]:
|
||||
self._infos[CONF_USE_POWER_CENTRAL_CONFIG] = False
|
||||
self._infos[CONF_POWER_SENSOR] = None
|
||||
self._infos[CONF_MAX_POWER_SENSOR] = None
|
||||
if not self._infos[CONF_USE_PRESENCE_FEATURE]:
|
||||
self._infos[CONF_USE_PRESENCE_CENTRAL_CONFIG] = False
|
||||
self._infos[CONF_PRESENCE_SENSOR] = None
|
||||
if not self._infos[CONF_USE_CENTRAL_BOILER_FEATURE]:
|
||||
self._infos[CONF_CENTRAL_BOILER_ACTIVATION_SRV] = None
|
||||
self._infos[CONF_CENTRAL_BOILER_DEACTIVATION_SRV] = None
|
||||
if not self._infos[CONF_USE_AUTO_START_STOP_FEATURE]:
|
||||
self._infos[CONF_AUTO_START_STOP_LEVEL] = AUTO_START_STOP_LEVEL_NONE
|
||||
|
||||
_LOGGER.info(
|
||||
"Recreating entry %s due to configuration change. New config is now: %s",
|
||||
self.config_entry.entry_id,
|
||||
self._infos,
|
||||
)
|
||||
|
||||
# Removes temporary value
|
||||
if COMES_FROM in self._infos:
|
||||
del self._infos[COMES_FROM]
|
||||
await super().async_step_finalize(_)
|
||||
|
||||
self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos)
|
||||
return self.async_create_entry(title=None, data=None)
|
||||
|
||||
@@ -16,6 +16,14 @@ from homeassistant.components.input_number import (
|
||||
DOMAIN as INPUT_NUMBER_DOMAIN,
|
||||
)
|
||||
|
||||
from homeassistant.components.select import (
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
)
|
||||
|
||||
from homeassistant.components.input_select import (
|
||||
DOMAIN as INPUT_SELECT_DOMAIN,
|
||||
)
|
||||
|
||||
from homeassistant.components.input_datetime import (
|
||||
DOMAIN as INPUT_DATETIME_DOMAIN,
|
||||
)
|
||||
@@ -120,9 +128,7 @@ STEP_CENTRAL_BOILER_SCHEMA = vol.Schema(
|
||||
STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
|
||||
{
|
||||
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
|
||||
selector.EntitySelectorConfig(
|
||||
domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN], multiple=True
|
||||
),
|
||||
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN, SELECT_DOMAIN, INPUT_SELECT_DOMAIN, CLIMATE_DOMAIN], multiple=True),
|
||||
),
|
||||
vol.Optional(CONF_HEATER_KEEP_ALIVE): cv.positive_int,
|
||||
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
|
||||
@@ -132,6 +138,10 @@ STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
|
||||
),
|
||||
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
|
||||
vol.Optional(CONF_INVERSE_SWITCH, default=False): cv.boolean,
|
||||
vol.Optional("on_command_text"): vol.In([]),
|
||||
vol.Optional(CONF_VSWITCH_ON_CMD_LIST): selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiple=True)),
|
||||
vol.Optional("off_command_text"): vol.In([]),
|
||||
vol.Optional(CONF_VSWITCH_OFF_CMD_LIST): selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiple=True)),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -232,7 +242,7 @@ STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||
STEP_CENTRAL_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||
{
|
||||
vol.Required(CONF_TPI_COEF_INT, default=0.6): selector.NumberSelector(selector.NumberSelectorConfig(min=0.0, max=1.0, step=0.01, mode=selector.NumberSelectorMode.BOX)),
|
||||
vol.Required(CONF_TPI_COEF_EXT, default=0.01): selector.NumberSelector(selector.NumberSelectorConfig(min=0.0, max=1.0, mode=selector.NumberSelectorMode.BOX)),
|
||||
vol.Required(CONF_TPI_COEF_EXT, default=0.01): selector.NumberSelector(selector.NumberSelectorConfig(min=0.0, max=1.0, step=0.001, mode=selector.NumberSelectorMode.BOX)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -127,6 +127,9 @@ CONF_OPENING_DEGREE_LIST = "opening_degree_entity_ids"
|
||||
CONF_CLOSING_DEGREE_LIST = "closing_degree_entity_ids"
|
||||
CONF_MIN_OPENING_DEGREES = "min_opening_degrees"
|
||||
|
||||
CONF_VSWITCH_ON_CMD_LIST = "vswitch_on_command"
|
||||
CONF_VSWITCH_OFF_CMD_LIST = "vswitch_off_command"
|
||||
|
||||
# Deprecated
|
||||
CONF_HEATER = "heater_entity_id"
|
||||
CONF_HEATER_2 = "heater_entity2_id"
|
||||
@@ -562,6 +565,10 @@ class ValveRegulationMinOpeningDegreesIncorrect(HomeAssistantError):
|
||||
"""Error to indicate that the minimal opening degrees is not a list of int separated by coma"""
|
||||
|
||||
|
||||
class VirtualSwitchConfigurationIncorrect(HomeAssistantError):
|
||||
"""Error when a virtual switch is not configured correctly"""
|
||||
|
||||
|
||||
class overrides: # pylint: disable=invalid-name
|
||||
"""An annotation to inform overrides"""
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
|
||||
self._auto_start_stop_level, self.name
|
||||
)
|
||||
|
||||
# Fix an eventual incoherent state
|
||||
if self._vtherm.is_on and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP:
|
||||
self._vtherm.hvac_off_reason = None
|
||||
|
||||
@overrides
|
||||
async def start_listening(self):
|
||||
"""Start listening the underlying entity"""
|
||||
|
||||
@@ -104,7 +104,8 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
|
||||
async def check_power_available(self) -> bool:
|
||||
"""Check if the Vtherm can be started considering overpowering.
|
||||
Returns True if no overpowering conditions are found
|
||||
Returns True if no overpowering conditions are found.
|
||||
If True the vtherm power is written into the temporay vtherm started
|
||||
"""
|
||||
|
||||
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
|
||||
@@ -116,6 +117,7 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
|
||||
current_power = vtherm_api.central_power_manager.current_power
|
||||
current_max_power = vtherm_api.central_power_manager.current_max_power
|
||||
started_vtherm_total_power = vtherm_api.central_power_manager.started_vtherm_total_power
|
||||
if (
|
||||
current_power is None
|
||||
or current_max_power is None
|
||||
@@ -146,7 +148,7 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
self._device_power * self._vtherm.proportional_algorithm.on_percent,
|
||||
)
|
||||
|
||||
ret = (current_power + power_consumption_max) < current_max_power
|
||||
ret = (current_power + started_vtherm_total_power + power_consumption_max) < current_max_power
|
||||
if not ret:
|
||||
_LOGGER.info(
|
||||
"%s - there is not enough power available power=%.3f, max_power=%.3f heater power=%.3f",
|
||||
@@ -155,6 +157,10 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
current_max_power,
|
||||
self._device_power,
|
||||
)
|
||||
else:
|
||||
# Adds the current_power_max to the started vtherm total power
|
||||
vtherm_api.central_power_manager.add_started_vtherm_total_power(power_consumption_max)
|
||||
|
||||
return ret
|
||||
|
||||
async def set_overpowering(self, overpowering: bool, power_consumption_max=0):
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
"quality_scale": "silver",
|
||||
"requirements": [],
|
||||
"ssdp": [],
|
||||
"version": "7.1.2",
|
||||
"version": "7.2.0",
|
||||
"zeroconf": []
|
||||
}
|
||||
@@ -303,6 +303,10 @@ class ValveOpenPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
|
||||
"""Called when my climate have change"""
|
||||
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
|
||||
|
||||
if not self.my_climate or not hasattr(self.my_climate, "valve_open_percent"):
|
||||
_LOGGER.warning("%s - my_climate not found or no valve_open_percent property found. This could be normal at startup. Ignore the underlying device change.", self)
|
||||
return
|
||||
|
||||
old_state = self._attr_native_value
|
||||
self._attr_native_value = self.my_climate.valve_open_percent
|
||||
if old_state != self._attr_native_value:
|
||||
|
||||
@@ -82,7 +82,9 @@
|
||||
"auto_regulation_periode_min": "Regulation minimum period",
|
||||
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
|
||||
"inverse_switch_command": "Inverse switch command",
|
||||
"auto_fan_mode": "Auto fan mode"
|
||||
"auto_fan_mode": "Auto fan mode",
|
||||
"vswitch_on_command": "Optional turn on commands",
|
||||
"vswitch_off_command": "Optional turn off commands"
|
||||
},
|
||||
"data_description": {
|
||||
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
|
||||
@@ -94,7 +96,9 @@
|
||||
"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",
|
||||
"auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
|
||||
"auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary",
|
||||
"vswitch_on_command": "A list of turn on command in the form: action:parameter. Example: select_option:comfort. See README for more examples",
|
||||
"vswitch_off_command": "A list of turn on command in the form: action:parameter. Example: select_option:frost. See README for more examples"
|
||||
}
|
||||
},
|
||||
"tpi": {
|
||||
@@ -330,7 +334,9 @@
|
||||
"auto_regulation_periode_min": "Regulation minimum period",
|
||||
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
|
||||
"inverse_switch_command": "Inverse switch command",
|
||||
"auto_fan_mode": "Auto fan mode"
|
||||
"auto_fan_mode": "Auto fan mode",
|
||||
"vswitch_on_command": "Optional turn on commands",
|
||||
"vswitch_off_command": "Optional turn off commands"
|
||||
},
|
||||
"data_description": {
|
||||
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
|
||||
@@ -342,7 +348,9 @@
|
||||
"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",
|
||||
"auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
|
||||
"auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary",
|
||||
"vswitch_on_command": "A list of turn on command in the form: action:parameter. Example: select_option:comfort. See README for more examples",
|
||||
"vswitch_off_command": "A list of turn on command in the form: action:parameter. Example: select_option:frost. See README for more examples"
|
||||
}
|
||||
},
|
||||
"tpi": {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import logging
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
from homeassistant.const import STATE_ON
|
||||
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||
from homeassistant.core import Event, HomeAssistant, State, callback
|
||||
from homeassistant.helpers.event import (
|
||||
async_track_state_change_event,
|
||||
@@ -614,7 +614,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
return
|
||||
|
||||
# Find the underlying which have change
|
||||
under = self.find_underlying_by_entity_id(new_state.entity_id)
|
||||
under: UnderlyingClimate = self.find_underlying_by_entity_id(new_state.entity_id)
|
||||
|
||||
if not under:
|
||||
_LOGGER.warning(
|
||||
@@ -626,6 +626,16 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
new_hvac_mode = new_state.state
|
||||
|
||||
old_state = event.data.get("old_state")
|
||||
|
||||
# Issue #829 - refresh underlying command if it comes back to life
|
||||
if old_state is not None and new_state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN) and old_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
_LOGGER.warning("%s - underlying %s come back to life. New state=%s, old_state=%s. Will refresh its status", self, under.entity_id, new_state.state, old_state.state)
|
||||
# Force hvac_mode and target temperature
|
||||
await under.set_hvac_mode(self.hvac_mode)
|
||||
await self._send_regulated_temperature(force=True)
|
||||
|
||||
return
|
||||
|
||||
old_hvac_action = (
|
||||
old_state.attributes.get("hvac_action")
|
||||
if old_state and old_state.attributes
|
||||
@@ -833,7 +843,8 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
and under.last_sent_temperature is not None
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"Do temperature check. under.last_sent_temperature is %s, new_target_temp is %s",
|
||||
"%s - Do temperature check. under.last_sent_temperature is %s, new_target_temp is %s",
|
||||
self,
|
||||
under.last_sent_temperature,
|
||||
new_target_temp,
|
||||
)
|
||||
@@ -1093,7 +1104,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
@overrides
|
||||
async def async_set_humidity(self, humidity: int):
|
||||
"""Set new target humidity."""
|
||||
_LOGGER.info("%s - Set fan mode: %s", self, humidity)
|
||||
_LOGGER.info("%s - Set humidity: %s", self, humidity)
|
||||
if humidity is None:
|
||||
return
|
||||
for under in self._underlyings:
|
||||
@@ -1104,7 +1115,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
@overrides
|
||||
async def async_set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing operation."""
|
||||
_LOGGER.info("%s - Set fan mode: %s", self, swing_mode)
|
||||
_LOGGER.info("%s - Set swing mode: %s", self, swing_mode)
|
||||
if swing_mode is None:
|
||||
return
|
||||
for under in self._underlyings:
|
||||
|
||||
@@ -255,6 +255,13 @@ class ThermostatOverClimateValve(ThermostatOverClimate):
|
||||
self._attr_min_temp,
|
||||
)
|
||||
|
||||
self._last_regulation_change = self.now
|
||||
self.reset_last_change_time_from_vtherm()
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - last_regulation_change is now: %s and last_change_from_vtherm is now: %s", self, self._last_regulation_change, self._last_change_time_from_vtherm
|
||||
) # pylint: disable=protected-access
|
||||
|
||||
for under in self._underlyings_valve_regulation:
|
||||
await under.set_valve_open_percent()
|
||||
|
||||
@@ -263,11 +270,6 @@ class ThermostatOverClimateValve(ThermostatOverClimate):
|
||||
"""True if the Thermostat is regulated by valve"""
|
||||
return True
|
||||
|
||||
# @property
|
||||
# def hvac_modes(self) -> list[HVACMode]:
|
||||
# """Get the hvac_modes"""
|
||||
# return self._hvac_list
|
||||
|
||||
@property
|
||||
def valve_open_percent(self) -> int:
|
||||
"""Gives the percentage of valve needed"""
|
||||
|
||||
@@ -14,6 +14,8 @@ from .const import (
|
||||
CONF_UNDERLYING_LIST,
|
||||
CONF_HEATER_KEEP_ALIVE,
|
||||
CONF_INVERSE_SWITCH,
|
||||
CONF_VSWITCH_ON_CMD_LIST,
|
||||
CONF_VSWITCH_OFF_CMD_LIST,
|
||||
overrides,
|
||||
)
|
||||
|
||||
@@ -40,6 +42,8 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
"tpi_coef_ext",
|
||||
"power_percent",
|
||||
"calculated_on_percent",
|
||||
"vswitch_on_commands",
|
||||
"vswitch_off_commands",
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -47,6 +51,8 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
|
||||
"""Initialize the thermostat over switch."""
|
||||
self._is_inversed: bool | None = None
|
||||
self._lst_vswitch_on: list[str] = []
|
||||
self._lst_vswitch_off: list[str] = []
|
||||
super().__init__(hass, unique_id, name, config_entry)
|
||||
|
||||
@property
|
||||
@@ -76,9 +82,13 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
)
|
||||
|
||||
lst_switches = config_entry.get(CONF_UNDERLYING_LIST)
|
||||
self._lst_vswitch_on = config_entry.get(CONF_VSWITCH_ON_CMD_LIST, [])
|
||||
self._lst_vswitch_off = config_entry.get(CONF_VSWITCH_OFF_CMD_LIST, [])
|
||||
|
||||
delta_cycle = self._cycle_min * 60 / len(lst_switches)
|
||||
for idx, switch in enumerate(lst_switches):
|
||||
vswitch_on = self._lst_vswitch_on[idx] if idx < len(self._lst_vswitch_on) else None
|
||||
vswitch_off = self._lst_vswitch_off[idx] if idx < len(self._lst_vswitch_off) else None
|
||||
self._underlyings.append(
|
||||
UnderlyingSwitch(
|
||||
hass=self._hass,
|
||||
@@ -86,6 +96,8 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
switch_entity_id=switch,
|
||||
initial_delay_sec=idx * delta_cycle,
|
||||
keep_alive_sec=config_entry.get(CONF_HEATER_KEEP_ALIVE, 0),
|
||||
vswitch_on=vswitch_on,
|
||||
vswitch_off=vswitch_off,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -142,6 +154,9 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||
"calculated_on_percent"
|
||||
] = self._prop_algorithm.calculated_on_percent
|
||||
|
||||
self._attr_extra_state_attributes["vswitch_on_commands"] = self._lst_vswitch_on
|
||||
self._attr_extra_state_attributes["vswitch_off_commands"] = self._lst_vswitch_off
|
||||
|
||||
self.async_write_ha_state()
|
||||
_LOGGER.debug(
|
||||
"%s - Calling update_custom_attributes: %s",
|
||||
|
||||
@@ -82,7 +82,9 @@
|
||||
"auto_regulation_periode_min": "Regulation minimum period",
|
||||
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
|
||||
"inverse_switch_command": "Inverse switch command",
|
||||
"auto_fan_mode": "Auto fan mode"
|
||||
"auto_fan_mode": "Auto fan mode",
|
||||
"vswitch_on_command": "Optional turn on commands",
|
||||
"vswitch_off_command": "Optional turn off commands"
|
||||
},
|
||||
"data_description": {
|
||||
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
|
||||
@@ -94,7 +96,9 @@
|
||||
"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",
|
||||
"auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
|
||||
"auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary",
|
||||
"vswitch_on_command": "A list of turn on command in the form: action:parameter. Example: select_option:comfort. See README for more examples",
|
||||
"vswitch_off_command": "A list of turn on command in the form: action:parameter. Example: select_option:frost. See README for more examples"
|
||||
}
|
||||
},
|
||||
"tpi": {
|
||||
@@ -234,7 +238,7 @@
|
||||
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||
"min_opening_degrees": "A comma seperated list of minimal opening degrees. Default to 0. Example: 20, 25, 30"
|
||||
"min_opening_degrees": "Opening degree minimum value for each underlying device, comma separated. Default to 0. Example: 20, 25, 30"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -330,7 +334,9 @@
|
||||
"auto_regulation_periode_min": "Regulation minimum period",
|
||||
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
|
||||
"inverse_switch_command": "Inverse switch command",
|
||||
"auto_fan_mode": "Auto fan mode"
|
||||
"auto_fan_mode": "Auto fan mode",
|
||||
"vswitch_on_command": "Optional turn on commands",
|
||||
"vswitch_off_command": "Optional turn off commands"
|
||||
},
|
||||
"data_description": {
|
||||
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
|
||||
@@ -342,7 +348,9 @@
|
||||
"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",
|
||||
"auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
|
||||
"auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary",
|
||||
"vswitch_on_command": "A list of turn on command in the form: action:parameter. Example: select_option:comfort. See README for more examples",
|
||||
"vswitch_off_command": "A list of turn on command in the form: action:parameter. Example: select_option:frost. See README for more examples"
|
||||
}
|
||||
},
|
||||
"tpi": {
|
||||
@@ -481,7 +489,7 @@
|
||||
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
|
||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||
"min_opening_degrees": "A comma seperated list of minimal opening degrees. Default to 0. Example: 20, 25, 30"
|
||||
"min_opening_degrees": "Opening degree minimum value for each underlying device, comma separated. Default to 0. Example: 20, 25, 30"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
},
|
||||
"type": {
|
||||
"title": "Entité(s) liée(s)",
|
||||
"description": "Attributs de(s) l'entité(s) liée(s)",
|
||||
"description": "Attributs de(s) l'entité(s) liée(s) [](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/over-switch.md#configuration)",
|
||||
"data": {
|
||||
"underlying_entity_ids": "Les équipements à controller",
|
||||
"heater_keep_alive": "keep-alive (sec)",
|
||||
@@ -82,7 +82,11 @@
|
||||
"auto_regulation_periode_min": "Période minimale de régulation",
|
||||
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
|
||||
"inverse_switch_command": "Inverser la commande",
|
||||
"auto_fan_mode": " Auto ventilation mode"
|
||||
"auto_fan_mode": " Auto ventilation mode",
|
||||
"on_command_text": "Personnalisation des commandes d'allumage",
|
||||
"vswitch_on_command": "Commande d'allumage (optionnel)",
|
||||
"off_command_text": "Personnalisation des commandes d'extinction",
|
||||
"vswitch_off_command": "Commande d'extinction (optionnel)"
|
||||
},
|
||||
"data_description": {
|
||||
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
|
||||
@@ -94,7 +98,8 @@
|
||||
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
||||
"auto_regulation_use_device_temp": "Compenser 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",
|
||||
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
||||
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important",
|
||||
"on_command_text": "Pour les sous-jacents de type `select` ou `climate` vous devez personnaliser les commandes."
|
||||
}
|
||||
},
|
||||
"tpi": {
|
||||
@@ -155,7 +160,7 @@
|
||||
},
|
||||
"data_description": {
|
||||
"motion_sensor_entity_id": "Id d'entité du détecteur de mouvement",
|
||||
"motion_delay": "Délai avant activation lorsqu'un mouvement est détecté (secondss)",
|
||||
"motion_delay": "Délai avant activation lorsqu'un mouvement est détecté (secondes)",
|
||||
"motion_off_delai": "Délai avant désactivation lorsqu'aucun mouvement n'est détecté (secondes)",
|
||||
"motion_preset": "Preset à utiliser si mouvement détecté",
|
||||
"no_motion_preset": "Preset à utiliser si pas de mouvement détecté",
|
||||
@@ -200,7 +205,7 @@
|
||||
"use_advanced_central_config": "Utiliser la configuration centrale avancée"
|
||||
},
|
||||
"data_description": {
|
||||
"minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé",
|
||||
"minimal_activation_delay": "Délai en secondes en-dessous duquel l'équipement ne sera pas activé",
|
||||
"safety_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position de sécurité",
|
||||
"safety_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
|
||||
"safety_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité",
|
||||
@@ -319,7 +324,7 @@
|
||||
},
|
||||
"type": {
|
||||
"title": "Entité(s) liée(s) - {name}",
|
||||
"description": "Attributs de(s) l'entité(s) liée(s)",
|
||||
"description": "Attributs de(s) l'entité(s) liée(s) [](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/over-switch.md#configuration)",
|
||||
"data": {
|
||||
"underlying_entity_ids": "Les équipements à controller",
|
||||
"heater_keep_alive": "keep-alive (sec)",
|
||||
@@ -330,7 +335,11 @@
|
||||
"auto_regulation_periode_min": "Période minimale de régulation",
|
||||
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
|
||||
"inverse_switch_command": "Inverser la commande",
|
||||
"auto_fan_mode": " Auto ventilation mode"
|
||||
"auto_fan_mode": " Auto ventilation mode",
|
||||
"on_command_text": "Personnalisation des commandes d'allumage",
|
||||
"vswitch_on_command": "Commande d'allumage (optionnel)",
|
||||
"off_command_text": "Personnalisation des commandes d'extinction",
|
||||
"vswitch_off_command": "Commande d'extinction (optionnel)"
|
||||
},
|
||||
"data_description": {
|
||||
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
|
||||
@@ -342,7 +351,8 @@
|
||||
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
||||
"auto_regulation_use_device_temp": "Compenser 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",
|
||||
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
||||
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important",
|
||||
"on_command_text": "Pour les sous-jacents de type `select` ou `climate`"
|
||||
}
|
||||
},
|
||||
"tpi": {
|
||||
@@ -487,7 +497,8 @@
|
||||
"no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.",
|
||||
"service_configuration_format": "Mauvais format de la configuration du service",
|
||||
"valve_regulation_nb_entities_incorrect": "Le nombre d'entités pour la régulation par vanne doit être égal au nombre d'entité sous-jacentes",
|
||||
"min_opening_degrees_format": "Une liste d'entiers positifs séparés par des ',' est attendu. Exemple : 20, 25, 30"
|
||||
"min_opening_degrees_format": "Une liste d'entiers positifs séparés par des ',' est attendu. Exemple : 20, 25, 30",
|
||||
"vswitch_configuration_incorrect": "La configuration de la personnalisation des commandes est incorrecte. Elle est obligatoire pour les sous-jacents non switch et le format doit être 'service_name[/attribut:valeur]'. Plus d'informations dans le README."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Le device est déjà configuré"
|
||||
@@ -499,7 +510,7 @@
|
||||
"thermostat_central_config": "Configuration centrale",
|
||||
"thermostat_over_switch": "Thermostat sur un switch",
|
||||
"thermostat_over_climate": "Thermostat sur un autre thermostat",
|
||||
"thermostat_over_valve": "Thermostat sur une valve"
|
||||
"thermostat_over_valve": "Thermostat sur une vanne"
|
||||
}
|
||||
},
|
||||
"auto_regulation_mode": {
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
""" Underlying entities classes """
|
||||
import logging
|
||||
from typing import Any
|
||||
import re
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, STATE_UNAVAILABLE
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, STATE_OFF, STATE_UNAVAILABLE
|
||||
from homeassistant.core import State
|
||||
|
||||
from homeassistant.exceptions import ServiceNotFound
|
||||
@@ -195,21 +197,22 @@ class UnderlyingEntity:
|
||||
self._cancel_cycle()
|
||||
await self.turn_off()
|
||||
|
||||
async def check_overpowering(self) -> bool:
|
||||
"""Check that a underlying can be turned on, else
|
||||
activate the overpowering state of the VTherm associated.
|
||||
Returns True if the check is ok (no overpowering needed)"""
|
||||
if not await self._thermostat.power_manager.check_power_available():
|
||||
_LOGGER.debug("%s - overpowering is detected", self)
|
||||
await self._thermostat.power_manager.set_overpowering(True)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class UnderlyingSwitch(UnderlyingEntity):
|
||||
"""Represent a underlying switch"""
|
||||
|
||||
_initialDelaySec: int
|
||||
_on_time_sec: int
|
||||
_off_time_sec: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
thermostat: Any,
|
||||
switch_entity_id: str,
|
||||
initial_delay_sec: int,
|
||||
keep_alive_sec: float,
|
||||
self, hass: HomeAssistant, thermostat: Any, switch_entity_id: str, initial_delay_sec: int, keep_alive_sec: float, vswitch_on: str = None, vswitch_off: str = None
|
||||
) -> None:
|
||||
"""Initialize the underlying switch"""
|
||||
|
||||
@@ -225,6 +228,14 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
self._on_time_sec = 0
|
||||
self._off_time_sec = 0
|
||||
self._keep_alive = IntervalCaller(hass, keep_alive_sec)
|
||||
self._vswitch_on = vswitch_on
|
||||
self._vswitch_off = vswitch_off
|
||||
self._domain = self._entity_id.split(".")[0]
|
||||
# build command
|
||||
command, data, state_on = self.build_command(use_on=True)
|
||||
self._on_command = {"command": command, "data": data, "state": state_on}
|
||||
command, data, state_off = self.build_command(use_on=False)
|
||||
self._off_command = {"command": command, "data": data, "state": state_off}
|
||||
|
||||
@property
|
||||
def initial_delay_sec(self):
|
||||
@@ -265,10 +276,11 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
@property
|
||||
def is_device_active(self):
|
||||
"""If the toggleable device is currently active."""
|
||||
real_state = self._hass.states.is_state(self._entity_id, STATE_ON)
|
||||
return (self.is_inversed and not real_state) or (
|
||||
not self.is_inversed and real_state
|
||||
)
|
||||
# real_state = self._hass.states.is_state(self._entity_id, STATE_ON)
|
||||
# return (self.is_inversed and not real_state) or (
|
||||
# not self.is_inversed and real_state
|
||||
# )
|
||||
return self._hass.states.is_state(self._entity_id, self._on_command.get("state"))
|
||||
|
||||
async def _keep_alive_callback(self):
|
||||
"""Keep alive: Turn on if already turned on, turn off if already turned off."""
|
||||
@@ -295,18 +307,44 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
)
|
||||
await (self.turn_on() if self.is_device_active else self.turn_off())
|
||||
|
||||
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
|
||||
def build_command(self, use_on: bool) -> Tuple[str, Dict[str, str]]:
|
||||
"""Build a command and returns a command and a dict as data"""
|
||||
|
||||
value = None
|
||||
data = {ATTR_ENTITY_ID: self._entity_id}
|
||||
vswitch = self._vswitch_on if use_on and not self.is_inversed else self._vswitch_off
|
||||
if vswitch:
|
||||
pattern = r"^(?P<command>[^/]+)(?:/(?P<argument>[^:]+)(?::(?P<value>.*))?)?$"
|
||||
match = re.match(pattern, vswitch)
|
||||
|
||||
if match:
|
||||
# Extraire les groupes nommés
|
||||
command = match.group("command")
|
||||
argument = match.group("argument")
|
||||
value = match.group("value")
|
||||
data.update({argument: value})
|
||||
else:
|
||||
raise ValueError(f"Invalid input format: {vswitch}")
|
||||
|
||||
else:
|
||||
command = SERVICE_TURN_ON if use_on and not self.is_inversed else SERVICE_TURN_OFF
|
||||
value = STATE_ON if use_on and not self.is_inversed else STATE_OFF
|
||||
|
||||
return command, data, value
|
||||
|
||||
async def turn_off(self):
|
||||
"""Turn heater toggleable device off."""
|
||||
self._keep_alive.cancel() # Cancel early to avoid a turn_on/turn_off race condition
|
||||
_LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
|
||||
command = SERVICE_TURN_OFF if not self.is_inversed else SERVICE_TURN_ON
|
||||
domain = self._entity_id.split(".")[0]
|
||||
|
||||
command = self._off_command.get("command")
|
||||
data = self._off_command.get("data")
|
||||
|
||||
# This may fails if called after shutdown
|
||||
try:
|
||||
try:
|
||||
data = {ATTR_ENTITY_ID: self._entity_id}
|
||||
await self._hass.services.async_call(domain, command, data)
|
||||
_LOGGER.debug("%s - Sending command %s with data=%s", self, command, data)
|
||||
await self._hass.services.async_call(self._domain, command, data)
|
||||
self._keep_alive.set_async_action(self._keep_alive_callback)
|
||||
except Exception:
|
||||
self._keep_alive.cancel()
|
||||
@@ -318,13 +356,18 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
"""Turn heater toggleable device on."""
|
||||
self._keep_alive.cancel() # Cancel early to avoid a turn_on/turn_off race condition
|
||||
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
|
||||
command = SERVICE_TURN_ON if not self.is_inversed else SERVICE_TURN_OFF
|
||||
domain = self._entity_id.split(".")[0]
|
||||
|
||||
if not await self.check_overpowering():
|
||||
return False
|
||||
|
||||
command = self._on_command.get("command")
|
||||
data = self._on_command.get("data")
|
||||
try:
|
||||
try:
|
||||
data = {ATTR_ENTITY_ID: self._entity_id}
|
||||
await self._hass.services.async_call(domain, command, data)
|
||||
_LOGGER.debug("%s - Sending command %s with data=%s", self, command, data)
|
||||
await self._hass.services.async_call(self._domain, command, data)
|
||||
self._keep_alive.set_async_action(self._keep_alive_callback)
|
||||
return True
|
||||
except Exception:
|
||||
self._keep_alive.cancel()
|
||||
raise
|
||||
@@ -414,10 +457,6 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
await self.turn_off()
|
||||
return
|
||||
|
||||
# if await self._thermostat.power_manager.check_overpowering():
|
||||
# _LOGGER.debug("%s - End of cycle (3)", self)
|
||||
# return
|
||||
|
||||
# safety mode could have change the on_time percent
|
||||
await self._thermostat.safety_manager.refresh_state()
|
||||
time = self._on_time_sec
|
||||
@@ -432,7 +471,8 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
time // 60,
|
||||
time % 60,
|
||||
)
|
||||
await self.turn_on()
|
||||
if not await self.turn_on():
|
||||
return
|
||||
else:
|
||||
_LOGGER.debug("%s - No action on heater cause duration is 0", self)
|
||||
self._async_cancel_cycle = self.call_later(
|
||||
@@ -557,6 +597,10 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
)
|
||||
return False
|
||||
|
||||
# When turning on a climate, check that power is available
|
||||
if hvac_mode in (HVACMode.HEAT, HVACMode.COOL) and not await self.check_overpowering():
|
||||
return False
|
||||
|
||||
data = {ATTR_ENTITY_ID: self._entity_id, "hvac_mode": hvac_mode}
|
||||
await self._hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
@@ -595,7 +639,7 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
|
||||
async def set_humidity(self, humidity: int):
|
||||
"""Set new target humidity."""
|
||||
_LOGGER.info("%s - Set fan mode: %s", self, humidity)
|
||||
_LOGGER.info("%s - Set humidity: %s", self, humidity)
|
||||
if not self.is_initialized:
|
||||
return
|
||||
data = {
|
||||
@@ -611,7 +655,7 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
|
||||
async def set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing operation."""
|
||||
_LOGGER.info("%s - Set fan mode: %s", self, swing_mode)
|
||||
_LOGGER.info("%s - Set swing mode: %s", self, swing_mode)
|
||||
if not self.is_initialized:
|
||||
return
|
||||
data = {
|
||||
@@ -635,6 +679,9 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
data = {
|
||||
ATTR_ENTITY_ID: self._entity_id,
|
||||
}
|
||||
|
||||
_LOGGER.info("%s - Set setpoint temperature to: %s", self, target_temp)
|
||||
|
||||
# Issue 807 add TARGET_TEMPERATURE only if in the features
|
||||
if ClimateEntityFeature.TARGET_TEMPERATURE_RANGE in self._underlying_climate.supported_features:
|
||||
data.update(
|
||||
@@ -654,6 +701,7 @@ class UnderlyingClimate(UnderlyingEntity):
|
||||
)
|
||||
|
||||
self._last_sent_temperature = target_temp
|
||||
_LOGGER.debug("%s - Last_sent_temperature is now: %s", self, self._last_sent_temperature)
|
||||
|
||||
@property
|
||||
def last_sent_temperature(self) -> float | None:
|
||||
|
||||
@@ -11,6 +11,7 @@ The behavior of this feature is as follows:
|
||||
1. When a new measurement of the home's power consumption or the maximum allowed power is received,
|
||||
2. If the maximum power is exceeded, the central command will shed the load of all active devices starting with those closest to the setpoint. This continues until enough _VTherms_ are shed,
|
||||
3. If there is available power reserve and some _VTherms_ are shed, the central command will re-enable as many devices as possible, starting with those furthest from the setpoint (at the time they were shed).
|
||||
4. When a _VTherm_ starts, a check is performed to determine if the declared power is available. If not, the _VTherm_ is put into shed mode.
|
||||
|
||||
**WARNING:** This is **not a safety feature** but an optimization function to manage consumption at the expense of some heating degradation. Overconsumption is still possible depending on the frequency of your consumption sensor updates and the actual power used by your equipment. Always maintain a safety margin.
|
||||
|
||||
|
||||
BIN
documentation/en/images/card-trv-jeffodilo.png
Normal file
BIN
documentation/en/images/card-trv-jeffodilo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
@@ -25,7 +25,8 @@ The installation should look like this:
|
||||
|
||||
## Configuration
|
||||
|
||||
Click on the "Underlying Entities" option from the menu, and you will see this configuration page:
|
||||
First, configure the main settings common to all _VTherms_ (see [main settings](base-attributes.md)).
|
||||
Then, click on the "Underlying Entities" option from the menu, and you will see this configuration page:
|
||||
|
||||

|
||||
|
||||
@@ -59,6 +60,8 @@ Of course, your underlying equipment must have ventilation, and it must be contr
|
||||
|
||||
### Compensating for the Internal Temperature of the Underlying Equipment
|
||||
|
||||
Warning: This option must not be used with direct valve control regulation if a calibration entity has been provided.
|
||||
|
||||
Sometimes, the internal thermometer of the underlying equipment (TRV, air conditioner, etc.) is inaccurate to the point that self-regulation is insufficient. This happens when the internal thermometer is placed too close to the heat source. The internal temperature rises much faster than the room temperature, leading to regulation failures.
|
||||
Example:
|
||||
1. Room temperature is 18°, setpoint is 20°.
|
||||
@@ -98,4 +101,4 @@ When this entity is 'On', all temperature or state changes made directly on the
|
||||
Be careful, if you use this feature, your equipment is now controlled in two ways: _VTherm_ and directly by you. The commands might be contradictory, which could lead to confusion about the equipment's state. _VTherm_ is equipped with a delay mechanism that prevents loops: the user gives a setpoint, which is captured by _VTherm_ and changes the setpoint, ... This delay may cause the change made directly on the equipment to be ignored if these changes are too close together in time.
|
||||
|
||||
Some equipment (like Daikin, for example) changes state by itself. If the checkbox is checked, it may turn off the _VTherm_ when that's not what you intended.
|
||||
That's why it's better not to use it. It generates a lot of confusion and many support requests.
|
||||
That's why it's better not to use it. It generates a lot of confusion and many support requests.
|
||||
|
||||
@@ -26,7 +26,8 @@ The installation should look like this:
|
||||
|
||||
## Configuration
|
||||
|
||||
Click on the "Underlying Entities" option from the menu, and you will see this configuration page:
|
||||
First, configure the main settings common to all _VTherms_ (see [main settings](base-attributes.md)).
|
||||
Then, click on the "Underlying Entities" option from the menu, and you will see this configuration page:
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@ The installation should be similar to the `over_switch` VTherm setup, except tha
|
||||
|
||||
## Configuration
|
||||
|
||||
Click on the "Underlying Entities" option from the menu, and you will see this configuration page, you should add the `number` entities that will be controlled by VTherm. Only `number` or `input_number` entities are accepted.
|
||||
First, configure the main settings common to all _VTherms_ (see [main settings](base-attributes.md)).
|
||||
Then, click on the "Underlying Entities" option from the menu, and you will see this configuration page, you should add the `number` entities that will be controlled by VTherm. Only `number` or `input_number` entities are accepted.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -40,4 +40,5 @@ Some TRV type thermostats are known to be incompatible with Versatile Thermostat
|
||||
5. TRVs like Aqara SRTS-A01 and MOES TV01-ZB, which lack the `hvac_action` state feedback to determine whether they are heating or not. Therefore, state feedback is inaccurate, but the rest seems functional.
|
||||
6. Airwell air conditioners with the "Midea AC LAN" integration. If two VTherm commands are too close together, the air conditioner stops itself.
|
||||
7. Climates based on the Overkiz integration do not work. It seems impossible to turn off or even change the temperature on these systems.
|
||||
8. Heating systems based on Netatmo perform poorly. Netatmo schedules conflict with _VTherm_ programming. Netatmo devices constantly revert to `Auto` mode, which is poorly managed with _VTherm_. In this mode, _VTherm_ cannot determine whether the system is heating or cooling, making it impossible to select the correct algorithm. Some users have managed to make it work using a virtual switch between _VTherm_ and the underlying system, but stability is not guaranteed. An example is provided in the [troubleshooting](troubleshooting.md) section.
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ This allows you to configure the valve control entities:
|
||||
You need to provide:
|
||||
1. As many valve opening control entities as there are underlying devices, and in the same order. These parameters are mandatory.
|
||||
2. As many temperature calibration entities as there are underlying devices, and in the same order. These parameters are optional; they must either all be provided or none.
|
||||
3. As many valve closure control entities as there are underlying devices, and in the same order. These parameters are optional; they must either all be provided or none.
|
||||
4. A list of minimum opening values for the valve when it needs to be opened. This field is a list of integers. If the valve needs to be opened, it will be opened at a minimum of this opening value. This allows enough water to pass through when it needs to be opened.
|
||||
3. As many valve closure control entities as there are underlying devices, and in the same order. These parameters are optional; they must either all be provided or none. For Sonoff TRVZB, you should not configure this entity. See the note below.
|
||||
4. A list of minimum opening values for the valve when it needs to be opened. This field is a list of integers. If the valve needs to be opened, it will be opened at a minimum of this opening value, else it will be set to 0 (to ensure the valve is closed). This allows enough water to pass through when it needs to be opened.
|
||||
|
||||
The opening rate calculation algorithm is based on the _TPI_ algorithm described [here](algorithms.md). This is the same algorithm used for _VTherms_ `over_switch` and `over_valve`.
|
||||
|
||||
@@ -152,4 +152,4 @@ To apply the changes, you must either **restart Home Assistant completely** or j
|
||||
|
||||
## Summary of the Auto-Regulation Algorithm
|
||||
|
||||
A summary of the auto-regulation algorithm is described [here](algorithms.md#the-auto-regulation-algorithm-without-valve-control)
|
||||
A summary of the auto-regulation algorithm is described [here](algorithms.md#the-auto-regulation-algorithm-without-valve-control)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Using a Heatzy](#using-a-heatzy)
|
||||
- [Using a radiator with a pilot wire (Nodon SIN-4-FP-21)](#using-a-radiator-with-a-pilot-wire-nodon-sin-4-fp-21)
|
||||
- [Using a Netatmo System](#using-a-netatmo-system)
|
||||
- [Only the first radiator heats](#only-the-first-radiator-heats)
|
||||
- [The radiator heats even though the setpoint temperature is exceeded, or it does not heat when the room temperature is well below the setpoint](#the-radiator-heats-even-though-the-setpoint-temperature-is-exceeded-or-it-does-not-heat-when-the-room-temperature-is-well-below-the-setpoint)
|
||||
- [Type `over_switch` or `over_valve`](#type-over_switch-or-over_valve)
|
||||
@@ -16,6 +17,7 @@
|
||||
- [Using a Group of People as a Presence Sensor](#using-a-group-of-people-as-a-presence-sensor)
|
||||
- [Enable Logs for the Versatile Thermostat](#enable-logs-for-the-versatile-thermostat)
|
||||
- [VTherm does not track setpoint changes made directly on the underlying device (`over_climate`)](#vtherm-does-not-track-setpoint-changes-made-directly-on-the-underlying-device-over_climate)
|
||||
- [VTherm Automatically Switches to 'Cooling' or 'Heating' Mode](#vtherm-automatically-switches-to-cooling-or-heating-mode)
|
||||
|
||||
|
||||
## Using a Heatzy
|
||||
@@ -85,6 +87,17 @@ Example:
|
||||
|
||||
Another more complex example is [here](https://github.com/jmcollin78/versatile_thermostat/discussions/431#discussioncomment-11393065)
|
||||
|
||||
## Using a Netatmo System
|
||||
|
||||
The system based on Netatmo TRVs does not work well with _VTherm_. You can find a discussion about the specific behavior of Netatmo systems (in French) here: [https://forum.hacf.fr/t/vannes-netatmo-et-vtherm/56063](https://forum.hacf.fr/t/vannes-netatmo-et-vtherm/56063).
|
||||
|
||||
However, some users have successfully integrated _VTherm_ with Netatmo by incorporating a virtual switch between _VTherm_ and the Netatmo `climate` entity, as follows:
|
||||
|
||||
```
|
||||
TODO
|
||||
```
|
||||
|
||||
|
||||
## Only the first radiator heats
|
||||
|
||||
In `over_switch` mode, if multiple radiators are configured for the same VTherm, the heating will be triggered sequentially to smooth out the consumption peaks as much as possible.
|
||||
@@ -213,4 +226,10 @@ Be careful, in debug mode, Versatile Thermostat is very verbose and can quickly
|
||||
|
||||
## VTherm does not track setpoint changes made directly on the underlying device (`over_climate`)
|
||||
|
||||
See the details of this feature [here](over-climate.md#track-underlying-temperature-changes).
|
||||
See the details of this feature [here](over-climate.md#track-underlying-temperature-changes).
|
||||
|
||||
## VTherm Automatically Switches to 'Cooling' or 'Heating' Mode
|
||||
|
||||
Some reversible heat pumps have modes that allow the heat pump to decide whether to heat or cool. These modes are labeled as 'Auto' or 'Heat_cool' depending on the brand. These two modes should not be used with _VTherm_ because _VTherm_'s algorithms require explicit knowledge of whether the system is in heating or cooling mode, which these modes do not provide.
|
||||
|
||||
You should only use the following modes: `Heat`, `Cool`, `Off`, or optionally `Fan` (although `Fan` has no practical purpose with _VTherm_).
|
||||
@@ -5,6 +5,7 @@
|
||||
- [Composant Scheduler !](#composant-scheduler-)
|
||||
- [Courbes de régulattion avec Plotly](#courbes-de-régulattion-avec-plotly)
|
||||
- [Les notification avec l'AappDaemon NOTIFIER](#les-notification-avec-laappdaemon-notifier)
|
||||
- [Une très belle carte (merci @Jeffodilo)](#une-très-belle-carte-merci-jeffodilo)
|
||||
|
||||
|
||||
## Versatile Thermostat UI Card
|
||||
@@ -213,4 +214,275 @@ action:
|
||||
mode: queued
|
||||
max: 30
|
||||
```
|
||||
</details>
|
||||
|
||||
## Une très belle carte (merci @Jeffodilo)
|
||||
|
||||
@Jeffodilo a réalisé et partagé le code d'une très belle carte adaptée au TRV :
|
||||
|
||||

|
||||
|
||||
Cette carte n’utilise pas card_mod, elle utilise les cartes custom assez courante suivantes
|
||||
On garde une partie de l’interface UI, sauf pour l’horizontale de la 2ème ligne
|
||||
- custom:vertical-stack-in-card
|
||||
- custom:stack-in-card
|
||||
- custom:mini-graph-card
|
||||
- custom:mushroom-template-card
|
||||
- custom:button-card
|
||||
|
||||
L'original est ici (En Français): [forum HACF](https://forum.hacf.fr/t/carte-mise-en-forme-vanne-avec-thermostant-versatile/56132)
|
||||
|
||||
Evidemment, vous devez l'adapter à votre code.
|
||||
|
||||
Le code:
|
||||
|
||||
<details>
|
||||
|
||||
```yaml
|
||||
type: vertical-stack
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:bed-double
|
||||
heading: Parents
|
||||
heading_style: title
|
||||
- type: custom:vertical-stack-in-card
|
||||
cards:
|
||||
- type: custom:mini-graph-card
|
||||
entities:
|
||||
- entity: sensor.sonde_parents_temperature
|
||||
name: Température
|
||||
state_adaptive_color: true
|
||||
- entity: climate.valve_parents
|
||||
name: Temp
|
||||
attribute: current_temperature
|
||||
unit: °C
|
||||
state_adaptive_color: true
|
||||
show_graph: false
|
||||
show_state: true
|
||||
hour24: true
|
||||
hours_to_show: 24
|
||||
points_per_hour: 2
|
||||
font_size: 50
|
||||
show:
|
||||
name: false
|
||||
icon: false
|
||||
legend: false
|
||||
labels: true
|
||||
extrema: false
|
||||
color_thresholds:
|
||||
- color: "#33ccff"
|
||||
value: 19
|
||||
- color: "#00ffff"
|
||||
value: 19.5
|
||||
- color: "#33ffcc"
|
||||
value: 20
|
||||
- color: "#00ff99"
|
||||
value: 20.5
|
||||
- color: "#ffff99"
|
||||
value: 21
|
||||
- color: "#ffff33"
|
||||
value: 21.5
|
||||
- color: "#ff9933"
|
||||
value: 22
|
||||
- color: "#cc6633"
|
||||
value: 24
|
||||
- color: "#ff6000"
|
||||
value: 26
|
||||
- type: custom:stack-in-card
|
||||
mode: horizontal
|
||||
cards:
|
||||
- type: custom:mushroom-template-card
|
||||
secondary: ""
|
||||
layout: horizontal
|
||||
tap_action:
|
||||
action: more-info
|
||||
entity: sensor.sonde_parents_temperature
|
||||
fill_container: false
|
||||
multiline_secondary: false
|
||||
primary: >-
|
||||
{% if is_state_attr('climate.versatile_parents','hvac_action',
|
||||
'idle') %}
|
||||
🗜️ {{ states('number.valve_parents_valve_opening_degree', with_unit=True,)}} |🔋{{ states('sensor.valve_parents_battery') }} % | Inactif
|
||||
{% elif is_state_attr('climate.versatile_parents','hvac_action',
|
||||
'heating') %}
|
||||
🗜️ {{ states('number.valve_parents_valve_opening_degree', with_unit=True,)}} |🔋{{ states('sensor.valve_parents_battery') }} % | Chauffe
|
||||
{% else %} 🗜️ {{
|
||||
states('number.valve_parents_valve_opening_degree',
|
||||
with_unit=True,)}} | 🔋{{ states('sensor.valve_parents_battery')
|
||||
}} % | Off {% endif %}
|
||||
icon: ""
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: custom:button-card
|
||||
name: Conf.
|
||||
entity: climate.versatile_parents
|
||||
show_state: false
|
||||
show_icon: true
|
||||
show_name: false
|
||||
icon: mdi:fire
|
||||
size: 80%
|
||||
styles:
|
||||
icon:
|
||||
- color: |
|
||||
[[[
|
||||
if (states['climate.versatile_parents']) {
|
||||
if (states['climate.versatile_parents'].attributes.preset_mode == 'comfort')
|
||||
return 'darkorange';
|
||||
else
|
||||
return 'white'; }
|
||||
]]]
|
||||
name:
|
||||
- color: white
|
||||
- font-size: 60%
|
||||
card:
|
||||
- height: 40px
|
||||
- width: 30px
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: climate.set_preset_mode
|
||||
target:
|
||||
entity_id:
|
||||
- climate.versatile_parents
|
||||
data:
|
||||
preset_mode: comfort
|
||||
- type: custom:button-card
|
||||
name: Eco
|
||||
entity: climate.versatile_parents
|
||||
show_state: false
|
||||
show_icon: true
|
||||
show_name: false
|
||||
icon: mdi:leaf
|
||||
size: 80%
|
||||
styles:
|
||||
icon:
|
||||
- color: |
|
||||
[[[
|
||||
if (states['climate.versatile_parents']) {
|
||||
if (states['climate.versatile_parents'].attributes.preset_mode == 'eco')
|
||||
return 'lightgreen';
|
||||
else
|
||||
return 'white'; }
|
||||
]]]
|
||||
name:
|
||||
- color: white
|
||||
- font-size: 60%
|
||||
card:
|
||||
- height: 40px
|
||||
- width: 30px
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: climate.set_preset_mode
|
||||
target:
|
||||
entity_id:
|
||||
- climate.versatile_parents
|
||||
data:
|
||||
preset_mode: eco
|
||||
- type: custom:button-card
|
||||
name: Manu
|
||||
entity: climate.versatile_parents
|
||||
show_state: false
|
||||
show_icon: true
|
||||
show_name: false
|
||||
icon: mdi:hand-back-left
|
||||
size: 80%
|
||||
styles:
|
||||
icon:
|
||||
- color: |
|
||||
[[[
|
||||
if (states['climate.versatile_parents']) {
|
||||
if (states['climate.versatile_parents'].attributes.preset_mode == 'none')
|
||||
return 'indianred';
|
||||
else
|
||||
return 'white'; }
|
||||
]]]
|
||||
name:
|
||||
- color: white
|
||||
- font-size: 60%
|
||||
card:
|
||||
- height: 40px
|
||||
- width: 30px
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: climate.set_preset_mode
|
||||
target:
|
||||
entity_id:
|
||||
- climate.versatile_parents
|
||||
data:
|
||||
preset_mode: none
|
||||
- type: custom:button-card
|
||||
name: Abs.
|
||||
entity: climate.versatile_parents
|
||||
show_state: false
|
||||
show_icon: true
|
||||
show_name: false
|
||||
icon: mdi:snowflake
|
||||
size: 80%
|
||||
styles:
|
||||
icon:
|
||||
- color: |
|
||||
[[[
|
||||
if (states['climate.versatile_parents']) {
|
||||
if (states['climate.versatile_parents'].attributes.preset_mode == 'frost')
|
||||
return 'skyblue';
|
||||
else
|
||||
return 'white'; }
|
||||
]]]
|
||||
name:
|
||||
- color: white
|
||||
- font-size: 60%
|
||||
card:
|
||||
- height: 40px
|
||||
- width: 30px
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: climate.set_preset_mode
|
||||
target:
|
||||
entity_id:
|
||||
- climate.versatile_parents
|
||||
data:
|
||||
preset_mode: frost
|
||||
- type: custom:button-card
|
||||
name: Boost
|
||||
entity: climate.versatile_parents
|
||||
show_state: false
|
||||
show_icon: true
|
||||
show_name: false
|
||||
icon: mdi:rocket-launch
|
||||
size: 80%
|
||||
styles:
|
||||
icon:
|
||||
- color: |
|
||||
[[[
|
||||
if (states['climate.versatile_parents']) {
|
||||
if (states['climate.versatile_parents'].attributes.preset_mode == 'boost')
|
||||
return 'red';
|
||||
else
|
||||
return 'white'; }
|
||||
]]]
|
||||
name:
|
||||
- color: white
|
||||
- font-size: 60%
|
||||
card:
|
||||
- height: 40px
|
||||
- width: 30px
|
||||
tap_action:
|
||||
action: perform-action
|
||||
perform_action: climate.set_preset_mode
|
||||
target:
|
||||
entity_id:
|
||||
- climate.versatile_parents
|
||||
data:
|
||||
preset_mode: boost
|
||||
- type: custom:mushroom-climate-card
|
||||
entity: climate.versatile_parents
|
||||
show_temperature_control: true
|
||||
hvac_modes: []
|
||||
tap_action:
|
||||
action: more-info
|
||||
primary_info: state
|
||||
icon: mdi:radiator
|
||||
secondary_info: last-updated
|
||||
fill_container: false
|
||||
layout: horizontal
|
||||
```
|
||||
</details>
|
||||
@@ -10,6 +10,7 @@ Le comportement de cette fonction est le suivant :
|
||||
1. lorsqu'une nouvelle mesure de la puissance consommée du logement ou de la puissance maximale autorisée est reçue,
|
||||
2. si la puissance max est dépassée, la commande centrale va mettre en délestage tous les équipements actifs en commençant par ceux qui sont le plus près de la consigne. Il fait ça jusqu'à ce que suffisament de _VTherm_ soient délestés,
|
||||
3. si une réserve de puissance est disponible et que des _VTherms_ sont délestés, alors la commande centrale va délester autant d'équipements que possible en commençant par les plus loin de la consigne (au moment où il a été mis en délestage),
|
||||
4. au démarrage d'un _VTherm_, une vérification est effectuée pour savoir si la puissance déclarée est disponible. Si non, le _VTherm_ est passé en délestage.
|
||||
|
||||
ATTENTION: ce fonctionnement **n'est pas une fonction de sécurité** mais plus une fonction permettant une optimisation de la consommation au prix d'une dégradation du chauffage. Des dépassements sont possibles selon la fréquence de remontée de vos capteurs de consommation, la puissance réellement utilisée par votre équipements. Vous devez donc toujours garder une marge de sécurité.
|
||||
|
||||
|
||||
BIN
documentation/fr/images/card-trv-jeffodilo.png
Normal file
BIN
documentation/fr/images/card-trv-jeffodilo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
BIN
documentation/fr/images/config-vswitch1.png
Normal file
BIN
documentation/fr/images/config-vswitch1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
documentation/fr/images/config-vswitch2.png
Normal file
BIN
documentation/fr/images/config-vswitch2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -26,7 +26,8 @@ L'installation doit ressembler à ça :
|
||||
|
||||
## Configuration
|
||||
|
||||
Cliquer sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration :
|
||||
Configurez d'abord les paramètres principaux et communs à tous les _VTherm_ (cf. [paramètres principaux](base-attributes.md)).
|
||||
Cliquez ensuite sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration :
|
||||
|
||||

|
||||
|
||||
@@ -62,6 +63,8 @@ Une fois l'écart de température redevenu faible, la ventilation se mettra dans
|
||||
|
||||
### Compenser la température interne du sous-jacent
|
||||
|
||||
Attention : cette option ne doit pas être utilisée avec une régulation par contrôle direct de la vanne si une entité de calibrage a été fournie.
|
||||
|
||||
Quelque fois, il arrive que le thermomètre interne du sous-jacent (TRV, climatisation, ...) soit tellement faux que l' auto-régulation ne suffise pas à réguler.
|
||||
Cela arrive lorsque le thermomètre interne est trop près de la source de chaleur. La température interne monte alors beaucoup plus vite que la température de la pièce, ce qui génère des défauts dans la régulation.
|
||||
Exemple :
|
||||
@@ -102,4 +105,4 @@ Lorsque cette entité est 'On', tous les changements de température ou d'état
|
||||
Attention, si vous utilisez cette fonction, votre équipement est maintenant contrôlé par 2 moyens : _VTherm_ et par vous même directement. Les ordres peuvent être contradictoires et cela peut induire une incompréhension sur l'état de l'équipement. _VTherm_ est équipé d'un mécanisme de temporisation qui évite les boucles : l'utilisateur donne une consigne qui est captée par _VTherm_ qui change la consigne, ... Cette temporisation peut faire que le changement fait directement sur l'équipement est ignoré si ces changements sont trop rapprochés dans le temps.
|
||||
|
||||
Certains équipements (Daikin par exemple), changent d'état tout seul. Si la case est cochée, cela peut éteindre le _VTherm_ alors que ce n'est pas ce que vous souhaitiez.
|
||||
C'est pour ça qu'il est préférable de ne pas l'utiliser. Cela génère beaucoup d'incompréhensions et de nombreuses demandes de support.
|
||||
C'est pour ça qu'il est préférable de ne pas l'utiliser. Cela génère beaucoup d'incompréhensions et de nombreuses demandes de support.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- [Le keep-alive](#le-keep-alive)
|
||||
- [Le mode AC](#le-mode-ac)
|
||||
- [L'inversion de la commande](#linversion-de-la-commande)
|
||||
- [La personnalisation des commandes](#la-personnalisation-des-commandes)
|
||||
|
||||
## Pré-requis
|
||||
|
||||
@@ -17,20 +18,23 @@ L'installation doit ressembler à ça :
|
||||
1. L'utilisateur ou une automatisation ou le Sheduler programme une consigne (setpoint) par le biais d'un pre-réglage ou directement d'une température,
|
||||
2. régulièrement le thermomètre intérieur (2) ou extérieur (2b) envoie la température mesurée. Le thermomètre interieur doit être placé à une place pertinente pour le ressenti de l'utilisateur : idéalement au milieu du lieu de vie. Evitez de le mettre trop près d'une fenêtre ou trop proche du radiateur,
|
||||
3. avec les valeurs de consigne, les différentes températures et des paramètres de l'algorithme TPI (cf. [TPI](algorithms.md#lalgorithme-tpi)), VTherm va calculer un pourcentage de temps d'allumage,
|
||||
4. et va régulièrement commander l'allumage et l'extinction du ou des entités `switch` sous-jacentes,
|
||||
5. ces entités switchs sous-jacentes vont alors commander le switch physique
|
||||
4. et va régulièrement commander l'allumage et l'extinction du ou des entités `switch` (ou `select` ou `climate`) sous-jacentes,
|
||||
5. ces entités sous-jacentes vont alors commander l'équipement physique
|
||||
6. la commande du switch physique allumera ou éteindra le radiateur.
|
||||
|
||||
> Le pourcentage d'allumage est recalculé à chaque cycle et c'est ce qui permet de réguler la température de la pièce.
|
||||
|
||||
## Configuration
|
||||
|
||||
Cliquer sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration :
|
||||
Configurez d'abord les paramètres principaux et communs à tous les _VTherm_ (cf. [paramètres principaux](base-attributes.md)).
|
||||
Ensuite cliquez sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration :
|
||||
|
||||

|
||||
|
||||
### les sous-jacents
|
||||
Dans la "liste des équipements à contrôler" vous mettez les switchs qui vont être controllés par le VTherm. Seuls les entités de type `switch` ou `input_boolean` sont acceptées.
|
||||
Dans la "liste des équipements à contrôler" vous mettez les switchs qui vont être controllés par le VTherm. Seuls les entités de type `switch` ou `input_boolean` ou `select` ou `input_select` ou `climate` sont acceptées.
|
||||
|
||||
Si un des sous-jacents n'est pas un `switch` alors la personnalisation des commandes est obligatoires. Par défaut pour les `switch` les commandes sont les commandes classique d'allumage / extinction du switch (`turn_on`, `turn_off`)
|
||||
|
||||
L'algorithme à utiliser est aujourd'hui limité à TPI est disponible. Voir [algorithme](#algorithme).
|
||||
Si plusieurs entités de type sont configurées, la thermostat décale les activations afin de minimiser le nombre de switch actif à un instant t. Ca permet une meilleure répartition de la puissance puisque chaque radiateur va s'allumer à son tour.
|
||||
@@ -53,3 +57,37 @@ Il est possible de choisir un thermostat over switch qui commande une climatisat
|
||||
|
||||
Si votre équipement est commandé par un fil pilote avec un diode, vous aurez certainement besoin de cocher la case "Inverser la case". Elle permet de mettre le switch à `On` lorsqu'on doit étiendre l'équipement et à `Off` lorsqu'on doit l'allumer. Les temps de cycle sont donc inversés avec cette option.
|
||||
|
||||
### La personnalisation des commandes
|
||||
|
||||
Cette section de configuration permet de personnaliser les commandes d'allumage et d'extinction envoyée à l'équipement sous-jacent,
|
||||
Ces commandes sont obligatoires si un des sous-jacents n'est pas un `switch` (pour les `switchs` les commandes d'allumage/extinction classiques sont utilisées).
|
||||
|
||||
Pour personnaliser les commande, cliquez sur `Ajouter` en bas de page sur les commandes d'allumage et sur les commandes d'extinction :
|
||||
|
||||

|
||||
|
||||
et donner la commande d'allumage et d'exinction avec le format `commande[/attribut[:valeur]]`.
|
||||
Les commandes possibles dépendent du type de sous-jacents :
|
||||
|
||||
| type de sous-jacent | commandes d'allumage possibles | commandes d'extinction possibles | S'applique à |
|
||||
| --------------------------- | ------------------------------------- | ----------------------------------- | ------------------------------ |
|
||||
| `switch` ou `input_boolean` | `turn_on` | `turn_off` | tous les switchs |
|
||||
| `select` ou `input_select` | `select_option/option:comfort` | `set_option/option:frost` | Nodon SIN-4-FP-21 et assimilés |
|
||||
| `climate` (hvac_mode) | `set_hvac_mode/hvac_mode:heat` | `set_hvac_mode/hvac_mode:off` | eCosy (via Tuya Local) |
|
||||
| `climate` (preset) | `set_preset_mode/preset_mode:comfort` | `set_preset_mode/preset_mode:frost` | Heatzy |
|
||||
|
||||
Evidemment, tous ces exemples peuvent être adaptés à votre cas.
|
||||
|
||||
Exemple pour un Nodon SIN-4-FP-21 :
|
||||

|
||||
|
||||
Cliquez sur valider pour accepter les modifications.
|
||||
|
||||
Si l'erreur suivante se produit :
|
||||
|
||||
> La configuration de la personnalisation des commandes est incorrecte. Elle est obligatoire pour les sous-jacents non switch et le format doit être 'service_name[/attribut:valeur]'. Plus d'informations dans le README.
|
||||
|
||||
Cela signifie que une des commandes saisies est invalide. Les règles à respecter sont les suivantes :
|
||||
1. chaque commande doit avoir le format `commande[/attribut[:valeur]]` (ex: `select_option/option:comfort` ou `turn_on`) sans blanc et sans caractères spéciaux sauf '_',
|
||||
2. il doit y avoir autant de commandes qu'il y a de sous-jacents déclarés sauf si tous les sous-jacents sont des `switchs` auquel cas il n'est pas nécessaire de paramétrer les commandes,
|
||||
3. si plusieurs sous-jacents sont configurés, les commandes doivent être dans le même ordre. Le nombre de commandes d'allumage doit être égal au nombre de commandes d'extinction et de sous-jacents (dans l'ordre donc). Il est possible de mettre des sous-jacents de type différent. À partir du moment où un sous-jacent n'est pas un `switch`, il faut paramétrer toutes les commandes de tous les sous-jacents y compris des éventuels switchs.
|
||||
@@ -22,11 +22,12 @@ L'installation doit ressembler à celle pour le VTherm `over_switch` sauf que l'
|
||||
|
||||
## Configuration
|
||||
|
||||
Cliquer sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration. Vous mettez les entités `numnber` ou `input_number`qui vont être controllés par le VTherm :
|
||||
Configurez d'abord les paramètres principaux et communs à tous les _VTherm_ (cf. [paramètres principaux](base-attributes.md)).
|
||||
Ensuite cliquez sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration. Vous mettez les entités `numnber` ou `input_number`qui vont être controllés par le VTherm :
|
||||
|
||||

|
||||
|
||||
L'algorithme à utiliser est aujourd'hui limité à TPI est disponible. Voir [algorithme](#algorithme).
|
||||
|
||||
Il est possible de choisir un thermostat over valve qui commande une climatisation en cochant la case "AC Mode". Dans ce cas, seul le mode refroidissement sera visible.
|
||||
Il est possible de choisir un thermostat `over-valve` qui commande une climatisation en cochant la case "AC Mode". Dans ce cas, seul le mode refroidissement sera visible.
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ Ce thermostat peut piloter 3 types d'équipements :
|
||||
2. une sonde de température pour la pièce (ou un input_number),
|
||||
3. un capteur de température externe (pensez à l'intégration météo si vous n'en avez pas)
|
||||
2. un autre thermostat qui a ses propres modes de fonctionnement (nommé ```thermostat_over_climate```). Pour ce type de thermostat la configuration minimale nécessite :
|
||||
1. un équipement - comme une climatisation, une valve thermostatique - qui est pilotée par sa propre entity de type ```climate```,
|
||||
3. un équipement qui peut prendre une valeur de 0 à 100% (nommée ```thermostat_over_valve```). A 0 le chauffage est coupé, 100% il est ouvert à fond. Ce type permet de piloter une valve thermostatique (cf. valve Shelly) qui expose une entité de type `number.` permetttant de piloter directement l'ouverture de la vanne. Versatile Thermostat régule la température de la pièce en jouant sur le pourcentage d'ouverture, à l'aide des capteurs de température intérieur et extérieur en utilisant l'algorithme TPI décrit ci-dessous.
|
||||
1. un équipement - comme une climatisation, une vanne thermostatique - qui est pilotée par sa propre entity de type ```climate```,
|
||||
3. un équipement qui peut prendre une valeur de 0 à 100% (nommée ```thermostat_over_valve```). A 0 le chauffage est coupé, 100% il est ouvert à fond. Ce type permet de piloter une vanne thermostatique (cf. TRV Shelly) qui expose une entité de type `number.` permetttant de piloter directement l'ouverture de la vanne. Versatile Thermostat régule la température de la pièce en jouant sur le pourcentage d'ouverture, à l'aide des capteurs de température intérieur et extérieur en utilisant l'algorithme TPI décrit ci-dessous.
|
||||
|
||||
Le type `over_climate` vous permet d'ajouter à votre équipement existant toutes les fonctionnalités apportées par VersatileThermostat. L'entité `climate` VersatileThermostat contrôlera votre entité climate sous-jacente, l'éteindra si les fenêtres sont ouvertes, la fera passer en mode Eco si personne n'est présent, etc. Voir [ici] (#pourquoi-un-nouveau-thermostat-implémentation). Pour ce type de thermostat, tous les cycles de chauffage sont contrôlés par l'entité climate sous-jacente et non par le thermostat polyvalent lui-même. Une fonction facultative d'auto-régulation permet au Versatile Thermostat d'ajuster la température donnée en consigne au sous-jacent afin d'atteindre la consigne.
|
||||
|
||||
@@ -33,11 +33,12 @@ Toutes ces fonctions sont configurables de façon centralisée ou individuelle e
|
||||
|
||||
## Incompatibilités
|
||||
Certains thermostat de type TRV sont réputés incompatibles avec le Versatile Thermostat. C'est le cas des vannes suivantes :
|
||||
1. les vannes POPP de Danfoss avec retour de température. Il est impossible d'éteindre cette vanne et elle s'auto-régule d'elle-même causant des conflits avec le VTherm,
|
||||
1. les vannes POPP de Danfoss avec retour de température. Il est impossible d'éteindre cette vanne et elle s'auto-régule d'elle-même causant des conflits avec le _VTherm_,
|
||||
2. Les thermostats « Homematic » (et éventuellement Homematic IP) sont connus pour rencontrer des problèmes avec le Versatile Thermostat en raison des limitations du protocole RF sous-jacent. Ce problème se produit particulièrement lorsque vous essayez de contrôler plusieurs thermostats Homematic à la fois dans une seule instance de VTherm. Afin de réduire la charge du cycle de service, vous pouvez par ex. regroupez les thermostats avec des procédures spécifiques à Homematic (par exemple en utilisant un thermostat mural) et laissez Versatile Thermostat contrôler uniquement le thermostat mural directement. Une autre option consiste à contrôler un seul thermostat et à propager les changements de mode CVC et de température par un automatisme,
|
||||
3. les thermostats de type Heatzy qui ne supportent pas les commandes de type set_temperature
|
||||
4. les thermostats de type Rointe ont tendance a se réveiller tout seul. Le reste fonctionne normalement.
|
||||
5. les TRV de type Aqara SRTS-A01 et MOES TV01-ZB qui n'ont pas le retour d'état `hvac_action` permettant de savoir si elle chauffe ou pas. Donc les retours d'état sont faussés, le reste à l'air fonctionnel.
|
||||
6. La clim Airwell avec l'intégration "Midea AC LAN". Si 2 commandes de VTherm sont trop rapprochées, la clim s'arrête d'elle même.
|
||||
7. Les climates basés sur l'intégration Overkiz ne fonctionnent pas. Il parait impossible d'éteindre ni même de changer la température sur ces systèmes.
|
||||
8. Les systèmes de chauffage basés sur Netatmo fonctionnent mal. Les plannings Netatmo viennent en concurrence de la programmation _VTherm_. Les sous-jacents Netatmo repasse en mode `Auto` tout le temps et ce mode est très mal géré avec _VTherm_ qui ne peut pas savoir si le sysème chauffe ou refroidit et donc quel algorithme choisir. Certains ont quand même réussi à le faire fonctionner avec un switch virtuel entre le _VTherm_ et le sous-jacent mais sans garantie de stabilité. Un exemple est donné dans la section [dépannage](troubleshooting.md)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
> * **Release 5.3** : Ajout d'une fonction de pilotage d'une chaudière centrale [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - plus d'infos ici: [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale). Ajout de la possibilité de désactiver le mode sécurité pour le thermomètre extérieur [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
|
||||
> * **Release 5.2** : Ajout d'un `central_mode` permettant de piloter tous les VTherms de façon centralisée [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158).
|
||||
> * **Release 5.1** : Limitation des valeurs envoyées aux valves et au température envoyées au climate sous-jacent.
|
||||
> * **Release 5.1** : Limitation des valeurs envoyées aux vannes et au température envoyées au climate sous-jacent.
|
||||
> * **Release 5.0** : Ajout d'une configuration centrale permettant de mettre en commun les attributs qui peuvent l'être [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
|
||||
> * **Release 4.3** : Ajout d'un mode auto-fan pour le type `over_climate` permettant d'activer la ventilation si l'écart de température est important [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223).
|
||||
> * **Release 4.2** : Le calcul de la pente de la courbe de température se fait maintenant en °/heure et non plus en °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction de la détection automatique des ouvertures par l'ajout d'un lissage de la courbe de température .
|
||||
|
||||
@@ -32,8 +32,8 @@ Elle permet de configurer les entités de contrôle de la vanne :
|
||||
Vous devez donner :
|
||||
1. autant d'entités de contrôle d'ouverture de la vanne qu'il y a de sous-jacents et dans le même odre. Ces paramètres sont obligatoires,
|
||||
2. autant d'entités de calibrage du décalage de température qu'il y a de sous-jacents et dans le même ordre. Ces paramètres sont facultatifs ; ils doivent être tous founis ou aucun,
|
||||
3. autant d'entités de de contrôile du taux de fermture qu'il y a de sous-jacents et dans le même ordre. Ces paramètres sont facultatifs ; ils doivent être tous founis ou aucun,
|
||||
4. une liste de valeurs minimales d'ouverture de la vanne lorsqu'elle doit être ouverte. Ce champ est une liste d'entier. Si la vanne doit être ouverte, elle le sera au minimum avec cette valeur d'ouverture. Cela permet de laisser passer suffisamment d'eau lorsqu'elle doit être ouverte.
|
||||
3. autant d'entités de de contrôile du taux de fermture qu'il y a de sous-jacents et dans le même ordre. Ces paramètres sont facultatifs ; ils doivent être tous founis ou aucun. Pour les Sonoff TRVZB, ils ne doivent pas être fournis,
|
||||
4. une liste de valeurs minimales d'ouverture de la vanne lorsqu'elle doit être ouverte. Ce champ est une liste d'entier. Si la vanne doit être ouverte, elle le sera au minimum avec cette valeur d'ouverture, sinon elle sera totalement close (0). Cela permet de laisser passer suffisamment d'eau lorsqu'elle doit être ouverte mais garde la fermeeture complète si il n'y a pas besoin de chauffer.
|
||||
|
||||
L'algorithme de calcul du taux d'ouverture est basé sur le _TPI_ qui est décrit [ici](algorithms.md). C'est le même algorithme qui est utilisé pour les _VTherm_ `over_switch` et `over_valve`.
|
||||
|
||||
@@ -152,4 +152,4 @@ Pour que les modifications soient prises en compte, il faut soit **relancer tota
|
||||
|
||||
## Synthèse de l'algorithme d'auto-régulation
|
||||
|
||||
Une synthèse de l'algorithme d'auto-régulation est décrite [ici](algorithms.md#lalgorithme-dauto-régulation-sans-contrôle-de-la-vanne)
|
||||
Une synthèse de l'algorithme d'auto-régulation est décrite [ici](algorithms.md#lalgorithme-dauto-régulation-sans-contrôle-de-la-vanne)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- [Dépannages](#dépannages)
|
||||
- [Utilisation d'un Heatzy](#utilisation-dun-heatzy)
|
||||
- [Utilisation d'un radiateur avec un fil pilote (Nodon SIN-4-FP-21)](#utilisation-dun-radiateur-avec-un-fil-pilote-nodon-sin-4-fp-21)
|
||||
- [Utilisation d'un système Netatmo](#utilisation-dun-système-netatmo)
|
||||
- [Seul le premier radiateur chauffe](#seul-le-premier-radiateur-chauffe)
|
||||
- [Le radiateur chauffe alors que la température de consigne est dépassée ou ne chauffe pas alors que la température de la pièce est bien en-dessous de la consigne](#le-radiateur-chauffe-alors-que-la-température-de-consigne-est-dépassée-ou-ne-chauffe-pas-alors-que-la-température-de-la-pièce-est-bien-en-dessous-de-la-consigne)
|
||||
- [Type `over_switch` ou `over_valve`](#type-over_switch-ou-over_valve)
|
||||
@@ -84,6 +85,15 @@ Exemple :
|
||||
```
|
||||
Un exemple plus complet est [ici](https://github.com/jmcollin78/versatile_thermostat/discussions/431#discussioncomment-11393065)
|
||||
|
||||
## Utilisation d'un système Netatmo
|
||||
Le système à base de TRV Netatmo fonctionne mal avec _VTherm_. Vous avez ici une discussion sur le fonctionnement particulier des systèmes Netatmo (en Français) : https://forum.hacf.fr/t/vannes-netatmo-et-vtherm/56063
|
||||
Cependant certains ont réussi une intégration _VTerm_ Netatmo en intégrant un switch virtuel entre _VTherm_ et le `climate` Netatmo suivant :
|
||||
```
|
||||
TODO add virtual switch code
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Seul le premier radiateur chauffe
|
||||
|
||||
En mode `over_switch` si plusieurs radiateurs sont configurés pour un même VTherm, l'alllumage va se faire de façon séquentiel pour lisser au plus possible les pics de consommation.
|
||||
@@ -210,4 +220,9 @@ Attention, en mode debug Versatile Thermostat est très verbeux et peut vite ral
|
||||
|
||||
## VTherm ne suit pas les changements de consigne faits directement depuis le sous-jacents (`over_climate`)
|
||||
|
||||
Voir le détail de cette fonction [ici](over-climate.md#suivre-les-changements-de-température-du-sous-jacent).
|
||||
Voir le détail de cette fonction [ici](over-climate.md#suivre-les-changements-de-température-du-sous-jacent).
|
||||
|
||||
## VTherm passe tout seul en mode 'clim' ou en mode 'chauffage'
|
||||
|
||||
Certaine _PAC_ réversibles ont des modes qui permettent de laisser le choix à la _PAC_ de chauffer ou de réfroidir. Ces modes sont 'Auto' or 'Heat_cool' selon les marques. Ces 2 modes ne doivent pas être utilisés avec _VTherm_ car les algorithmes de _VTherm_ ont besoin de savoir si ils sont en mode chauffe ou refroidissement ce que ne permettent pas ces modes.
|
||||
Vous devez donc utiliser uniquement les modes : `Heat`, `Cool`, `Off` ou `Fan` éventuellement (bien que fan n'a aucun sens avec _Vtherm)
|
||||
|
||||
BIN
images/new-icon.png
Normal file
BIN
images/new-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -87,7 +87,6 @@ FULL_SWITCH_CONFIG = (
|
||||
| MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG
|
||||
| MOCK_TH_OVER_SWITCH_TYPE_CONFIG
|
||||
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
|
||||
# | MOCK_PRESETS_CONFIG
|
||||
| MOCK_FULL_FEATURES
|
||||
| MOCK_WINDOW_CONFIG
|
||||
| MOCK_MOTION_CONFIG
|
||||
@@ -111,12 +110,7 @@ FULL_SWITCH_AC_CONFIG = (
|
||||
)
|
||||
|
||||
PARTIAL_CLIMATE_CONFIG = (
|
||||
MOCK_TH_OVER_CLIMATE_USER_CONFIG
|
||||
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
|
||||
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
|
||||
| MOCK_TH_OVER_CLIMATE_TYPE_CONFIG
|
||||
# | MOCK_PRESETS_CONFIG
|
||||
| MOCK_ADVANCED_CONFIG
|
||||
MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | MOCK_ADVANCED_CONFIG
|
||||
)
|
||||
|
||||
PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP = (
|
||||
@@ -124,7 +118,6 @@ PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP = (
|
||||
| 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
|
||||
)
|
||||
|
||||
@@ -133,24 +126,17 @@ PARTIAL_CLIMATE_NOT_REGULATED_CONFIG = (
|
||||
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
|
||||
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
|
||||
| MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG
|
||||
# | MOCK_PRESETS_CONFIG
|
||||
| MOCK_ADVANCED_CONFIG
|
||||
)
|
||||
|
||||
PARTIAL_CLIMATE_AC_CONFIG = (
|
||||
MOCK_TH_OVER_CLIMATE_USER_CONFIG
|
||||
| MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG
|
||||
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
|
||||
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
|
||||
# | MOCK_PRESETS_CONFIG
|
||||
| MOCK_ADVANCED_CONFIG
|
||||
MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_ADVANCED_CONFIG
|
||||
)
|
||||
|
||||
FULL_4SWITCH_CONFIG = (
|
||||
MOCK_TH_OVER_4SWITCH_USER_CONFIG
|
||||
| MOCK_TH_OVER_4SWITCH_TYPE_CONFIG
|
||||
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
|
||||
# | MOCK_PRESETS_CONFIG
|
||||
| MOCK_WINDOW_CONFIG
|
||||
| MOCK_MOTION_CONFIG
|
||||
| MOCK_POWER_CONFIG
|
||||
@@ -1037,6 +1023,16 @@ default_temperatures_ac_away = {
|
||||
"boost_ac_away": 23.1,
|
||||
}
|
||||
|
||||
default_temperatures_ac = {
|
||||
"frost": 7.0,
|
||||
"eco": 17.0,
|
||||
"comfort": 19.0,
|
||||
"boost": 21.0,
|
||||
"eco_ac": 27.0,
|
||||
"comfort_ac": 25.0,
|
||||
"boost_ac": 23.0,
|
||||
}
|
||||
|
||||
default_temperatures_away = {
|
||||
"frost": 7.0,
|
||||
"eco": 17.0,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pylint: disable=wildcard-import, unused-wildcard-import, unused-argument, line-too-long, protected-access
|
||||
|
||||
""" Test the normal start of a Thermostat """
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, PropertyMock
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -179,7 +179,7 @@ async def test_overpowering_binary_sensors(
|
||||
)
|
||||
# fmt:off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
# fmt: on
|
||||
await send_power_change_event(entity, 150, now)
|
||||
await send_max_power_change_event(entity, 100, now)
|
||||
|
||||
@@ -398,7 +398,8 @@ async def test_bug_407(
|
||||
assert entity.target_temperature == 19
|
||||
assert mock_service_call.call_count >= 1
|
||||
|
||||
# 3. if heater is stopped (is_device_active==False) and power is over max, then overpowering should be started
|
||||
# 3. Evenif heater is stopped (is_device_active==False) and power is over max, then overpowering should be started
|
||||
# due to check before start heating
|
||||
side_effects.add_or_update_side_effect("sensor.the_power_sensor", State("sensor.the_power_sensor", 150))
|
||||
with patch(
|
||||
"homeassistant.core.ServiceRegistry.async_call"
|
||||
@@ -413,18 +414,18 @@ async def test_bug_407(
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
# change preset to Boost
|
||||
# change preset to Comfort
|
||||
await entity.async_set_preset_mode(PRESET_COMFORT)
|
||||
# waits that the heater starts
|
||||
# waits the eventual heater starts
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# simulate a refresh for central power (not necessary)
|
||||
await do_central_power_refresh(hass)
|
||||
# simulate a refresh for central power (not necessary because it is checked before start)
|
||||
# await do_central_power_refresh(hass)
|
||||
|
||||
assert entity.power_manager.is_overpowering_detected is False
|
||||
assert entity.power_manager.is_overpowering_detected is True
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_COMFORT
|
||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||
assert entity.preset_mode is PRESET_POWER
|
||||
assert entity.power_manager.overpowering_state is STATE_ON
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# pylint: disable=protected-access, unused-argument, line-too-long
|
||||
""" Test the Central Power management """
|
||||
import asyncio
|
||||
from unittest.mock import patch, AsyncMock, MagicMock, PropertyMock
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
@@ -10,6 +11,14 @@ from custom_components.versatile_thermostat.feature_power_manager import (
|
||||
from custom_components.versatile_thermostat.central_feature_power_manager import (
|
||||
CentralFeaturePowerManager,
|
||||
)
|
||||
|
||||
from custom_components.versatile_thermostat.thermostat_switch import (
|
||||
ThermostatOverSwitch,
|
||||
)
|
||||
from custom_components.versatile_thermostat.thermostat_climate import (
|
||||
ThermostatOverClimate,
|
||||
)
|
||||
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
@@ -700,3 +709,150 @@ async def test_central_power_manager_max_power_event(
|
||||
|
||||
assert central_power_manager.current_max_power == expected_power
|
||||
assert mock_calculate_shedding.call_count == nb_call
|
||||
|
||||
|
||||
async def test_central_power_manager_start_vtherm_power(hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager):
|
||||
"""Tests the central power start VTherm power. The objective is to starts VTherm until the power max is exceeded"""
|
||||
|
||||
temps = {
|
||||
"eco": 17,
|
||||
"comfort": 18,
|
||||
"boost": 19,
|
||||
}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverSwitchMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverSwitchMockName",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: True,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.01,
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SAFETY_DELAY_MIN: 5,
|
||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_DEVICE_POWER: 1000,
|
||||
CONF_PRESET_POWER: 12,
|
||||
},
|
||||
)
|
||||
|
||||
entity: ThermostatOverSwitch = await create_thermostat(hass, entry, "climate.theoverswitchmockname", temps)
|
||||
assert entity
|
||||
|
||||
now: datetime = NowClass.get_now(hass)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
central_power_manager = VersatileThermostatAPI.get_vtherm_api().central_power_manager
|
||||
assert central_power_manager
|
||||
|
||||
side_effects = SideEffects(
|
||||
{
|
||||
"sensor.the_power_sensor": State("sensor.the_power_sensor", 1000),
|
||||
"sensor.the_max_power_sensor": State("sensor.the_max_power_sensor", 2100),
|
||||
},
|
||||
State("unknown.entity_id", "unknown"),
|
||||
)
|
||||
|
||||
# 1. Make the heater heats
|
||||
# fmt: off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=False):
|
||||
# fmt: on
|
||||
# make the heater heats
|
||||
await send_power_change_event(entity, 1000, now)
|
||||
await send_max_power_change_event(entity, 2100, now)
|
||||
|
||||
await send_temperature_change_event(entity, 15, now)
|
||||
await send_ext_temperature_change_event(entity, 1, now)
|
||||
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
||||
assert entity.target_temperature == 19
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# the power of Vtherm should have been added
|
||||
assert central_power_manager.started_vtherm_total_power == 1000
|
||||
|
||||
# 2. Check that another heater cannot heat
|
||||
entry2 = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverClimateMockName2",
|
||||
unique_id="uniqueId2",
|
||||
data={
|
||||
CONF_NAME: "TheOverClimateMockName2",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: True,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_UNDERLYING_LIST: ["switch.mock_climate"],
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SAFETY_DELAY_MIN: 5,
|
||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_DEVICE_POWER: 150,
|
||||
CONF_PRESET_POWER: 12,
|
||||
},
|
||||
)
|
||||
|
||||
entity2: ThermostatOverClimate = await create_thermostat(hass, entry2, "climate.theoverclimatemockname2", temps)
|
||||
assert entity2
|
||||
|
||||
fake_underlying_climate = MockClimate(
|
||||
hass=hass,
|
||||
unique_id="mockUniqueId",
|
||||
name="MockClimateName",
|
||||
)
|
||||
|
||||
# fmt: off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=False), \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",return_value=fake_underlying_climate):
|
||||
# fmt: on
|
||||
# make the heater heats
|
||||
await entity2.async_set_preset_mode(PRESET_COMFORT)
|
||||
assert entity2.preset_mode is PRESET_COMFORT
|
||||
assert entity2.power_manager.overpowering_state is STATE_UNKNOWN
|
||||
assert entity2.target_temperature == 18
|
||||
await entity2.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity2.hvac_mode is HVACMode.HEAT
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# the power of Vtherm should have not been added (cause it has not started) and the entity2 should be shedding
|
||||
assert central_power_manager.started_vtherm_total_power == 1000
|
||||
|
||||
|
||||
assert entity2.power_manager.overpowering_state is STATE_ON
|
||||
|
||||
# 3. sends a new power sensor event
|
||||
await send_max_power_change_event(entity, 2150, now)
|
||||
# No change
|
||||
assert central_power_manager.started_vtherm_total_power == 1000
|
||||
|
||||
await send_power_change_event(entity, 1010, now)
|
||||
assert central_power_manager.started_vtherm_total_power == 0
|
||||
|
||||
@@ -287,6 +287,7 @@ async def test_user_config_flow_over_switch(
|
||||
CONF_USE_POWER_FEATURE: True,
|
||||
CONF_USE_PRESENCE_FEATURE: True,
|
||||
CONF_USE_CENTRAL_BOILER_FEATURE: False,
|
||||
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
|
||||
}
|
||||
)
|
||||
assert result["result"]
|
||||
@@ -492,9 +493,7 @@ async def test_user_config_flow_over_climate(
|
||||
)
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result.get("errors") is None
|
||||
assert result[
|
||||
"data"
|
||||
] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | {
|
||||
assert result["data"] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | {
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 10,
|
||||
CONF_SAFETY_DELAY_MIN: 5,
|
||||
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
|
||||
@@ -517,6 +516,7 @@ async def test_user_config_flow_over_climate(
|
||||
CONF_USED_BY_CENTRAL_BOILER: False,
|
||||
CONF_USE_CENTRAL_MODE: False,
|
||||
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
||||
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
|
||||
}
|
||||
assert result["result"]
|
||||
assert result["result"].domain == DOMAIN
|
||||
@@ -1378,6 +1378,7 @@ async def test_user_config_flow_over_switch_bug_552_tpi(
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_USE_CENTRAL_BOILER_FEATURE: False,
|
||||
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
|
||||
}
|
||||
)
|
||||
assert result["result"]
|
||||
@@ -1681,9 +1682,7 @@ async def test_user_config_flow_over_climate_valve(
|
||||
)
|
||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert result.get("errors") is None
|
||||
assert result[
|
||||
"data"
|
||||
] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | {
|
||||
assert result["data"] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | {
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 10,
|
||||
CONF_SAFETY_DELAY_MIN: 5,
|
||||
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
|
||||
@@ -1717,6 +1716,7 @@ async def test_user_config_flow_over_climate_valve(
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.1,
|
||||
CONF_MIN_OPENING_DEGREES: "10, 20,0",
|
||||
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
|
||||
}
|
||||
assert result["result"]
|
||||
assert result["result"].domain == DOMAIN
|
||||
|
||||
@@ -347,22 +347,17 @@ async def test_motion_management_time_not_enough(
|
||||
assert entity.presence_state == STATE_ON
|
||||
|
||||
# 2. starts detecting motion with time not enough
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=False
|
||||
) as mock_condition, patch(
|
||||
"homeassistant.core.StateMachine.get",
|
||||
return_value=State(
|
||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
||||
),
|
||||
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
try_condition = await send_motion_change_event(
|
||||
@@ -387,14 +382,11 @@ async def test_motion_management_time_not_enough(
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# starts detecting motion with time enough this time
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -415,22 +407,17 @@ async def test_motion_management_time_not_enough(
|
||||
assert entity.presence_state == STATE_ON
|
||||
|
||||
# stop detecting motion with off delay too low
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=False
|
||||
) as mock_condition, patch(
|
||||
"homeassistant.core.StateMachine.get",
|
||||
return_value=State(
|
||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
||||
),
|
||||
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=2)
|
||||
try_condition = await send_motion_change_event(
|
||||
@@ -454,14 +441,11 @@ async def test_motion_management_time_not_enough(
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# stop detecting motion with off delay enough long
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -562,14 +546,11 @@ async def test_motion_management_time_enough_and_presence(
|
||||
assert entity.presence_state == "on"
|
||||
|
||||
# starts detecting motion
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -590,14 +571,11 @@ async def test_motion_management_time_enough_and_presence(
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# stop detecting motion with confirmation of stop
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -693,14 +671,11 @@ async def test_motion_management_time_enough_and_not_presence(
|
||||
assert entity.presence_state == STATE_OFF
|
||||
|
||||
# starts detecting motion
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -722,14 +697,11 @@ async def test_motion_management_time_enough_and_not_presence(
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# stop detecting motion with confirmation of stop
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -826,14 +798,11 @@ async def test_motion_management_with_stop_during_condition(
|
||||
assert entity.presence_state == STATE_OFF
|
||||
|
||||
# starts detecting motion
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -959,12 +928,11 @@ async def test_motion_management_with_stop_during_condition_last_state_on(
|
||||
# 1. starts detecting motion but the sensor is off
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
||||
"homeassistant.core.StateMachine.get",
|
||||
return_value=State(
|
||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
||||
),
|
||||
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=5)
|
||||
try_condition1 = await send_motion_change_event(
|
||||
@@ -982,12 +950,11 @@ async def test_motion_management_with_stop_during_condition_last_state_on(
|
||||
# 2. starts detecting motion but the sensor is on
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
||||
"homeassistant.core.StateMachine.get",
|
||||
return_value=State(
|
||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_ON
|
||||
),
|
||||
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_ON),
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=5)
|
||||
try_condition1 = await send_motion_change_event(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
""" Test the Multiple switch management """
|
||||
import asyncio
|
||||
from unittest.mock import patch, call, ANY
|
||||
from unittest.mock import patch, call, ANY, PropertyMock
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
@@ -84,14 +84,11 @@ async def test_one_switch_cycle(
|
||||
assert mock_is_state.call_count == 1
|
||||
|
||||
# Set temperature to a low level
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
) as mock_device_active, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.call_later",
|
||||
@@ -107,7 +104,8 @@ async def test_one_switch_cycle(
|
||||
# assert mock_heater_on.call_count == 1
|
||||
assert mock_heater_on.call_count == 0
|
||||
# There is no check if active
|
||||
assert mock_device_active.call_count == 0
|
||||
# don't work with PropertyMock
|
||||
# assert mock_device_active.call_count == 0
|
||||
|
||||
# 4 calls dispatched along the cycle
|
||||
assert mock_call_later.call_count == 1
|
||||
@@ -119,14 +117,11 @@ async def test_one_switch_cycle(
|
||||
|
||||
# Set a temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
) as mock_device_active:
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
@@ -141,14 +136,11 @@ async def test_one_switch_cycle(
|
||||
|
||||
# Set another temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active:
|
||||
await send_temperature_change_event(entity, 18.1, event_timestamp)
|
||||
@@ -176,14 +168,11 @@ async def test_one_switch_cycle(
|
||||
|
||||
# Simulate the end of heater on cycle
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active:
|
||||
await entity.underlying_entity(
|
||||
@@ -201,14 +190,11 @@ async def test_one_switch_cycle(
|
||||
|
||||
# Simulate the start of heater on cycle
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active:
|
||||
await entity.underlying_entity(
|
||||
@@ -306,14 +292,11 @@ async def test_multiple_switchs(
|
||||
)
|
||||
|
||||
# Set temperature to a low level
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
) as mock_device_active, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.call_later",
|
||||
@@ -329,7 +312,8 @@ async def test_multiple_switchs(
|
||||
# assert mock_heater_on.call_count == 1
|
||||
assert mock_heater_on.call_count == 0
|
||||
# There is no check if active
|
||||
assert mock_device_active.call_count == 0
|
||||
# don't work with PropertyMock
|
||||
# assert mock_device_active.call_count == 0
|
||||
|
||||
# 4 calls dispatched along the cycle
|
||||
assert mock_call_later.call_count == 4
|
||||
@@ -344,14 +328,11 @@ async def test_multiple_switchs(
|
||||
|
||||
# Set a temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
) as mock_device_active:
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
@@ -818,7 +799,7 @@ async def test_multiple_switch_power_management(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
#fmt: on
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
@@ -1233,3 +1233,111 @@ async def test_manual_hvac_off_should_take_the_lead_over_auto_start_stop(
|
||||
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_MANUAL
|
||||
assert vtherm._saved_hvac_mode == HVACMode.OFF
|
||||
assert mock_send_event.call_count == 0 # nothing have change
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_underlying_from_comes_back_to_life(
|
||||
hass: HomeAssistant,
|
||||
skip_hass_states_is_state,
|
||||
skip_turn_on_off_heater,
|
||||
skip_send_event,
|
||||
):
|
||||
"""Test that when a underlying climate comes back to life (from unkwown or unavailable) the last state is send"""
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverClimateMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverClimateMockName",
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_AUTO_START_STOP_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SAFETY_DELAY_MIN: 5,
|
||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_NONE,
|
||||
CONF_AC_MODE: True,
|
||||
}, # 5 minutes security delay
|
||||
)
|
||||
|
||||
# Underlying is in HEAT mode but should be shutdown at startup
|
||||
fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {}, HVACMode.COOL, HVACAction.COOLING)
|
||||
|
||||
# 1. initialize the vtherm in COOL with Boost
|
||||
# fmt: off
|
||||
with patch("custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",return_value=fake_underlying_climate) as mock_find_climate:
|
||||
# fmt: on
|
||||
entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname", temps=default_temperatures_ac)
|
||||
|
||||
assert entity
|
||||
assert entity.name == "TheOverClimateMockName"
|
||||
assert entity.is_over_climate is True
|
||||
|
||||
# Set hvac_mode to COOL
|
||||
await entity.async_set_hvac_mode(HVACMode.COOL)
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
|
||||
# it is very hot today
|
||||
await send_temperature_change_event(entity, 27, now, False)
|
||||
await send_ext_temperature_change_event(entity, 35, now, False)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entity.hvac_mode is HVACMode.COOL
|
||||
# because in MockClimate HVACAction is HEATING if hvac_mode is not set
|
||||
assert entity.hvac_action is HVACAction.COOLING
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.target_temperature == 23
|
||||
|
||||
|
||||
# 2. send under state event comes back from life
|
||||
# fmt: off
|
||||
with patch("custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode") as mock_underlying_set_hvac_mode, \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_temperature") as mock_underlying_set_temperature:
|
||||
# fmt: on
|
||||
now = now + timedelta(minutes=2)
|
||||
# 2. Change the target temp of underlying thermostat at now -> the event will be disgarded because to fast (to avoid loop cf issue 121)
|
||||
await send_climate_change_event_with_temperature(
|
||||
entity,
|
||||
HVACMode.HEAT,
|
||||
STATE_UNKNOWN,
|
||||
HVACAction.OFF,
|
||||
STATE_UNKNOWN,
|
||||
now,
|
||||
entity.min_temp + 1,
|
||||
True,
|
||||
"climate.mock_climate", # the underlying climate entity id
|
||||
)
|
||||
|
||||
assert mock_underlying_set_hvac_mode.call_count == 1
|
||||
mock_underlying_set_hvac_mode.assert_has_calls(
|
||||
[
|
||||
call.set_hvac_mode(HVACMode.COOL),
|
||||
]
|
||||
)
|
||||
|
||||
assert mock_underlying_set_temperature.call_count == 1
|
||||
mock_underlying_set_temperature.assert_has_calls(
|
||||
[
|
||||
call.set_temperature(23, 30, 15),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# Nothing should have changed
|
||||
assert entity.target_temperature == 23
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.hvac_mode is HVACMode.COOL
|
||||
|
||||
@@ -523,7 +523,7 @@ async def test_power_management_hvac_on(
|
||||
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
# fmt: on
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
@@ -825,3 +825,135 @@ async def test_power_management_energy_over_climate(
|
||||
# Test the re-increment
|
||||
entity.incremente_energy()
|
||||
assert entity.total_energy == 2 * 100 * 3.0 / 60
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_power_management_turn_off_while_shedding(hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager):
|
||||
"""Test the Power management and that we can turn off a Vtherm that
|
||||
is in overpowering state"""
|
||||
|
||||
temps = {
|
||||
"eco": 17,
|
||||
"comfort": 18,
|
||||
"boost": 19,
|
||||
}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverSwitchMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverSwitchMockName",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: True,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.01,
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SAFETY_DELAY_MIN: 5,
|
||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_DEVICE_POWER: 100,
|
||||
CONF_PRESET_POWER: 12,
|
||||
},
|
||||
)
|
||||
|
||||
entity: ThermostatOverSwitch = await create_thermostat(hass, entry, "climate.theoverswitchmockname", temps)
|
||||
assert entity
|
||||
|
||||
now: datetime = NowClass.get_now(hass)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
tpi_algo = entity._prop_algorithm
|
||||
assert tpi_algo
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
||||
assert entity.target_temperature == 19
|
||||
|
||||
# make the heater heats
|
||||
await send_temperature_change_event(entity, 15, now)
|
||||
await send_ext_temperature_change_event(entity, 1, now)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entity.power_percent > 0
|
||||
|
||||
side_effects = SideEffects(
|
||||
{
|
||||
"sensor.the_power_sensor": State("sensor.the_power_sensor", 50),
|
||||
"sensor.the_max_power_sensor": State("sensor.the_max_power_sensor", 49),
|
||||
},
|
||||
State("unknown.entity_id", "unknown"),
|
||||
)
|
||||
# # fmt:off
|
||||
# with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()):
|
||||
# # fmt: on
|
||||
# await send_power_change_event(entity, 50, datetime.now())
|
||||
# # Send power max mesurement
|
||||
# now = now + timedelta(seconds=30)
|
||||
# VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
# await send_max_power_change_event(entity, 300, datetime.now())
|
||||
#
|
||||
# assert entity.power_manager.is_overpowering_detected is False
|
||||
# # All configuration is complete and power is < power_max
|
||||
# assert entity.preset_mode is PRESET_BOOST
|
||||
# assert entity.power_manager.overpowering_state is STATE_OFF
|
||||
|
||||
# 1. Set VTherm to overpowering
|
||||
# Send power max mesurement too low and HVACMode is on and device is active
|
||||
|
||||
#
|
||||
#
|
||||
# fmt:off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"), \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
# fmt: on
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
await send_max_power_change_event(entity, 49, now)
|
||||
assert entity.power_manager.is_overpowering_detected is True
|
||||
# All configuration is complete and power is > power_max we switch to POWER preset
|
||||
assert entity.preset_mode is PRESET_POWER
|
||||
assert entity.power_manager.overpowering_state is STATE_ON
|
||||
assert entity.target_temperature == 12
|
||||
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_heater_off.call_count == 1
|
||||
|
||||
# 2. Turn-off Vtherm
|
||||
# fmt:off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
# fmt: on
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.OFF)
|
||||
await VersatileThermostatAPI.get_vtherm_api().central_power_manager._do_immediate_shedding()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# VTherm is off and overpowering if off also
|
||||
assert entity.hvac_mode == HVACMode.OFF
|
||||
assert entity.power_manager.is_overpowering_detected is False
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||
assert entity.target_temperature == 19
|
||||
|
||||
@@ -2039,16 +2039,13 @@ async def test_bug_66(
|
||||
assert entity.window_state is STATE_UNKNOWN
|
||||
|
||||
# Open the window and let the thermostat shut down
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
):
|
||||
await send_temperature_change_event(entity, 15, now)
|
||||
@@ -2067,16 +2064,13 @@ async def test_bug_66(
|
||||
assert entity.window_state == STATE_ON
|
||||
|
||||
# Close the window but too shortly
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=False
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = now + timedelta(minutes=1)
|
||||
@@ -2090,16 +2084,13 @@ async def test_bug_66(
|
||||
assert entity.window_state == STATE_ON
|
||||
|
||||
# Reopen immediatly with sufficient time
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
try_window_condition = await send_window_change_event(
|
||||
@@ -2113,16 +2104,13 @@ async def test_bug_66(
|
||||
assert entity.hvac_mode == HVACMode.OFF
|
||||
|
||||
# Close the window but with sufficient time this time
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = now + timedelta(minutes=2)
|
||||
|
||||
Reference in New Issue
Block a user