Compare commits
5 Commits
7.1.4
...
7.1.6beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05fe2055e2 | ||
|
|
8743872c09 | ||
|
|
c4a1631d29 | ||
|
|
d1ef83f422 | ||
|
|
42ac2b0f98 |
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
|
||||||
@@ -35,6 +35,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.
|
_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
|
# Documentation
|
||||||
|
|
||||||
La documentation est maintenant découpée en plusieurs pages pour faciliter la lecture et la recherche d'informations :
|
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.
|
_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
|
# Documentation
|
||||||
|
|
||||||
The documentation is now divided into several pages for easier reading and searching:
|
The documentation is now divided into several pages for easier reading and searching:
|
||||||
|
|||||||
@@ -1067,9 +1067,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
save_state()
|
save_state()
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def async_set_preset_mode(
|
async def async_set_preset_mode(self, preset_mode: str, overwrite_saved_preset=True):
|
||||||
self, preset_mode: str, overwrite_saved_preset=True
|
|
||||||
):
|
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
|
|
||||||
# We accept a new preset when:
|
# We accept a new preset when:
|
||||||
@@ -1097,14 +1095,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.async_set_preset_mode_internal(
|
await self.async_set_preset_mode_internal(preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset)
|
||||||
preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset
|
|
||||||
)
|
|
||||||
await self.async_control_heating(force=True)
|
await self.async_control_heating(force=True)
|
||||||
|
|
||||||
async def async_set_preset_mode_internal(
|
async def async_set_preset_mode_internal(self, preset_mode: str, force=False, overwrite_saved_preset=True):
|
||||||
self, preset_mode: str, force=False, overwrite_saved_preset=True
|
|
||||||
):
|
|
||||||
"""Set new preset mode."""
|
"""Set new preset mode."""
|
||||||
_LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force)
|
_LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force)
|
||||||
if (
|
if (
|
||||||
@@ -1534,8 +1528,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
is_window_detected = self._window_manager.is_window_detected
|
is_window_detected = self._window_manager.is_window_detected
|
||||||
if new_central_mode == CENTRAL_MODE_AUTO:
|
if new_central_mode == CENTRAL_MODE_AUTO:
|
||||||
if not is_window_detected and not first_init:
|
if not is_window_detected and not first_init:
|
||||||
await self.restore_hvac_mode()
|
await self.restore_preset_mode(force=False)
|
||||||
await self.restore_preset_mode()
|
await self.restore_hvac_mode(need_control_heating=True)
|
||||||
elif is_window_detected and self.hvac_mode == HVACMode.OFF:
|
elif is_window_detected and self.hvac_mode == HVACMode.OFF:
|
||||||
# do not restore but mark the reason of off with window detection
|
# do not restore but mark the reason of off with window detection
|
||||||
self.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
|
self.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
|
||||||
@@ -1573,9 +1567,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
and HVACMode.HEAT in self.hvac_modes
|
and HVACMode.HEAT in self.hvac_modes
|
||||||
):
|
):
|
||||||
await self.async_set_hvac_mode(HVACMode.HEAT)
|
await self.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
await self.async_set_preset_mode(
|
await self.async_set_preset_mode(PRESET_FROST_PROTECTION, overwrite_saved_preset=False)
|
||||||
PRESET_FROST_PROTECTION, overwrite_saved_preset=False
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
|
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
@@ -1800,9 +1792,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
# If the changed preset is active, change the current temperature
|
# If the changed preset is active, change the current temperature
|
||||||
# Issue #119 - reload new preset temperature also in ac mode
|
# Issue #119 - reload new preset temperature also in ac mode
|
||||||
if preset.startswith(self._attr_preset_mode):
|
if preset.startswith(self._attr_preset_mode):
|
||||||
await self.async_set_preset_mode_internal(
|
await self.async_set_preset_mode_internal(preset.rstrip(PRESET_AC_SUFFIX), force=True)
|
||||||
preset.rstrip(PRESET_AC_SUFFIX), force=True
|
|
||||||
)
|
|
||||||
await self.async_control_heating(force=True)
|
await self.async_control_heating(force=True)
|
||||||
|
|
||||||
async def SERVICE_SET_SAFETY(
|
async def SERVICE_SET_SAFETY(
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
self._current_max_power: float = None
|
self._current_max_power: float = None
|
||||||
self._power_temp: float = None
|
self._power_temp: float = None
|
||||||
self._cancel_calculate_shedding_call = None
|
self._cancel_calculate_shedding_call = None
|
||||||
|
self._started_vtherm_total_power: float = None
|
||||||
# Not used now
|
# Not used now
|
||||||
self._last_shedding_date = None
|
self._last_shedding_date = None
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
and self._power_temp
|
and self._power_temp
|
||||||
):
|
):
|
||||||
self._is_configured = True
|
self._is_configured = True
|
||||||
|
self._started_vtherm_total_power = 0
|
||||||
else:
|
else:
|
||||||
_LOGGER.info("Power management is not fully configured and will be deactivated")
|
_LOGGER.info("Power management is not fully configured and will be deactivated")
|
||||||
|
|
||||||
@@ -102,6 +104,8 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
"""Handle power changes."""
|
"""Handle power changes."""
|
||||||
_LOGGER.debug("Receive new Power event")
|
_LOGGER.debug("Receive new Power event")
|
||||||
_LOGGER.debug(event)
|
_LOGGER.debug(event)
|
||||||
|
|
||||||
|
self._started_vtherm_total_power = 0
|
||||||
await self.refresh_state()
|
await self.refresh_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@@ -275,6 +279,12 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
vtherms.sort(key=cmp_to_key(cmp_temps))
|
vtherms.sort(key=cmp_to_key(cmp_temps))
|
||||||
return vtherms
|
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
|
@property
|
||||||
def is_configured(self) -> bool:
|
def is_configured(self) -> bool:
|
||||||
"""True if the FeatureManager is fully configured"""
|
"""True if the FeatureManager is fully configured"""
|
||||||
@@ -305,5 +315,10 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
"""Return the max power sensor entity id"""
|
"""Return the max power sensor entity id"""
|
||||||
return self._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):
|
def __str__(self):
|
||||||
return "CentralPowerManager"
|
return "CentralPowerManager"
|
||||||
|
|||||||
@@ -318,9 +318,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if infos.get(CONF_UNDERLYING_LIST, None) is not None and not infos.get(
|
# checks that at least one underlying is set but not it central configuration
|
||||||
CONF_UNDERLYING_LIST, None
|
if len(infos.get(CONF_UNDERLYING_LIST, [])) < 1:
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -901,10 +900,42 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
return await self.generic_step("advanced", schema, user_input, next_step)
|
return await self.generic_step("advanced", schema, user_input, next_step)
|
||||||
|
|
||||||
async def async_step_finalize(self, _):
|
async def async_step_finalize(self, _):
|
||||||
"""Should be implemented by Leaf classes"""
|
"""Finalize the creation. Should be overriden by underlyings"""
|
||||||
raise HomeAssistantError(
|
if not self._infos[CONF_USE_WINDOW_FEATURE]:
|
||||||
"async_finalize not implemented on VersatileThermostat sub-class"
|
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
|
class VersatileThermostatConfigFlow( # pylint: disable=abstract-method
|
||||||
@@ -928,9 +959,8 @@ class VersatileThermostatConfigFlow( # pylint: disable=abstract-method
|
|||||||
async def async_step_finalize(self, _):
|
async def async_step_finalize(self, _):
|
||||||
"""Finalization of the ConfigEntry creation"""
|
"""Finalization of the ConfigEntry creation"""
|
||||||
_LOGGER.debug("ConfigFlow.async_finalize")
|
_LOGGER.debug("ConfigFlow.async_finalize")
|
||||||
# Removes temporary value
|
await super().async_step_finalize(_)
|
||||||
if COMES_FROM in self._infos:
|
|
||||||
del self._infos[COMES_FROM]
|
|
||||||
return self.async_create_entry(title=self._infos[CONF_NAME], data=self._infos)
|
return self.async_create_entry(title=self._infos[CONF_NAME], data=self._infos)
|
||||||
|
|
||||||
|
|
||||||
@@ -968,37 +998,13 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
|
|
||||||
async def async_step_finalize(self, _):
|
async def async_step_finalize(self, _):
|
||||||
"""Finalization of the ConfigEntry creation"""
|
"""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(
|
_LOGGER.info(
|
||||||
"Recreating entry %s due to configuration change. New config is now: %s",
|
"Recreating entry %s due to configuration change. New config is now: %s",
|
||||||
self.config_entry.entry_id,
|
self.config_entry.entry_id,
|
||||||
self._infos,
|
self._infos,
|
||||||
)
|
)
|
||||||
|
await super().async_step_finalize(_)
|
||||||
# Removes temporary value
|
|
||||||
if COMES_FROM in self._infos:
|
|
||||||
del self._infos[COMES_FROM]
|
|
||||||
|
|
||||||
self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos)
|
self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos)
|
||||||
return self.async_create_entry(title=None, data=None)
|
return self.async_create_entry(title=None, data=None)
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
|
|||||||
self._auto_start_stop_level, self.name
|
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
|
@overrides
|
||||||
async def start_listening(self):
|
async def start_listening(self):
|
||||||
"""Start listening the underlying entity"""
|
"""Start listening the underlying entity"""
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
|
|
||||||
async def check_power_available(self) -> bool:
|
async def check_power_available(self) -> bool:
|
||||||
"""Check if the Vtherm can be started considering overpowering.
|
"""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()
|
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
|
||||||
@@ -116,6 +117,7 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
|
|
||||||
current_power = vtherm_api.central_power_manager.current_power
|
current_power = vtherm_api.central_power_manager.current_power
|
||||||
current_max_power = vtherm_api.central_power_manager.current_max_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 (
|
if (
|
||||||
current_power is None
|
current_power is None
|
||||||
or current_max_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,
|
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:
|
if not ret:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - there is not enough power available power=%.3f, max_power=%.3f heater power=%.3f",
|
"%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,
|
current_max_power,
|
||||||
self._device_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
|
return ret
|
||||||
|
|
||||||
async def set_overpowering(self, overpowering: bool, power_consumption_max=0):
|
async def set_overpowering(self, overpowering: bool, power_consumption_max=0):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta, datetime
|
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.core import Event, HomeAssistant, State, callback
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
@@ -614,7 +614,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Find the underlying which have change
|
# 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:
|
if not under:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
@@ -626,6 +626,16 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
new_hvac_mode = new_state.state
|
new_hvac_mode = new_state.state
|
||||||
|
|
||||||
old_state = event.data.get("old_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_hvac_action = (
|
||||||
old_state.attributes.get("hvac_action")
|
old_state.attributes.get("hvac_action")
|
||||||
if old_state and old_state.attributes
|
if old_state and old_state.attributes
|
||||||
|
|||||||
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 |
@@ -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.
|
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.
|
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
- [Troubleshooting](#troubleshooting)
|
- [Troubleshooting](#troubleshooting)
|
||||||
- [Using a Heatzy](#using-a-heatzy)
|
- [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 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)
|
- [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)
|
- [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)
|
- [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)
|
- [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)
|
- [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 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
|
## Using a Heatzy
|
||||||
@@ -85,6 +87,17 @@ Example:
|
|||||||
|
|
||||||
Another more complex example is [here](https://github.com/jmcollin78/versatile_thermostat/discussions/431#discussioncomment-11393065)
|
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
|
## 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.
|
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.
|
||||||
@@ -214,3 +227,9 @@ 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`)
|
## 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-)
|
- [Composant Scheduler !](#composant-scheduler-)
|
||||||
- [Courbes de régulattion avec Plotly](#courbes-de-régulattion-avec-plotly)
|
- [Courbes de régulattion avec Plotly](#courbes-de-régulattion-avec-plotly)
|
||||||
- [Les notification avec l'AappDaemon NOTIFIER](#les-notification-avec-laappdaemon-notifier)
|
- [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
|
## Versatile Thermostat UI Card
|
||||||
@@ -214,3 +215,274 @@ mode: queued
|
|||||||
max: 30
|
max: 30
|
||||||
```
|
```
|
||||||
</details>
|
</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>
|
||||||
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 |
@@ -33,11 +33,12 @@ Toutes ces fonctions sont configurables de façon centralisée ou individuelle e
|
|||||||
|
|
||||||
## Incompatibilités
|
## Incompatibilités
|
||||||
Certains thermostat de type TRV sont réputés incompatibles avec le Versatile Thermostat. C'est le cas des vannes suivantes :
|
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,
|
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
|
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.
|
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.
|
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.
|
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.
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
- [Dépannages](#dépannages)
|
- [Dépannages](#dépannages)
|
||||||
- [Utilisation d'un Heatzy](#utilisation-dun-heatzy)
|
- [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 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)
|
- [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)
|
- [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)
|
- [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)
|
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
|
## 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.
|
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.
|
||||||
@@ -211,3 +221,8 @@ 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`)
|
## 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)
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ FULL_SWITCH_CONFIG = (
|
|||||||
| MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG
|
| MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG
|
||||||
| MOCK_TH_OVER_SWITCH_TYPE_CONFIG
|
| MOCK_TH_OVER_SWITCH_TYPE_CONFIG
|
||||||
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
|
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
|
||||||
# | MOCK_PRESETS_CONFIG
|
|
||||||
| MOCK_FULL_FEATURES
|
| MOCK_FULL_FEATURES
|
||||||
| MOCK_WINDOW_CONFIG
|
| MOCK_WINDOW_CONFIG
|
||||||
| MOCK_MOTION_CONFIG
|
| MOCK_MOTION_CONFIG
|
||||||
@@ -111,12 +110,7 @@ FULL_SWITCH_AC_CONFIG = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
PARTIAL_CLIMATE_CONFIG = (
|
PARTIAL_CLIMATE_CONFIG = (
|
||||||
MOCK_TH_OVER_CLIMATE_USER_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
|
||||||
| 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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP = (
|
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_MAIN_CONFIG
|
||||||
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
|
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
|
||||||
| MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG
|
| MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG
|
||||||
# | MOCK_PRESETS_CONFIG
|
|
||||||
| MOCK_ADVANCED_CONFIG
|
| MOCK_ADVANCED_CONFIG
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -133,24 +126,17 @@ PARTIAL_CLIMATE_NOT_REGULATED_CONFIG = (
|
|||||||
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
|
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
|
||||||
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
|
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
|
||||||
| MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG
|
| MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG
|
||||||
# | MOCK_PRESETS_CONFIG
|
|
||||||
| MOCK_ADVANCED_CONFIG
|
| MOCK_ADVANCED_CONFIG
|
||||||
)
|
)
|
||||||
|
|
||||||
PARTIAL_CLIMATE_AC_CONFIG = (
|
PARTIAL_CLIMATE_AC_CONFIG = (
|
||||||
MOCK_TH_OVER_CLIMATE_USER_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
|
||||||
| 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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
FULL_4SWITCH_CONFIG = (
|
FULL_4SWITCH_CONFIG = (
|
||||||
MOCK_TH_OVER_4SWITCH_USER_CONFIG
|
MOCK_TH_OVER_4SWITCH_USER_CONFIG
|
||||||
| MOCK_TH_OVER_4SWITCH_TYPE_CONFIG
|
| MOCK_TH_OVER_4SWITCH_TYPE_CONFIG
|
||||||
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
|
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
|
||||||
# | MOCK_PRESETS_CONFIG
|
|
||||||
| MOCK_WINDOW_CONFIG
|
| MOCK_WINDOW_CONFIG
|
||||||
| MOCK_MOTION_CONFIG
|
| MOCK_MOTION_CONFIG
|
||||||
| MOCK_POWER_CONFIG
|
| MOCK_POWER_CONFIG
|
||||||
@@ -1037,6 +1023,16 @@ default_temperatures_ac_away = {
|
|||||||
"boost_ac_away": 23.1,
|
"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 = {
|
default_temperatures_away = {
|
||||||
"frost": 7.0,
|
"frost": 7.0,
|
||||||
"eco": 17.0,
|
"eco": 17.0,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pylint: disable=wildcard-import, unused-wildcard-import, unused-argument, line-too-long, protected-access
|
# pylint: disable=wildcard-import, unused-wildcard-import, unused-argument, line-too-long, protected-access
|
||||||
|
|
||||||
""" Test the normal start of a Thermostat """
|
""" Test the normal start of a Thermostat """
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch, PropertyMock
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -179,7 +179,7 @@ async def test_overpowering_binary_sensors(
|
|||||||
)
|
)
|
||||||
# fmt:off
|
# fmt:off
|
||||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
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
|
# fmt: on
|
||||||
await send_power_change_event(entity, 150, now)
|
await send_power_change_event(entity, 150, now)
|
||||||
await send_max_power_change_event(entity, 100, now)
|
await send_max_power_change_event(entity, 100, now)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# pylint: disable=protected-access, unused-argument, line-too-long
|
# pylint: disable=protected-access, unused-argument, line-too-long
|
||||||
""" Test the Central Power management """
|
""" Test the Central Power management """
|
||||||
|
import asyncio
|
||||||
from unittest.mock import patch, AsyncMock, MagicMock, PropertyMock
|
from unittest.mock import patch, AsyncMock, MagicMock, PropertyMock
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
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 (
|
from custom_components.versatile_thermostat.central_feature_power_manager import (
|
||||||
CentralFeaturePowerManager,
|
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
|
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
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 central_power_manager.current_max_power == expected_power
|
||||||
assert mock_calculate_shedding.call_count == nb_call
|
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_POWER_FEATURE: True,
|
||||||
CONF_USE_PRESENCE_FEATURE: True,
|
CONF_USE_PRESENCE_FEATURE: True,
|
||||||
CONF_USE_CENTRAL_BOILER_FEATURE: False,
|
CONF_USE_CENTRAL_BOILER_FEATURE: False,
|
||||||
|
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert result["result"]
|
assert result["result"]
|
||||||
@@ -492,9 +493,7 @@ async def test_user_config_flow_over_climate(
|
|||||||
)
|
)
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
assert result.get("errors") is None
|
assert result.get("errors") is None
|
||||||
assert result[
|
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 | {
|
||||||
"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_MINIMAL_ACTIVATION_DELAY: 10,
|
||||||
CONF_SAFETY_DELAY_MIN: 5,
|
CONF_SAFETY_DELAY_MIN: 5,
|
||||||
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
|
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_USED_BY_CENTRAL_BOILER: False,
|
||||||
CONF_USE_CENTRAL_MODE: False,
|
CONF_USE_CENTRAL_MODE: False,
|
||||||
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
||||||
|
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
|
||||||
}
|
}
|
||||||
assert result["result"]
|
assert result["result"]
|
||||||
assert result["result"].domain == DOMAIN
|
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_POWER_FEATURE: False,
|
||||||
CONF_USE_PRESENCE_FEATURE: False,
|
CONF_USE_PRESENCE_FEATURE: False,
|
||||||
CONF_USE_CENTRAL_BOILER_FEATURE: False,
|
CONF_USE_CENTRAL_BOILER_FEATURE: False,
|
||||||
|
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert result["result"]
|
assert result["result"]
|
||||||
@@ -1681,9 +1682,7 @@ async def test_user_config_flow_over_climate_valve(
|
|||||||
)
|
)
|
||||||
assert result["type"] == FlowResultType.CREATE_ENTRY
|
assert result["type"] == FlowResultType.CREATE_ENTRY
|
||||||
assert result.get("errors") is None
|
assert result.get("errors") is None
|
||||||
assert result[
|
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 | {
|
||||||
"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_MINIMAL_ACTIVATION_DELAY: 10,
|
||||||
CONF_SAFETY_DELAY_MIN: 5,
|
CONF_SAFETY_DELAY_MIN: 5,
|
||||||
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
|
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_INT: 0.3,
|
||||||
CONF_TPI_COEF_EXT: 0.1,
|
CONF_TPI_COEF_EXT: 0.1,
|
||||||
CONF_MIN_OPENING_DEGREES: "10, 20,0",
|
CONF_MIN_OPENING_DEGREES: "10, 20,0",
|
||||||
|
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
|
||||||
}
|
}
|
||||||
assert result["result"]
|
assert result["result"]
|
||||||
assert result["result"].domain == DOMAIN
|
assert result["result"].domain == DOMAIN
|
||||||
|
|||||||
@@ -347,22 +347,17 @@ async def test_motion_management_time_not_enough(
|
|||||||
assert entity.presence_state == STATE_ON
|
assert entity.presence_state == STATE_ON
|
||||||
|
|
||||||
# 2. starts detecting motion with time not enough
|
# 2. starts detecting motion with time not enough
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=False
|
"homeassistant.helpers.condition.state", return_value=False
|
||||||
) as mock_condition, patch(
|
) as mock_condition, patch(
|
||||||
"homeassistant.core.StateMachine.get",
|
"homeassistant.core.StateMachine.get",
|
||||||
return_value=State(
|
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
event_timestamp = now - timedelta(minutes=4)
|
event_timestamp = now - timedelta(minutes=4)
|
||||||
try_condition = await send_motion_change_event(
|
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
|
assert mock_send_event.call_count == 0
|
||||||
|
|
||||||
# starts detecting motion with time enough this time
|
# starts detecting motion with time enough this time
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"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
|
assert entity.presence_state == STATE_ON
|
||||||
|
|
||||||
# stop detecting motion with off delay too low
|
# stop detecting motion with off delay too low
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_device_active, patch(
|
) as mock_device_active, patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=False
|
"homeassistant.helpers.condition.state", return_value=False
|
||||||
) as mock_condition, patch(
|
) as mock_condition, patch(
|
||||||
"homeassistant.core.StateMachine.get",
|
"homeassistant.core.StateMachine.get",
|
||||||
return_value=State(
|
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
event_timestamp = now - timedelta(minutes=2)
|
event_timestamp = now - timedelta(minutes=2)
|
||||||
try_condition = await send_motion_change_event(
|
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
|
assert mock_send_event.call_count == 0
|
||||||
|
|
||||||
# stop detecting motion with off delay enough long
|
# stop detecting motion with off delay enough long
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_device_active, patch(
|
) as mock_device_active, patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"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"
|
assert entity.presence_state == "on"
|
||||||
|
|
||||||
# starts detecting motion
|
# starts detecting motion
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"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
|
assert mock_send_event.call_count == 0
|
||||||
|
|
||||||
# stop detecting motion with confirmation of stop
|
# stop detecting motion with confirmation of stop
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"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
|
assert entity.presence_state == STATE_OFF
|
||||||
|
|
||||||
# starts detecting motion
|
# starts detecting motion
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"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
|
assert mock_send_event.call_count == 0
|
||||||
|
|
||||||
# stop detecting motion with confirmation of stop
|
# stop detecting motion with confirmation of stop
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"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
|
assert entity.presence_state == STATE_OFF
|
||||||
|
|
||||||
# starts detecting motion
|
# starts detecting motion
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"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
|
# 1. starts detecting motion but the sensor is off
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
||||||
"homeassistant.core.StateMachine.get",
|
"homeassistant.core.StateMachine.get",
|
||||||
return_value=State(
|
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
event_timestamp = now - timedelta(minutes=5)
|
event_timestamp = now - timedelta(minutes=5)
|
||||||
try_condition1 = await send_motion_change_event(
|
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
|
# 2. starts detecting motion but the sensor is on
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
||||||
"homeassistant.core.StateMachine.get",
|
"homeassistant.core.StateMachine.get",
|
||||||
return_value=State(
|
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_ON),
|
||||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_ON
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
event_timestamp = now - timedelta(minutes=5)
|
event_timestamp = now - timedelta(minutes=5)
|
||||||
try_condition1 = await send_motion_change_event(
|
try_condition1 = await send_motion_change_event(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
""" Test the Multiple switch management """
|
""" Test the Multiple switch management """
|
||||||
import asyncio
|
import asyncio
|
||||||
from unittest.mock import patch, call, ANY
|
from unittest.mock import patch, call, ANY, PropertyMock
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -84,14 +84,11 @@ async def test_one_switch_cycle(
|
|||||||
assert mock_is_state.call_count == 1
|
assert mock_is_state.call_count == 1
|
||||||
|
|
||||||
# Set temperature to a low level
|
# Set temperature to a low level
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
) as mock_device_active, patch(
|
) as mock_device_active, patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.call_later",
|
"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 == 1
|
||||||
assert mock_heater_on.call_count == 0
|
assert mock_heater_on.call_count == 0
|
||||||
# There is no check if active
|
# 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
|
# 4 calls dispatched along the cycle
|
||||||
assert mock_call_later.call_count == 1
|
assert mock_call_later.call_count == 1
|
||||||
@@ -119,14 +117,11 @@ async def test_one_switch_cycle(
|
|||||||
|
|
||||||
# Set a temperature at middle level
|
# Set a temperature at middle level
|
||||||
event_timestamp = now - timedelta(minutes=4)
|
event_timestamp = now - timedelta(minutes=4)
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
) as mock_device_active:
|
) as mock_device_active:
|
||||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
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
|
# Set another temperature at middle level
|
||||||
event_timestamp = now - timedelta(minutes=3)
|
event_timestamp = now - timedelta(minutes=3)
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_device_active:
|
) as mock_device_active:
|
||||||
await send_temperature_change_event(entity, 18.1, event_timestamp)
|
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
|
# Simulate the end of heater on cycle
|
||||||
event_timestamp = now - timedelta(minutes=3)
|
event_timestamp = now - timedelta(minutes=3)
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_device_active:
|
) as mock_device_active:
|
||||||
await entity.underlying_entity(
|
await entity.underlying_entity(
|
||||||
@@ -201,14 +190,11 @@ async def test_one_switch_cycle(
|
|||||||
|
|
||||||
# Simulate the start of heater on cycle
|
# Simulate the start of heater on cycle
|
||||||
event_timestamp = now - timedelta(minutes=3)
|
event_timestamp = now - timedelta(minutes=3)
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_device_active:
|
) as mock_device_active:
|
||||||
await entity.underlying_entity(
|
await entity.underlying_entity(
|
||||||
@@ -306,14 +292,11 @@ async def test_multiple_switchs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Set temperature to a low level
|
# Set temperature to a low level
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
) as mock_device_active, patch(
|
) as mock_device_active, patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.call_later",
|
"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 == 1
|
||||||
assert mock_heater_on.call_count == 0
|
assert mock_heater_on.call_count == 0
|
||||||
# There is no check if active
|
# 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
|
# 4 calls dispatched along the cycle
|
||||||
assert mock_call_later.call_count == 4
|
assert mock_call_later.call_count == 4
|
||||||
@@ -344,14 +328,11 @@ async def test_multiple_switchs(
|
|||||||
|
|
||||||
# Set a temperature at middle level
|
# Set a temperature at middle level
|
||||||
event_timestamp = now - timedelta(minutes=4)
|
event_timestamp = now - timedelta(minutes=4)
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
) as mock_device_active:
|
) as mock_device_active:
|
||||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
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, \
|
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_on") as mock_heater_on, \
|
||||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
|
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
|
#fmt: on
|
||||||
now = now + timedelta(seconds=30)
|
now = now + timedelta(seconds=30)
|
||||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
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.hvac_off_reason == HVAC_OFF_REASON_MANUAL
|
||||||
assert vtherm._saved_hvac_mode == HVACMode.OFF
|
assert vtherm._saved_hvac_mode == HVACMode.OFF
|
||||||
assert mock_send_event.call_count == 0 # nothing have change
|
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.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_on") as mock_heater_on, \
|
||||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
|
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
|
# fmt: on
|
||||||
now = now + timedelta(seconds=30)
|
now = now + timedelta(seconds=30)
|
||||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
@@ -913,12 +913,15 @@ async def test_power_management_turn_off_while_shedding(hass: HomeAssistant, ski
|
|||||||
|
|
||||||
# 1. Set VTherm to overpowering
|
# 1. Set VTherm to overpowering
|
||||||
# Send power max mesurement too low and HVACMode is on and device is active
|
# Send power max mesurement too low and HVACMode is on and device is active
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
# fmt:off
|
# fmt:off
|
||||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
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.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_on") as mock_heater_on, \
|
||||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
|
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
|
# fmt: on
|
||||||
now = now + timedelta(seconds=30)
|
now = now + timedelta(seconds=30)
|
||||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
@@ -939,7 +942,7 @@ async def test_power_management_turn_off_while_shedding(hass: HomeAssistant, ski
|
|||||||
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
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_on") as mock_heater_on, \
|
||||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, \
|
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
|
# fmt: on
|
||||||
now = now + timedelta(seconds=30)
|
now = now + timedelta(seconds=30)
|
||||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
|
|||||||
@@ -2039,16 +2039,13 @@ async def test_bug_66(
|
|||||||
assert entity.window_state is STATE_UNKNOWN
|
assert entity.window_state is STATE_UNKNOWN
|
||||||
|
|
||||||
# Open the window and let the thermostat shut down
|
# Open the window and let the thermostat shut down
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"homeassistant.helpers.condition.state", return_value=True
|
||||||
) as mock_condition, patch(
|
) as mock_condition, patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=True,
|
return_value=True,
|
||||||
):
|
):
|
||||||
await send_temperature_change_event(entity, 15, now)
|
await send_temperature_change_event(entity, 15, now)
|
||||||
@@ -2067,16 +2064,13 @@ async def test_bug_66(
|
|||||||
assert entity.window_state == STATE_ON
|
assert entity.window_state == STATE_ON
|
||||||
|
|
||||||
# Close the window but too shortly
|
# Close the window but too shortly
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"homeassistant.helpers.condition.state", return_value=False
|
"homeassistant.helpers.condition.state", return_value=False
|
||||||
) as mock_condition, patch(
|
) as mock_condition, patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
):
|
):
|
||||||
event_timestamp = now + timedelta(minutes=1)
|
event_timestamp = now + timedelta(minutes=1)
|
||||||
@@ -2090,16 +2084,13 @@ async def test_bug_66(
|
|||||||
assert entity.window_state == STATE_ON
|
assert entity.window_state == STATE_ON
|
||||||
|
|
||||||
# Reopen immediatly with sufficient time
|
# Reopen immediatly with sufficient time
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"homeassistant.helpers.condition.state", return_value=True
|
||||||
) as mock_condition, patch(
|
) as mock_condition, patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
):
|
):
|
||||||
try_window_condition = await send_window_change_event(
|
try_window_condition = await send_window_change_event(
|
||||||
@@ -2113,16 +2104,13 @@ async def test_bug_66(
|
|||||||
assert entity.hvac_mode == HVACMode.OFF
|
assert entity.hvac_mode == HVACMode.OFF
|
||||||
|
|
||||||
# Close the window but with sufficient time this time
|
# Close the window but with sufficient time this time
|
||||||
with patch(
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
||||||
) as mock_send_event, patch(
|
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||||
) as mock_heater_on, 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.turn_off"
|
|
||||||
) as mock_heater_off, patch(
|
|
||||||
"homeassistant.helpers.condition.state", return_value=True
|
"homeassistant.helpers.condition.state", return_value=True
|
||||||
) as mock_condition, patch(
|
) as mock_condition, patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||||
|
new_callable=PropertyMock,
|
||||||
return_value=False,
|
return_value=False,
|
||||||
):
|
):
|
||||||
event_timestamp = now + timedelta(minutes=2)
|
event_timestamp = now + timedelta(minutes=2)
|
||||||
|
|||||||
Reference in New Issue
Block a user