All tests Window Feature Manager ok.
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
706
tests/test_window_feature_manager.py
Normal file
706
tests/test_window_feature_manager.py
Normal 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()
|
||||||
Reference in New Issue
Block a user