Add test_auto_start_stop feature manager. All tests ok

This commit is contained in:
Jean-Marc Collin
2024-12-27 17:49:09 +00:00
parent 9fc8f9c909
commit 7ec7d3a26a
7 changed files with 472 additions and 210 deletions

View File

@@ -125,6 +125,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
.union(FeaturePresenceManager.unrecorded_attributes)
.union(FeaturePowerManager.unrecorded_attributes)
.union(FeatureMotionManager.unrecorded_attributes)
.union(FeatureWindowManager.unrecorded_attributes)
)
def __init__(
@@ -216,6 +217,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
# Instanciate all features manager
self._managers: list[BaseFeatureManager] = []
self._presence_manager: FeaturePresenceManager = FeaturePresenceManager(
self, hass
)
@@ -1299,7 +1301,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
return
# TODO ce code avec du dearm est curieux. A voir après refacto
dearm_window_auto = await self._async_update_temp(new_state)
self.recalculate()
await self.async_control_heating(force=False)
@@ -1866,7 +1867,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
),
}
self._presence_manager.add_custom_attributes(self._attr_extra_state_attributes)
for manager in self._managers:
manager.add_custom_attributes(self._attr_extra_state_attributes)
@overrides
def async_write_ha_state(self):

View File

@@ -0,0 +1,236 @@
""" Implements the Auto-start/stop Feature Manager """
# pylint: disable=line-too-long
import logging
from typing import Any
from homeassistant.core import (
HomeAssistant,
)
from homeassistant.components.climate import HVACMode
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .base_manager import BaseFeatureManager
from .auto_start_stop_algorithm import (
AutoStartStopDetectionAlgorithm,
AUTO_START_STOP_ACTION_OFF,
AUTO_START_STOP_ACTION_ON,
)
_LOGGER = logging.getLogger(__name__)
class FeatureAutoStartStopManager(BaseFeatureManager):
"""The implementation of the AutoStartStop feature"""
unrecorded_attributes = frozenset(
{
"auto_start_stop_level",
"auto_start_stop_dtmin",
"auto_start_stop_enable",
"auto_start_stop_accumulated_error",
"auto_start_stop_accumulated_error_threshold",
"auto_start_stop_last_switch_date",
}
)
def __init__(self, vtherm: Any, hass: HomeAssistant):
"""Init of a featureManager"""
super().__init__(vtherm, hass)
self._auto_start_stop_level: TYPE_AUTO_START_STOP_LEVELS = (
AUTO_START_STOP_LEVEL_NONE
)
self._auto_start_stop_algo: AutoStartStopDetectionAlgorithm | None = None
self._is_configured: bool = False
self._is_auto_start_stop_enabled: bool = False
@overrides
def post_init(self, entry_infos: ConfigData):
"""Reinit of the manager"""
use_auto_start_stop = entry_infos.get(CONF_USE_AUTO_START_STOP_FEATURE, False)
if use_auto_start_stop:
self._auto_start_stop_level = (
entry_infos.get(CONF_AUTO_START_STOP_LEVEL, None)
or AUTO_START_STOP_LEVEL_NONE
)
self._is_configured = True
else:
self._auto_start_stop_level = AUTO_START_STOP_LEVEL_NONE
self._is_configured = False
# Instanciate the auto start stop algo
self._auto_start_stop_algo = AutoStartStopDetectionAlgorithm(
self._auto_start_stop_level, self.name
)
@overrides
def start_listening(self):
"""Start listening the underlying entity"""
@overrides
def stop_listening(self):
"""Stop listening and remove the eventual timer still running"""
@overrides
async def refresh_state(self) -> bool:
"""Check the auto-start-stop and an eventual action
Return False if we should stop the control_heating method"""
if not self._is_configured or not self._is_auto_start_stop_enabled:
_LOGGER.debug("%s - auto start/stop is disabled (or not configured)", self)
return True
slope = (
self._vtherm.last_temperature_slope or 0
) / 60 # to have the slope in °/min
action = self._auto_start_stop_algo.calculate_action(
self._vtherm.hvac_mode,
self._vtherm.saved_hvac_mode,
self._vtherm.target_temperature,
self._vtherm.current_temperature,
slope,
self._vtherm.now,
)
_LOGGER.debug("%s - auto_start_stop action is %s", self, action)
if action == AUTO_START_STOP_ACTION_OFF and self._vtherm.is_on:
_LOGGER.info(
"%s - Turning OFF the Vtherm due to auto-start-stop conditions",
self,
)
self._vtherm.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
await self._vtherm.async_turn_off()
# Send an event
self._vtherm.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "stop",
"name": self.name,
"cause": "Auto stop conditions reached",
"hvac_mode": self._vtherm.hvac_mode,
"saved_hvac_mode": self._vtherm.saved_hvac_mode,
"target_temperature": self._vtherm.target_temperature,
"current_temperature": self._vtherm.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
# Stop here
return False
elif (
action == AUTO_START_STOP_ACTION_ON
and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.info(
"%s - Turning ON the Vtherm due to auto-start-stop conditions", self
)
await self._vtherm.async_turn_on()
# Send an event
self._vtherm.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start conditions reached",
"hvac_mode": self._vtherm.hvac_mode,
"saved_hvac_mode": self._vtherm.saved_hvac_mode,
"target_temperature": self._vtherm.target_temperature,
"current_temperature": self._vtherm.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self._vtherm.update_custom_attributes()
return True
def set_auto_start_stop_enable(self, is_enabled: bool):
"""Enable/Disable the auto-start/stop feature"""
self._is_auto_start_stop_enabled = is_enabled
if (
self._vtherm.hvac_mode == HVACMode.OFF
and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.debug(
"%s - the vtherm is off cause auto-start/stop and enable have been set to false -> starts the VTherm"
)
self.hass.create_task(self._vtherm.async_turn_on())
# Send an event
self._vtherm.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start stop disabled",
"hvac_mode": self._vtherm.hvac_mode,
"saved_hvac_mode": self._vtherm.saved_hvac_mode,
"target_temperature": self._vtherm.target_temperature,
"current_temperature": self._vtherm.current_temperature,
"temperature_slope": round(
self._vtherm.last_temperature_slope or 0, 3
),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self._vtherm.update_custom_attributes()
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
"""Add some custom attributes"""
extra_state_attributes.update(
{
"is_auto_start_stop_configured": self.is_configured,
}
)
if self.is_configured:
extra_state_attributes.update(
{
"auto_start_stop_enable": self.auto_start_stop_enable,
"auto_start_stop_level": self._auto_start_stop_algo.level,
"auto_start_stop_dtmin": self._auto_start_stop_algo.dt_min,
"auto_start_stop_accumulated_error": self._auto_start_stop_algo.accumulated_error,
"auto_start_stop_accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
"auto_start_stop_last_switch_date": self._auto_start_stop_algo.last_switch_date,
}
)
@overrides
@property
def is_configured(self) -> bool:
"""Return True of the window feature is configured"""
return self._is_configured
@property
def auto_start_stop_level(self) -> TYPE_AUTO_START_STOP_LEVELS:
"""Return the auto start/stop level."""
return self._auto_start_stop_level
@property
def auto_start_stop_enable(self) -> bool:
"""Returns the auto_start_stop_enable"""
return self._is_auto_start_stop_enabled
@property
def is_auto_stopped(self) -> bool:
"""Returns the is vtherm is stopped and reason is AUTO_START_STOP"""
return (
self._vtherm.hvac_mode == HVACMode.OFF
and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
)
def __str__(self):
return f"AutoStartStopManager-{self.name}"

View File

@@ -84,8 +84,13 @@ class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEn
def update_my_state_and_vtherm(self):
"""Update the auto_start_stop_enable flag in my VTherm"""
self.async_write_ha_state()
if self.my_climate is not None:
self.my_climate.set_auto_start_stop_enable(self._attr_is_on)
if (
self.my_climate is not None
and self.my_climate.auto_start_stop_manager is not None
):
self.my_climate.auto_start_stop_manager.set_auto_start_stop_enable(
self._attr_is_on
)
@callback
async def async_turn_on(self, **kwargs: Any) -> None:

View File

@@ -24,11 +24,7 @@ from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .vtherm_api import VersatileThermostatAPI
from .underlyings import UnderlyingClimate
from .auto_start_stop_algorithm import (
AutoStartStopDetectionAlgorithm,
AUTO_START_STOP_ACTION_OFF,
AUTO_START_STOP_ACTION_ON,
)
from .feature_auto_start_stop_manager import FeatureAutoStartStopManager
_LOGGER = logging.getLogger(__name__)
@@ -55,15 +51,9 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"auto_activated_fan_mode",
"auto_deactivated_fan_mode",
"auto_regulation_use_device_temp",
"auto_start_stop_level",
"auto_start_stop_dtmin",
"auto_start_stop_enable",
"auto_start_stop_accumulated_error",
"auto_start_stop_accumulated_error_threshold",
"auto_start_stop_last_switch_date",
"follow_underlying_temp_change",
}
)
).union(FeatureAutoStartStopManager.unrecorded_attributes)
)
def __init__(
@@ -83,11 +73,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
# The fan_mode name depending of the current_mode
self._auto_activated_fan_mode: str | None = None
self._auto_deactivated_fan_mode: str | None = None
self._auto_start_stop_level: TYPE_AUTO_START_STOP_LEVELS = (
AUTO_START_STOP_LEVEL_NONE
)
self._auto_start_stop_algo: AutoStartStopDetectionAlgorithm | None = None
self._is_auto_start_stop_enabled: bool = False
self._follow_underlying_temp_change: bool = False
self._last_regulation_change = None # NowClass.get_now(hass)
@@ -99,6 +84,12 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat"""
self._auto_start_stop_manager: FeatureAutoStartStopManager = (
FeatureAutoStartStopManager(self, self._hass)
)
self.register_manager(self._auto_start_stop_manager)
super().post_init(config_entry)
for climate in config_entry.get(CONF_UNDERLYING_LIST):
@@ -136,19 +127,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
CONF_AUTO_REGULATION_USE_DEVICE_TEMP, False
)
use_auto_start_stop = config_entry.get(CONF_USE_AUTO_START_STOP_FEATURE, False)
if use_auto_start_stop:
self._auto_start_stop_level = config_entry.get(
CONF_AUTO_START_STOP_LEVEL, AUTO_START_STOP_LEVEL_NONE
)
else:
self._auto_start_stop_level = AUTO_START_STOP_LEVEL_NONE
# Instanciate the auto start stop algo
self._auto_start_stop_algo = AutoStartStopDetectionAlgorithm(
self._auto_start_stop_level, self.name
)
@property
def is_over_climate(self) -> bool:
"""True if the Thermostat is over_climate"""
@@ -538,28 +516,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
self.auto_regulation_use_device_temp
)
self._attr_extra_state_attributes["auto_start_stop_enable"] = (
self.auto_start_stop_enable
)
self._attr_extra_state_attributes["auto_start_stop_level"] = (
self._auto_start_stop_algo.level
)
self._attr_extra_state_attributes["auto_start_stop_dtmin"] = (
self._auto_start_stop_algo.dt_min
)
self._attr_extra_state_attributes["auto_start_stop_accumulated_error"] = (
self._auto_start_stop_algo.accumulated_error
)
self._attr_extra_state_attributes[
"auto_start_stop_accumulated_error_threshold"
] = self._auto_start_stop_algo.accumulated_error_threshold
self._attr_extra_state_attributes["auto_start_stop_last_switch_date"] = (
self._auto_start_stop_algo.last_switch_date
)
self._attr_extra_state_attributes["follow_underlying_temp_change"] = (
self._follow_underlying_temp_change
)
@@ -899,90 +855,17 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
await end_climate_changed(changes)
async def check_auto_start_stop(self):
"""Check the auto-start-stop and an eventual action
Return False if we should stop the control_heating method"""
slope = (self.last_temperature_slope or 0) / 60 # to have the slope in °/min
action = self._auto_start_stop_algo.calculate_action(
self.hvac_mode,
self._saved_hvac_mode,
self.target_temperature,
self.current_temperature,
slope,
self.now,
)
_LOGGER.debug("%s - auto_start_stop action is %s", self, action)
if action == AUTO_START_STOP_ACTION_OFF and self.is_on:
_LOGGER.info(
"%s - Turning OFF the Vtherm due to auto-start-stop conditions",
self,
)
self.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
await self.async_turn_off()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "stop",
"name": self.name,
"cause": "Auto stop conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
# Stop here
return False
elif (
action == AUTO_START_STOP_ACTION_ON
and self.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.info(
"%s - Turning ON the Vtherm due to auto-start-stop conditions", self
)
await self.async_turn_on()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self.update_custom_attributes()
return True
@overrides
async def async_control_heating(self, force=False, _=None) -> bool:
"""The main function used to run the calculation at each cycle"""
ret = await super().async_control_heating(force, _)
# Check if we need to auto start/stop the Vtherm
if self.auto_start_stop_enable:
continu = await self.check_auto_start_stop()
if not continu:
return ret
else:
_LOGGER.debug("%s - auto start/stop is disabled", self)
continu = await self.auto_start_stop_manager.refresh_state()
if not continu:
return ret
# Continue the normal async_control_heating
# Continue the normal async_control_heating
# Send the regulated temperature to the underlyings
await self._send_regulated_temperature()
@@ -992,37 +875,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return ret
def set_auto_start_stop_enable(self, is_enabled: bool):
"""Enable/Disable the auto-start/stop feature"""
self._is_auto_start_stop_enabled = is_enabled
if (
self.hvac_mode == HVACMode.OFF
and self.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.debug(
"%s - the vtherm is off cause auto-start/stop and enable have been set to false -> starts the VTherm"
)
self.hass.create_task(self.async_turn_on())
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start stop disabled",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(self.last_temperature_slope or 0, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self.update_custom_attributes()
def set_follow_underlying_temp_change(self, follow: bool):
"""Set the flaf follow the underlying temperature changes"""
self._follow_underlying_temp_change = follow
@@ -1173,21 +1025,16 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return False
return True
@property
def auto_start_stop_level(self) -> TYPE_AUTO_START_STOP_LEVELS:
"""Return the auto start/stop level."""
return self._auto_start_stop_level
@property
def auto_start_stop_enable(self) -> bool:
"""Returns the auto_start_stop_enable"""
return self._is_auto_start_stop_enabled
@property
def follow_underlying_temp_change(self) -> bool:
"""Get the follow underlying temp change flag"""
return self._follow_underlying_temp_change
@property
def auto_start_stop_manager(self) -> FeatureAutoStartStopManager:
"""Return the auto-start-stop Manager"""
return self._auto_start_stop_manager
@overrides
def init_underlyings(self):
"""Init the underlyings if not already done"""

View File

@@ -1,4 +1,4 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable, too-many-lines
""" Test the Auto Start Stop algorithm management """
from datetime import datetime, timedelta
@@ -363,15 +363,14 @@ async def test_auto_start_stop_none_vtherm(
# Initialize all temps
await set_all_climate_preset_temp(hass, vtherm, temps, "overclimate")
# Check correct initialization of auto_start_stop attributes
assert (
vtherm._attr_extra_state_attributes["auto_start_stop_level"]
== AUTO_START_STOP_LEVEL_NONE
)
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] is None
assert vtherm._attr_extra_state_attributes.get("auto_start_stop_level") is None
assert vtherm._attr_extra_state_attributes.get("auto_start_stop_dtmin") is None
# 1. Vtherm auto-start/stop should be in NONE mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_NONE
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_NONE
)
# 2. We should not find any switch Enable entity
assert (
@@ -464,7 +463,10 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 15
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_MEDIUM
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_MEDIUM
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -488,7 +490,7 @@ async def test_auto_start_stop_medium_heat_vtherm(
# 3. Set current temperature to 19 5 min later
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -500,7 +502,7 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_mode == HVACMode.HEAT
assert mock_send_event.call_count == 0
assert (
vtherm._auto_start_stop_algo.accumulated_error == 0
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 0
) # target = current = 19
# 4. Set current temperature to 20 5 min later
@@ -516,7 +518,10 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_mode == HVACMode.HEAT
assert mock_send_event.call_count == 0
# accumulated_error = target - current = -1 x 5 min / 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2.5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error
== -2.5
)
# 5. Set current temperature to 21 5 min later -> should turn off
now = now + timedelta(minutes=5)
@@ -532,7 +537,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
# accumulated_error = -2.5 + target - current = -2 x 5 min / 2 capped to 5
assert vtherm._auto_start_stop_algo.accumulated_error == -5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -5
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -577,7 +584,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
await hass.async_block_till_done()
# accumulated_error = .... capped to -5
assert vtherm._auto_start_stop_algo.accumulated_error == -5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -5
)
# VTherm should stay stopped cause slope is too low to allow the turn to On
assert vtherm.hvac_mode == HVACMode.OFF
@@ -593,7 +602,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
await hass.async_block_till_done()
# accumulated_error = -5/2 + target - current = 1 x 20 min / 2 capped to 5
assert vtherm._auto_start_stop_algo.accumulated_error == 5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 5
)
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.HEAT
@@ -717,7 +728,10 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
@@ -736,7 +750,7 @@ async def test_auto_start_stop_fast_ac_vtherm(
# 3. Set current temperature to 19 5 min later
now = now + timedelta(minutes=5)
# reset accumulated error for test
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -748,7 +762,8 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm.hvac_mode == HVACMode.COOL
assert mock_send_event.call_count == 0
assert (
vtherm._auto_start_stop_algo.accumulated_error == 0 # target = current = 25
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error
== 0 # target = current = 25
)
# 4. Set current temperature to 23 5 min later -> should turn off
@@ -764,7 +779,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm.hvac_mode == HVACMode.OFF
# accumulated_error = target - current = 2 x 5 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == 2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 2
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -809,7 +826,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
await hass.async_block_till_done()
# accumulated_error = 2/2 + target - current = -1 x 20 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# VTherm should stay stopped
assert vtherm.hvac_mode == HVACMode.OFF
@@ -826,7 +845,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
await hass.async_block_till_done()
# accumulated_error = 2/2 + target - current = -1 x 20 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.COOL
@@ -948,7 +969,10 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
@@ -966,7 +990,7 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
# 3. Set current temperature to 21 5 min later to auto-stop
now = now + timedelta(minutes=5)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -977,7 +1001,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.OFF
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -1032,7 +1058,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
await hass.async_block_till_done()
assert vtherm.target_temperature == 21
assert vtherm._auto_start_stop_algo.accumulated_error == 2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 2
)
# VTherm should have been restarted
assert vtherm.hvac_mode == HVACMode.HEAT
@@ -1154,7 +1182,10 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in FAST mode and enable should be on
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1185,7 +1216,7 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
# 3. Set current temperature to 21 5 min later to auto-stop
now = now + timedelta(minutes=5)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1197,7 +1228,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
assert vtherm.hvac_mode == HVACMode.HEAT
# Not calculated cause enable = false
assert vtherm._auto_start_stop_algo.accumulated_error == 0
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 0
)
# a message should have been sent
assert mock_send_event.call_count == 0
@@ -1288,7 +1321,10 @@ async def test_auto_start_stop_fast_heat_window(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1315,7 +1351,7 @@ async def test_auto_start_stop_fast_heat_window(
# 3. Set current temperature to 21 5 min later -> should turn off VTherm
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1463,7 +1499,10 @@ async def test_auto_start_stop_fast_heat_window_mixed(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1513,7 +1552,7 @@ async def test_auto_start_stop_fast_heat_window_mixed(
# 4. Set current temperature to 21 5 min later -> should turn off VTherm
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1637,6 +1676,9 @@ async def test_auto_start_stop_disable_vtherm_off(
await set_all_climate_preset_temp(hass, vtherm, temps, "overclimate")
# Check correct initialization of auto_start_stop attributes
assert (
vtherm._attr_extra_state_attributes["is_auto_start_stop_configured"] is True
)
assert (
vtherm._attr_extra_state_attributes["auto_start_stop_level"]
== AUTO_START_STOP_LEVEL_FAST
@@ -1646,7 +1688,10 @@ async def test_auto_start_stop_disable_vtherm_off(
# 1. Vtherm auto-start/stop should be in FAST mode and enable should be on
vtherm._set_now(now)
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)

View File

@@ -0,0 +1,121 @@
# pylint: disable=unused-argument, line-too-long, protected-access, too-many-lines
""" Test the Window management """
import logging
from datetime import datetime, timedelta
from unittest.mock import patch, call, PropertyMock, AsyncMock, MagicMock
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.feature_auto_start_stop_manager import (
FeatureAutoStartStopManager,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
async def test_auto_start_stop_feature_manager_create(
hass: HomeAssistant,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
auto_start_stop_manager = FeatureAutoStartStopManager(fake_vtherm, hass)
assert auto_start_stop_manager is not None
assert auto_start_stop_manager.is_configured is False
assert auto_start_stop_manager.is_auto_stopped is False
assert auto_start_stop_manager.auto_start_stop_enable is False
assert auto_start_stop_manager.name == "the name"
assert len(auto_start_stop_manager._active_listener) == 0
custom_attributes = {}
auto_start_stop_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["is_auto_start_stop_configured"] is False
# assert custom_attributes["auto_start_stop_enable"] is False
# assert custom_attributes["auto_start_stop_level"] == AUTO_START_STOP_LEVEL_NONE
# assert custom_attributes["auto_start_stop_dtmin"] is None
# assert custom_attributes["auto_start_stop_accumulated_error"] is None
# assert custom_attributes["auto_start_stop_accumulated_error_threshold"] is None
# assert custom_attributes["auto_start_stop_last_switch_date"] is None
@pytest.mark.parametrize(
"use_auto_start_stop_feature, level, is_configured",
[
# fmt: off
( True, AUTO_START_STOP_LEVEL_NONE, True),
( True, AUTO_START_STOP_LEVEL_SLOW, True),
( True, AUTO_START_STOP_LEVEL_MEDIUM, True),
( True, AUTO_START_STOP_LEVEL_FAST, True),
# Level is missing , will be set to None
( True, None, True),
( False, AUTO_START_STOP_LEVEL_NONE, False),
( False, AUTO_START_STOP_LEVEL_SLOW, False),
( False, AUTO_START_STOP_LEVEL_MEDIUM, False),
( False, AUTO_START_STOP_LEVEL_FAST, False),
# Level is missing , will be set to None
( False, None, False),
# fmt: on
],
)
async def test_auto_start_stop_feature_manager_post_init(
hass: HomeAssistant, use_auto_start_stop_feature, level, is_configured
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
auto_start_stop_manager = FeatureAutoStartStopManager(fake_vtherm, hass)
assert auto_start_stop_manager is not None
# 2. post_init
auto_start_stop_manager.post_init(
{
CONF_USE_AUTO_START_STOP_FEATURE: use_auto_start_stop_feature,
CONF_AUTO_START_STOP_LEVEL: level,
}
)
assert auto_start_stop_manager.is_configured is is_configured
assert (
auto_start_stop_manager.auto_start_stop_level == level
if level and is_configured
else AUTO_START_STOP_LEVEL_NONE
)
assert auto_start_stop_manager.auto_start_stop_enable is False
assert auto_start_stop_manager._auto_start_stop_algo is not None
custom_attributes = {}
auto_start_stop_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["is_auto_start_stop_configured"] is is_configured
if auto_start_stop_manager.is_configured:
assert custom_attributes["auto_start_stop_enable"] is False
assert (
custom_attributes["auto_start_stop_level"] == level
if level and is_configured
else AUTO_START_STOP_LEVEL_NONE
)
assert (
custom_attributes["auto_start_stop_dtmin"]
== auto_start_stop_manager._auto_start_stop_algo.dt_min
)
assert (
custom_attributes["auto_start_stop_accumulated_error"]
== auto_start_stop_manager._auto_start_stop_algo.accumulated_error
)
assert (
custom_attributes["auto_start_stop_accumulated_error_threshold"]
== auto_start_stop_manager._auto_start_stop_algo.accumulated_error_threshold
)
assert (
custom_attributes["auto_start_stop_last_switch_date"]
== auto_start_stop_manager._auto_start_stop_algo.last_switch_date
)

View File

@@ -938,7 +938,10 @@ async def test_manual_hvac_off_should_take_the_lead_over_window(
== AUTO_START_STOP_LEVEL_FAST
)
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1112,7 +1115,10 @@ async def test_manual_hvac_off_should_take_the_lead_over_auto_start_stop(
== AUTO_START_STOP_LEVEL_FAST
)
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1138,7 +1144,7 @@ async def test_manual_hvac_off_should_take_the_lead_over_auto_start_stop(
now = now + timedelta(minutes=5)
vtherm._set_now(now)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event: