All tests Window Feature Manager ok.

This commit is contained in:
Jean-Marc Collin
2024-12-27 11:53:58 +00:00
parent 18248aee9e
commit c83c772901
4 changed files with 727 additions and 254 deletions

View File

@@ -37,7 +37,7 @@
"yzhang.markdown-all-in-one", "yzhang.markdown-all-in-one",
"github.vscode-github-actions", "github.vscode-github-actions",
"azuretools.vscode-docker", "azuretools.vscode-docker",
"huizhou.githd", "huizhou.githd"
], ],
"settings": { "settings": {
"files.eol": "\n", "files.eol": "\n",

View File

@@ -8,6 +8,7 @@ from datetime import timedelta
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, STATE_ON,
STATE_OFF,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
STATE_UNKNOWN, STATE_UNKNOWN,
) )
@@ -199,33 +200,30 @@ class FeatureWindowManager(BaseFeatureManager):
_LOGGER.debug( _LOGGER.debug(
"Window delay condition is not satisfied. Ignore window event" "Window delay condition is not satisfied. Ignore window event"
) )
self._window_state = old_state.state == STATE_ON # TODO Why ?
# self._window_state = old_state.state or STATE_OFF
return return
_LOGGER.debug("%s - Window delay condition is satisfied", self) _LOGGER.debug("%s - Window delay condition is satisfied", self)
if self._window_state == (new_state.state == STATE_ON): if self._window_state == new_state.state:
_LOGGER.debug("%s - no change in window state. Forget the event") _LOGGER.debug("%s - no change in window state. Forget the event")
return return
self._window_state = new_state.state == STATE_ON
_LOGGER.debug("%s - Window ByPass is : %s", self, self._is_window_bypass) _LOGGER.debug("%s - Window ByPass is : %s", self, self._is_window_bypass)
if self._is_window_bypass: if self._is_window_bypass:
_LOGGER.info( _LOGGER.info(
"%s - Window ByPass is activated. Ignore window event", self "%s - Window ByPass is activated. Ignore window event", self
) )
else: else:
await self.update_window_state(self._window_state) await self.update_window_state(new_state.state)
self._vtherm.update_custom_attributes() self._vtherm.update_custom_attributes()
if new_state is None or old_state is None or new_state.state == old_state.state: if new_state is None or old_state is None or new_state.state == old_state.state:
return try_window_condition return try_window_condition
if self._window_call_cancel: self.dearm_window_timer()
self._window_call_cancel()
self._window_call_cancel = None
self._window_call_cancel = async_call_later( self._window_call_cancel = async_call_later(
self.hass, timedelta(seconds=self._window_delay_sec), try_window_condition self.hass, timedelta(seconds=self._window_delay_sec), try_window_condition
) )
@@ -249,8 +247,6 @@ class FeatureWindowManager(BaseFeatureManager):
self._vtherm.saved_target_temp, self._vtherm.saved_target_temp,
) )
self._window_state = new_state
if self._window_action in [ if self._window_action in [
CONF_WINDOW_FROST_TEMP, CONF_WINDOW_FROST_TEMP,
CONF_WINDOW_ECO_TEMP, CONF_WINDOW_ECO_TEMP,
@@ -275,6 +271,7 @@ class FeatureWindowManager(BaseFeatureManager):
self, self,
self._window_action, self._window_action,
) )
return False
else: else:
_LOGGER.info( _LOGGER.info(
"%s - Window is open. Apply window action %s", self, self._window_action "%s - Window is open. Apply window action %s", self, self._window_action
@@ -283,9 +280,9 @@ class FeatureWindowManager(BaseFeatureManager):
_LOGGER.debug( _LOGGER.debug(
"%s is already off. Forget turning off VTherm due to window detection" "%s is already off. Forget turning off VTherm due to window detection"
) )
return return False
self._window_state = new_state # self._window_state = new_state
if self._vtherm.last_central_mode in [CENTRAL_MODE_AUTO, None]: if self._vtherm.last_central_mode in [CENTRAL_MODE_AUTO, None]:
if self._window_action in [ if self._window_action in [
CONF_WINDOW_TURN_OFF, CONF_WINDOW_TURN_OFF,
@@ -306,14 +303,13 @@ class FeatureWindowManager(BaseFeatureManager):
elif ( elif (
self._window_action == CONF_WINDOW_FROST_TEMP self._window_action == CONF_WINDOW_FROST_TEMP
and self._vtherm.is_preset_configured(PRESET_FROST_PROTECTION) and self._vtherm.is_preset_configured(PRESET_FROST_PROTECTION)
is not None
): ):
await self._vtherm.change_target_temperature( await self._vtherm.change_target_temperature(
self._vtherm.find_preset_temp(PRESET_FROST_PROTECTION) self._vtherm.find_preset_temp(PRESET_FROST_PROTECTION)
) )
elif ( elif (
self._window_action == CONF_WINDOW_ECO_TEMP self._window_action == CONF_WINDOW_ECO_TEMP
and self._vtherm.is_preset_configured(PRESET_ECO) is not None and self._vtherm.is_preset_configured(PRESET_ECO)
): ):
await self._vtherm.change_target_temperature( await self._vtherm.change_target_temperature(
self._vtherm.find_preset_temp(PRESET_ECO) self._vtherm.find_preset_temp(PRESET_ECO)
@@ -322,6 +318,7 @@ class FeatureWindowManager(BaseFeatureManager):
self._vtherm.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION) self._vtherm.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
await self._vtherm.async_set_hvac_mode(HVACMode.OFF) await self._vtherm.async_set_hvac_mode(HVACMode.OFF)
self._window_state = new_state
return True return True
async def manage_window_auto(self, in_cycle=False) -> callable: async def manage_window_auto(self, in_cycle=False) -> callable:
@@ -345,16 +342,14 @@ class FeatureWindowManager(BaseFeatureManager):
{"type": "end", "cause": cause, "curve_slope": slope}, {"type": "end", "cause": cause, "curve_slope": slope},
) )
# Set attributes # Set attributes
self._window_auto_state = False self._window_auto_state = STATE_OFF
await self.update_window_state(self._window_auto_state) await self.update_window_state(self._window_auto_state)
# await self.restore_hvac_mode(True) # await self.restore_hvac_mode(True)
if self._window_call_cancel: self.dearm_window_timer()
self._window_call_cancel()
self._window_call_cancel = None
if not self._window_auto_algo: if not self._window_auto_algo:
return return None
if in_cycle: if in_cycle:
slope = self._window_auto_algo.check_age_last_measurement( slope = self._window_auto_algo.check_age_last_measurement(
@@ -378,11 +373,11 @@ class FeatureWindowManager(BaseFeatureManager):
"%s - Window auto event is ignored because bypass is ON or window auto detection is disabled", "%s - Window auto event is ignored because bypass is ON or window auto detection is disabled",
self, self,
) )
return return None
if ( if (
self._window_auto_algo.is_window_open_detected() self._window_auto_algo.is_window_open_detected()
and self._window_auto_state is False and self._window_auto_state in [STATE_UNKNOWN, STATE_OFF]
and self._vtherm.hvac_mode != HVACMode.OFF and self._vtherm.hvac_mode != HVACMode.OFF
): ):
if ( if (
@@ -406,15 +401,11 @@ class FeatureWindowManager(BaseFeatureManager):
{"type": "start", "cause": "slope alert", "curve_slope": slope}, {"type": "start", "cause": "slope alert", "curve_slope": slope},
) )
# Set attributes # Set attributes
self._window_auto_state = True self._window_auto_state = STATE_ON
await self.update_window_state(self._window_auto_state) await self.update_window_state(self._window_auto_state)
# self.save_hvac_mode()
# await self.async_set_hvac_mode(HVACMode.OFF)
# Arm the end trigger # Arm the end trigger
if self._window_call_cancel: self.dearm_window_timer()
self._window_call_cancel()
self._window_call_cancel = None
self._window_call_cancel = async_call_later( self._window_call_cancel = async_call_later(
self.hass, self.hass,
timedelta(minutes=self._window_auto_max_duration), timedelta(minutes=self._window_auto_max_duration),
@@ -423,7 +414,7 @@ class FeatureWindowManager(BaseFeatureManager):
elif ( elif (
self._window_auto_algo.is_window_close_detected() self._window_auto_algo.is_window_close_detected()
and self._window_auto_state is True and self._window_auto_state == STATE_ON
): ):
await deactivate_window_auto(False) await deactivate_window_auto(False)

View File

@@ -2,7 +2,7 @@
""" Test the Window management """ """ Test the Window management """
import asyncio import asyncio
import logging import logging
from unittest.mock import patch, call, PropertyMock, AsyncMock, MagicMock from unittest.mock import patch, call, PropertyMock
from datetime import datetime, timedelta from datetime import datetime, timedelta
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
@@ -10,234 +10,10 @@ from custom_components.versatile_thermostat.thermostat_climate import (
ThermostatOverClimate, ThermostatOverClimate,
) )
from custom_components.versatile_thermostat.feature_window_manager import (
FeatureWindowManager,
)
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)
async def test_window_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
window_manager = FeatureWindowManager(fake_vtherm, hass)
assert window_manager is not None
assert window_manager.is_configured is False
assert window_manager.is_window_auto_configured is False
assert window_manager.is_window_detected is False
assert window_manager.window_state == STATE_UNAVAILABLE
assert window_manager.name == "the name"
assert len(window_manager._active_listener) == 0
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] is None
assert custom_attributes["window_state"] == STATE_UNAVAILABLE
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_configured"] is False
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["window_delay_sec"] == 0
assert custom_attributes["window_auto_open_threshold"] == 0
assert custom_attributes["window_auto_close_threshold"] == 0
assert custom_attributes["window_auto_max_duration"] == 0
assert custom_attributes["window_action"] is None
@pytest.mark.parametrize(
"use_window_feature, window_sensor_entity_id, window_delay_sec, window_auto_open_threshold, window_auto_close_threshold, window_auto_max_duration, window_action, is_configured, is_auto_configured, window_state, window_auto_state",
[
# fmt: off
( True, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_TURN_OFF, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
( False, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_TURN_OFF, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
( True, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_TURN_OFF, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
# delay is missing
( True, "sensor.the_window_sensor", None, None, None, None, CONF_WINDOW_TURN_OFF, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# action is missing -> defaults to TURN_OFF
( True, "sensor.the_window_sensor", 10, None, None, None, None, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
# With Window auto config complete
( True, None, None, 1, 2, 3, CONF_WINDOW_TURN_OFF, True, True, STATE_UNKNOWN, STATE_UNKNOWN ),
# With Window auto config not complete -> missing open threshold but defaults to 0
( True, None, None, None, 2, 3, CONF_WINDOW_TURN_OFF, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# With Window auto config not complete -> missing close threshold
( True, None, None, 1, None, 3, CONF_WINDOW_TURN_OFF, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# With Window auto config not complete -> missing max duration threshold but defaults to 0
( True, None, None, 1, 2, None, CONF_WINDOW_TURN_OFF, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# fmt: on
],
)
async def test_window_feature_manager_post_init(
hass: HomeAssistant,
use_window_feature,
window_sensor_entity_id,
window_delay_sec,
window_auto_open_threshold,
window_auto_close_threshold,
window_auto_max_duration,
window_action,
is_configured,
is_auto_configured,
window_state,
window_auto_state,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
assert window_manager is not None
# 2. post_init
window_manager.post_init(
{
CONF_USE_WINDOW_FEATURE: use_window_feature,
CONF_WINDOW_SENSOR: window_sensor_entity_id,
CONF_WINDOW_DELAY: window_delay_sec,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: window_auto_open_threshold,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: window_auto_close_threshold,
CONF_WINDOW_AUTO_MAX_DURATION: window_auto_max_duration,
CONF_WINDOW_ACTION: window_action,
}
)
assert window_manager.is_configured is is_configured
assert window_manager.is_window_auto_configured == is_auto_configured
assert window_manager.window_sensor_entity_id == window_sensor_entity_id
assert window_manager.window_state == window_state
assert window_manager.window_auto_state == window_auto_state
assert window_manager.window_delay_sec == window_delay_sec
assert window_manager.window_auto_open_threshold == window_auto_open_threshold
assert window_manager.window_auto_close_threshold == window_auto_close_threshold
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == window_sensor_entity_id
assert custom_attributes["window_state"] == window_state
assert custom_attributes["window_auto_state"] == window_auto_state
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is is_configured
assert custom_attributes["is_window_auto_configured"] is is_auto_configured
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is window_delay_sec
assert custom_attributes["window_auto_open_threshold"] is window_auto_open_threshold
assert (
custom_attributes["window_auto_close_threshold"] is window_auto_close_threshold
)
assert custom_attributes["window_auto_max_duration"] is window_auto_max_duration
@pytest.mark.parametrize(
"current_state, new_state, nb_call, window_state, is_window_detected, changed",
[
(STATE_OFF, STATE_ON, 1, STATE_ON, True, True),
(STATE_OFF, STATE_OFF, 0, STATE_OFF, False, False),
],
)
async def test_window_feature_manager_refresh_sensor(
hass: HomeAssistant,
current_state,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
changed,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
}
)
# 3. start listening
window_manager.start_listening()
assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE
assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)) as mock_get_state:
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.async_set_hvac_mode = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
# force old state for the test
window_manager._window_state = current_state
ret = await window_manager.refresh_state()
assert ret == changed
assert window_manager.is_configured is True
# in the refresh there is no delay
assert window_manager.window_state == new_state
assert mock_get_state.call_count == 1
assert mock_get_state.call_count == 1
assert fake_vtherm.async_set_hvac_mode.call_count == nb_call
assert fake_vtherm.set_hvac_off_reason.call_count == nb_call
if nb_call == 1:
fake_vtherm.async_set_hvac_mode.assert_has_calls(
[
call.async_set_hvac_mode(HVACMode.OFF),
]
)
fake_vtherm.set_hvac_off_reason.assert_has_calls(
[
call.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION),
]
)
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True]) @pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_management_time_not_enough( async def test_window_management_time_not_enough(

View File

@@ -0,0 +1,706 @@
# 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_window_manager import (
FeatureWindowManager,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
async def test_window_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
window_manager = FeatureWindowManager(fake_vtherm, hass)
assert window_manager is not None
assert window_manager.is_configured is False
assert window_manager.is_window_auto_configured is False
assert window_manager.is_window_detected is False
assert window_manager.window_state == STATE_UNAVAILABLE
assert window_manager.name == "the name"
assert len(window_manager._active_listener) == 0
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] is None
assert custom_attributes["window_state"] == STATE_UNAVAILABLE
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_configured"] is False
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["window_delay_sec"] == 0
assert custom_attributes["window_auto_open_threshold"] == 0
assert custom_attributes["window_auto_close_threshold"] == 0
assert custom_attributes["window_auto_max_duration"] == 0
assert custom_attributes["window_action"] is None
@pytest.mark.parametrize(
"use_window_feature, window_sensor_entity_id, window_delay_sec, window_auto_open_threshold, window_auto_close_threshold, window_auto_max_duration, window_action, is_configured, is_auto_configured, window_state, window_auto_state",
[
# fmt: off
( True, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_TURN_OFF, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
( False, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_FAN_ONLY, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
( True, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_FROST_TEMP, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
# delay is missing
( True, "sensor.the_window_sensor", None, None, None, None, CONF_WINDOW_ECO_TEMP, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# action is missing -> defaults to TURN_OFF
( True, "sensor.the_window_sensor", 10, None, None, None, None, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
# With Window auto config complete
( True, None, None, 1, 2, 3, CONF_WINDOW_FAN_ONLY, True, True, STATE_UNKNOWN, STATE_UNKNOWN ),
# With Window auto config not complete -> missing open threshold but defaults to 0
( True, None, None, None, 2, 3, CONF_WINDOW_FROST_TEMP, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# With Window auto config not complete -> missing close threshold
( True, None, None, 1, None, 3, CONF_WINDOW_ECO_TEMP, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# With Window auto config not complete -> missing max duration threshold but defaults to 0
( True, None, None, 1, 2, None, CONF_WINDOW_TURN_OFF, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# fmt: on
],
)
async def test_window_feature_manager_post_init(
hass: HomeAssistant,
use_window_feature,
window_sensor_entity_id,
window_delay_sec,
window_auto_open_threshold,
window_auto_close_threshold,
window_auto_max_duration,
window_action,
is_configured,
is_auto_configured,
window_state,
window_auto_state,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
assert window_manager is not None
# 2. post_init
window_manager.post_init(
{
CONF_USE_WINDOW_FEATURE: use_window_feature,
CONF_WINDOW_SENSOR: window_sensor_entity_id,
CONF_WINDOW_DELAY: window_delay_sec,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: window_auto_open_threshold,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: window_auto_close_threshold,
CONF_WINDOW_AUTO_MAX_DURATION: window_auto_max_duration,
CONF_WINDOW_ACTION: window_action,
}
)
assert window_manager.is_configured is is_configured
assert window_manager.is_window_auto_configured == is_auto_configured
assert window_manager.window_sensor_entity_id == window_sensor_entity_id
assert window_manager.window_state == window_state
assert window_manager.window_auto_state == window_auto_state
assert window_manager.window_delay_sec == window_delay_sec
assert window_manager.window_auto_open_threshold == window_auto_open_threshold
assert window_manager.window_auto_close_threshold == window_auto_close_threshold
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == window_sensor_entity_id
assert custom_attributes["window_state"] == window_state
assert custom_attributes["window_auto_state"] == window_auto_state
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is is_configured
assert custom_attributes["is_window_auto_configured"] is is_auto_configured
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is window_delay_sec
assert custom_attributes["window_auto_open_threshold"] is window_auto_open_threshold
assert (
custom_attributes["window_auto_close_threshold"] is window_auto_close_threshold
)
assert custom_attributes["window_auto_max_duration"] is window_auto_max_duration
@pytest.mark.parametrize(
"current_state, new_state, nb_call, window_state, is_window_detected, changed",
[
(STATE_OFF, STATE_ON, 1, STATE_ON, True, True),
(STATE_OFF, STATE_OFF, 0, STATE_OFF, False, False),
(STATE_ON, STATE_OFF, 1, STATE_OFF, False, True),
(STATE_ON, STATE_ON, 0, STATE_ON, True, False),
],
)
async def test_window_feature_manager_refresh_sensor_action_turn_off(
hass: HomeAssistant,
current_state,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
changed,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
}
)
# 3. start listening
window_manager.start_listening()
assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE
assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)) as mock_get_state:
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.async_set_hvac_mode = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
fake_vtherm.restore_hvac_mode = AsyncMock()
# force old state for the test
window_manager._window_state = current_state
if current_state == STATE_ON:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=HVAC_OFF_REASON_WINDOW_DETECTION)
else:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=None)
ret = await window_manager.refresh_state()
assert ret == changed
assert window_manager.is_configured is True
# in the refresh there is no delay
assert window_manager.window_state == new_state
assert mock_get_state.call_count == 1
assert fake_vtherm.set_hvac_off_reason.call_count == nb_call
if nb_call == 1:
if new_state == STATE_OFF:
assert fake_vtherm.restore_hvac_mode.call_count == 1
assert fake_vtherm.async_set_hvac_mode.call_count == 0
else:
assert fake_vtherm.async_set_hvac_mode.call_count == 1
fake_vtherm.async_set_hvac_mode.assert_has_calls(
[
call.async_set_hvac_mode(HVACMode.OFF),
]
)
assert fake_vtherm.restore_hvac_mode.call_count == 0
reason = None if current_state == STATE_ON and new_state == STATE_OFF else HVAC_OFF_REASON_WINDOW_DETECTION
fake_vtherm.set_hvac_off_reason.assert_has_calls(
[
call.set_hvac_off_reason(reason),
]
)
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.async_set_hvac_mode.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, new_state, nb_call, window_state, is_window_detected, changed",
[
(STATE_OFF, STATE_ON, 1, STATE_ON, True, True),
(STATE_OFF, STATE_OFF, 0, STATE_OFF, False, False),
(STATE_ON, STATE_OFF, 1, STATE_OFF, False, True),
(STATE_ON, STATE_ON, 0, STATE_ON, True, False),
],
)
async def test_window_feature_manager_refresh_sensor_action_frost_only(
hass: HomeAssistant,
current_state,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
changed,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
type(fake_vtherm).last_central_mode = PropertyMock(return_value=None)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
}
)
# 3. start listening
window_manager.start_listening()
assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE
assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)) as mock_get_state:
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.save_target_temp = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
fake_vtherm.restore_target_temp = AsyncMock()
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.find_preset_temp = MagicMock()
fake_vtherm.find_preset_temp.return_value = 17
# force old state for the test
window_manager._window_state = current_state
if current_state == STATE_ON:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=HVAC_OFF_REASON_WINDOW_DETECTION)
else:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=None)
ret = await window_manager.refresh_state()
assert ret == changed
assert window_manager.is_configured is True
# in the refresh there is no delay
assert window_manager.window_state == new_state
assert mock_get_state.call_count == 1
assert fake_vtherm.set_hvac_off_reason.call_count == 0
if nb_call == 1:
if new_state == STATE_OFF:
assert fake_vtherm.restore_target_temp.call_count == 1
assert fake_vtherm.save_target_temp.call_count == 0
assert fake_vtherm.change_target_temperature.call_count == 0
assert fake_vtherm.find_preset_temp.call_count == 0
else:
assert fake_vtherm.restore_target_temp.call_count == 0
assert fake_vtherm.save_target_temp.call_count == 1
assert fake_vtherm.change_target_temperature.call_count == 1
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.change_target_temperature(17),
]
)
assert fake_vtherm.find_preset_temp.call_count == 1
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.save_target_temp.call_count == 0
assert fake_vtherm.change_target_temperature.call_count == 0
assert fake_vtherm.find_preset_temp.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, long_enough, new_state, nb_call, window_state, is_window_detected",
[
(STATE_OFF, True, STATE_ON, 1, STATE_ON, True),
(STATE_OFF, True, STATE_OFF, 0, STATE_OFF, False),
(STATE_ON, True, STATE_OFF, 1, STATE_OFF, False),
(STATE_ON, True, STATE_ON, 0, STATE_ON, True),
(STATE_OFF, False, STATE_ON, 0, STATE_OFF, False),
(STATE_ON, False, STATE_OFF, 0, STATE_ON, True),
],
)
async def test_window_feature_manager_sensor_event_action_turn_off(
hass: HomeAssistant,
current_state,
long_enough,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
}
)
# 3. start listening
window_manager.start_listening()
assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.helpers.condition.state", return_value=long_enough):
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.async_set_hvac_mode = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
fake_vtherm.restore_hvac_mode = AsyncMock()
# force old state for the test
window_manager._window_state = current_state
if current_state == STATE_ON:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=HVAC_OFF_REASON_WINDOW_DETECTION)
else:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=None)
try_window_condition = await window_manager._window_sensor_changed(
event=Event(
event_type=EVENT_STATE_CHANGED,
data={
"entity_id": "sensor.the_window_sensor",
"new_state": State("sensor.the_window_sensor", new_state),
"old_state": State("sensor.the_window_sensor", current_state),
}))
assert try_window_condition is not None
await try_window_condition(None)
# There is change only if long enough
if long_enough:
assert window_manager.window_state == new_state
else:
assert window_manager.window_state == current_state
assert fake_vtherm.set_hvac_off_reason.call_count == nb_call
if nb_call == 1:
if new_state == STATE_OFF:
assert fake_vtherm.restore_hvac_mode.call_count == 1
assert fake_vtherm.async_set_hvac_mode.call_count == 0
else:
assert fake_vtherm.async_set_hvac_mode.call_count == 1
fake_vtherm.async_set_hvac_mode.assert_has_calls(
[
call.async_set_hvac_mode(HVACMode.OFF),
]
)
assert fake_vtherm.restore_hvac_mode.call_count == 0
reason = None if current_state == STATE_ON and new_state == STATE_OFF else HVAC_OFF_REASON_WINDOW_DETECTION
fake_vtherm.set_hvac_off_reason.assert_has_calls(
[
call.set_hvac_off_reason(reason),
]
)
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.async_set_hvac_mode.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state if long_enough else current_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, long_enough, new_state, nb_call, window_state, is_window_detected",
[
(STATE_OFF, True, STATE_ON, 1, STATE_ON, True),
(STATE_OFF, True, STATE_OFF, 0, STATE_OFF, False),
(STATE_ON, True, STATE_OFF, 1, STATE_OFF, False),
(STATE_ON, True, STATE_ON, 0, STATE_ON, True),
(STATE_OFF, False, STATE_ON, 0, STATE_OFF, False),
(STATE_ON, False, STATE_OFF, 0, STATE_ON, True),
],
)
async def test_window_feature_manager_event_sensor_action_frost_only(
hass: HomeAssistant,
current_state,
long_enough,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
type(fake_vtherm).last_central_mode = PropertyMock(return_value=None)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
}
)
# 3. start listening
window_manager.start_listening()
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.helpers.condition.state", return_value=long_enough):
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.save_target_temp = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
fake_vtherm.restore_target_temp = AsyncMock()
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.find_preset_temp = MagicMock()
fake_vtherm.find_preset_temp.return_value = 17
# force old state for the test
window_manager._window_state = current_state
if current_state == STATE_ON:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=HVAC_OFF_REASON_WINDOW_DETECTION)
else:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=None)
try_window_condition = await window_manager._window_sensor_changed(
event=Event(
event_type=EVENT_STATE_CHANGED,
data={
"entity_id": "sensor.the_window_sensor",
"new_state": State("sensor.the_window_sensor", new_state),
"old_state": State("sensor.the_window_sensor", current_state),
}))
assert try_window_condition is not None
await try_window_condition(None)
if long_enough:
assert window_manager.window_state == new_state
else:
assert window_manager.window_state == current_state
assert fake_vtherm.set_hvac_off_reason.call_count == 0
if nb_call == 1:
if new_state == STATE_OFF:
assert fake_vtherm.restore_target_temp.call_count == 1
assert fake_vtherm.save_target_temp.call_count == 0
assert fake_vtherm.change_target_temperature.call_count == 0
assert fake_vtherm.find_preset_temp.call_count == 0
else:
assert fake_vtherm.restore_target_temp.call_count == 0
assert fake_vtherm.save_target_temp.call_count == 1
assert fake_vtherm.change_target_temperature.call_count == 1
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.change_target_temperature(17),
]
)
assert fake_vtherm.find_preset_temp.call_count == 1
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.save_target_temp.call_count == 0
assert fake_vtherm.change_target_temperature.call_count == 0
assert fake_vtherm.find_preset_temp.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state if long_enough else current_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, in_cycle, new_temp, new_state, nb_call, window_state, is_window_detected",
[
(STATE_OFF, True, 10, STATE_ON, 1, STATE_ON, True),
(STATE_ON, True, 10, STATE_ON, 0, STATE_ON, True),
(STATE_ON, True, 20, STATE_OFF, 1, STATE_OFF, False),
(STATE_OFF, True, 20, STATE_OFF, 0, STATE_OFF, False),
],
)
async def test_window_feature_manager_window_auto(
hass: HomeAssistant,
current_state,
in_cycle,
new_temp,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
type(fake_vtherm).hvac_mode = PropertyMock(return_value=HVACMode.HEAT)
type(fake_vtherm).last_central_mode = PropertyMock(return_value=None)
type(fake_vtherm).proportional_algorithm = PropertyMock(return_value=None)
# 1. creation / post_init / start listening
window_manager = FeatureWindowManager(fake_vtherm, hass)
window_manager.post_init(
{
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 3,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1,
CONF_WINDOW_AUTO_MAX_DURATION: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
}
)
assert window_manager.is_window_auto_configured is True
window_manager.start_listening()
# 2. Call manage window auto
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
# Add a fake temp point for the window_auto_algo. We need at least 4 points
for i in range(0, 4):
window_manager._window_auto_algo.add_temp_measurement(
17 + (i * (new_temp - 17) / 4), now, True
)
now = now + timedelta(minutes=5)
# fmt:off
with patch("custom_components.versatile_thermostat.feature_window_manager.FeatureWindowManager.update_window_state") as mock_update_window_state:
#fmt: on
now = now + timedelta(minutes=10)
# From 17 to new_temp in 10 minutes
type(fake_vtherm).ema_temp = PropertyMock(return_value=new_temp)
type(fake_vtherm).last_temperature_measure = PropertyMock(return_value=now)
type(fake_vtherm).now = PropertyMock(return_value=now)
fake_vtherm.send_event = MagicMock()
window_manager._window_auto_state = current_state
dearm_window_auto = await window_manager.manage_window_auto(in_cycle=in_cycle)
assert dearm_window_auto is not None
assert mock_update_window_state.call_count == nb_call
if nb_call > 0:
mock_update_window_state.assert_has_calls(
[
call.update_window_state(new_state),
]
)
if new_state == STATE_ON:
assert window_manager._window_call_cancel is not None
assert window_manager.window_auto_state == new_state
# update_window_state is mocked
# assert window_manager.window_state == new_state
window_manager.stop_listening()
await hass.async_block_till_done()