2538 lines
100 KiB
Python
2538 lines
100 KiB
Python
# pylint: disable=unused-argument, line-too-long, protected-access, too-many-lines
|
|
""" Test the Window management """
|
|
import asyncio
|
|
import logging
|
|
from unittest.mock import patch, call, PropertyMock, AsyncMock, MagicMock
|
|
from datetime import datetime, timedelta
|
|
|
|
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
|
from custom_components.versatile_thermostat.thermostat_climate import (
|
|
ThermostatOverClimate,
|
|
)
|
|
|
|
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_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_timers", [True])
|
|
async def test_window_management_time_not_enough(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Window management when time is not enough"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
|
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
|
|
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 19
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
|
|
# Open the window, but condition of time is not satisfied and check the thermostat don't turns off
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=False
|
|
) as mock_condition:
|
|
await send_temperature_change_event(entity, 15, datetime.now())
|
|
await send_window_change_event(entity, True, False, datetime.now())
|
|
# simulate the call to try_window_condition. No need due to 0 WINDOW_DELAY and sleep after event is sent
|
|
# await try_window_condition(None)
|
|
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 1
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_condition.call_count == 1
|
|
|
|
assert entity.window_state == STATE_OFF
|
|
|
|
# Close the window
|
|
try_window_condition = await send_window_change_event(
|
|
entity, False, False, datetime.now()
|
|
)
|
|
# simulate the call to try_window_condition
|
|
await try_window_condition(None)
|
|
assert entity.window_state == STATE_OFF
|
|
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_management_time_enough(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Window management when time is enough"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
|
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
|
|
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 19
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
|
|
# change temperature to force turning on the heater
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
await send_temperature_change_event(entity, 15, datetime.now())
|
|
|
|
# Heater shoud turn-on
|
|
assert mock_heater_on.call_count >= 1
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
|
|
# Open the window, condition of time is satisfied, check the thermostat and heater turns off
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
await send_window_change_event(entity, True, False, datetime.now())
|
|
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})]
|
|
)
|
|
|
|
# Heater should not be on
|
|
assert mock_heater_on.call_count == 0
|
|
# One call in set_hvac_mode turn_off and one call in the control_heating for security
|
|
assert mock_heater_off.call_count == 2
|
|
assert mock_condition.call_count == 1
|
|
assert entity.hvac_mode is HVACMode.OFF
|
|
assert entity._saved_hvac_mode is HVACMode.HEAT
|
|
assert entity.hvac_off_reason == HVAC_OFF_REASON_WINDOW_DETECTION
|
|
assert entity.window_state == STATE_ON
|
|
|
|
# Close the window
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
try_function = await send_window_change_event(
|
|
entity, False, True, datetime.now(), sleep=False
|
|
)
|
|
|
|
await try_function(None)
|
|
|
|
# Wait for initial delay of heater
|
|
await asyncio.sleep(0.3)
|
|
|
|
assert entity.window_state == STATE_OFF
|
|
assert mock_heater_on.call_count == 1
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
|
|
),
|
|
],
|
|
any_order=False,
|
|
)
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity._saved_hvac_mode is HVACMode.HEAT # No change
|
|
assert entity.hvac_off_reason is None
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the auto Window management with fast slope down"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 21,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
|
|
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
|
|
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 21
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.is_window_auto_configured is True
|
|
|
|
# Initialize the slope algo with 2 measurements
|
|
event_timestamp = now + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# Make the temperature down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 0
|
|
assert entity.is_device_active is True
|
|
assert entity.last_temperature_slope == 0.0
|
|
assert entity._window_auto_algo.is_window_open_detected() is False
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
|
|
# send one degre down in one minute
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 2
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count >= 1
|
|
assert entity.last_temperature_slope == -6.24
|
|
assert entity._window_auto_algo.is_window_open_detected() is True
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.window_auto_state == STATE_ON
|
|
assert entity.hvac_mode is HVACMode.OFF
|
|
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF}),
|
|
call.send_event(
|
|
EventType.WINDOW_AUTO_EVENT,
|
|
{"type": "start", "cause": "slope alert", "curve_slope": -6.24},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
|
|
# send another 0.1 degre in one minute -> no change
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 17.9, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert round(entity.last_temperature_slope, 3) == -7.49
|
|
assert entity._window_auto_algo.is_window_open_detected() is True
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.window_auto_state == STATE_ON
|
|
assert entity.hvac_mode is HVACMode.OFF
|
|
|
|
# send another plus 1.1 degre in one minute -> restore state
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 2
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
|
|
),
|
|
call.send_event(
|
|
EventType.WINDOW_AUTO_EVENT,
|
|
{
|
|
"type": "end",
|
|
"cause": "end of slope alert",
|
|
"curve_slope": 0.42,
|
|
},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
assert mock_heater_on.call_count == 1
|
|
assert mock_heater_off.call_count == 0
|
|
assert entity.last_temperature_slope == 0.42
|
|
assert entity._window_auto_algo.is_window_open_detected() is False
|
|
assert entity._window_auto_algo.is_window_close_detected() is True
|
|
assert entity.window_auto_state == STATE_OFF
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_auto_fast_and_sensor(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test that the auto-window detection algorithm is deactivated if a window sensor is provided"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 21,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.fake_window_sensor",
|
|
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
|
|
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
|
|
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.target_temperature == 21
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.is_window_auto_configured is False
|
|
|
|
# Initialize the slope algo with 2 measurements
|
|
event_timestamp = now + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# Make the temperature down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The heater don't turns on
|
|
assert mock_send_event.call_count == 0
|
|
assert entity.is_device_active is True
|
|
assert entity.last_temperature_slope == 0.0
|
|
assert entity._window_auto_algo.is_window_open_detected() is False
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
|
|
# send one degre down in one minute
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0 # no change in heater
|
|
assert mock_heater_off.call_count == 0 # no change in heater
|
|
assert entity.last_temperature_slope == -6.24
|
|
# The window open should be detected (but not used)
|
|
# because we need to calculate the slope anyway, we have the algorithm running
|
|
assert entity._window_auto_algo.is_window_open_detected() is True
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.window_auto_state == STATE_OFF
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the Window auto management"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverClimateMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverClimateMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 21,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_CLIMATE: "switch.mock_climate",
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
|
|
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
|
|
CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverclimatemockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo is None
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 21
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.is_window_auto_configured is True
|
|
|
|
# 1. Initialize the slope algo with 2 measurements
|
|
event_timestamp = now + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# 2. Make the temperature down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
|
) as mock_set_hvac_mode, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.is_device_active",
|
|
return_value=True,
|
|
):
|
|
# This is the 3rd measurment. Slope is not ready
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The climate turns on but was alredy on
|
|
assert mock_set_hvac_mode.call_count == 0
|
|
assert entity.last_temperature_slope == 0.0
|
|
assert entity._window_auto_algo.is_window_open_detected() is False
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
|
|
# 3. send one degre down in one minute
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
|
) as mock_set_hvac_mode, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 18, event_timestamp, sleep=False)
|
|
|
|
assert entity.last_temperature_slope == -6.24
|
|
assert entity._window_auto_algo.is_window_open_detected() is True
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
|
|
assert mock_send_event.call_count == 2
|
|
# The heater turns off
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF}),
|
|
call.send_event(
|
|
EventType.WINDOW_AUTO_EVENT,
|
|
{
|
|
"type": "start",
|
|
"cause": "slope alert",
|
|
"curve_slope": -6.24,
|
|
},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
assert mock_set_hvac_mode.call_count >= 1
|
|
assert entity.window_auto_state == STATE_ON
|
|
assert entity.hvac_mode is HVACMode.OFF
|
|
|
|
# 4. This is to avoid that the slope stay under 6, else we will reactivate the window immediatly
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
dearm_window_auto = await send_temperature_change_event(
|
|
entity, 19, event_timestamp, sleep=False
|
|
)
|
|
assert entity.last_temperature_slope > -6.0
|
|
|
|
# 5. Waits for automatic disable
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
|
) as mock_set_hvac_mode, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
# simulate the expiration of the delay
|
|
await dearm_window_auto(None)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.window_auto_state == STATE_OFF
|
|
|
|
assert mock_set_hvac_mode.call_count == 1
|
|
assert round(entity.last_temperature_slope, 3) == -0.29
|
|
assert entity._window_auto_algo.is_window_open_detected() is False
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_auto_no_on_percent(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Power management"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 20,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
|
|
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
|
|
CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 20
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
|
|
# Initialize the slope algo with 2 measurements
|
|
event_timestamp = now + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 21, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 21, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 21, event_timestamp)
|
|
|
|
# Make the temperature down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 21, event_timestamp)
|
|
|
|
# The heater don't turns on
|
|
assert mock_heater_on.call_count == 0
|
|
assert entity.last_temperature_slope == 0.0
|
|
assert entity._window_auto_algo.is_window_open_detected() is False
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.proportional_algorithm.on_percent == 0.0
|
|
|
|
# send one degre down in one minute
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 20, event_timestamp)
|
|
|
|
# The heater turns on but no alert because the heater was not heating
|
|
assert entity.proportional_algorithm.on_percent == 0.0
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 1
|
|
assert entity.last_temperature_slope == -6.24
|
|
# The algo calculate open ...
|
|
assert entity._window_auto_algo.is_window_open_detected() is True
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
# But the entity is still on
|
|
assert entity.window_auto_state == STATE_OFF
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the Window management when bypass enabled"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
|
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 19
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.is_window_auto_configured is False
|
|
|
|
# change temperature to force turning on the heater
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
await send_temperature_change_event(entity, 15, datetime.now())
|
|
|
|
# Heater shoud turn-on
|
|
assert mock_heater_on.call_count >= 1
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
|
|
# Set Window ByPass to true
|
|
await entity.service_set_window_bypass_state(True)
|
|
assert entity.is_window_bypass is True
|
|
|
|
# entity._is_window_bypass = True
|
|
|
|
# Open the window, condition of time is satisfied, check the thermostat and heater turns off
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
await send_window_change_event(entity, True, False, datetime.now())
|
|
|
|
assert mock_send_event.call_count == 0
|
|
|
|
# Heater should not be on
|
|
assert mock_heater_on.call_count == 0
|
|
# One call in set_hvac_mode turn_off and one call in the control_heating for security
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_condition.call_count == 1
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.window_state == STATE_ON
|
|
|
|
# Close the window
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
try_function = await send_window_change_event(
|
|
entity, False, True, datetime.now(), sleep=False
|
|
)
|
|
|
|
await try_function(None)
|
|
|
|
# Wait for initial delay of heater
|
|
await asyncio.sleep(0.3)
|
|
|
|
assert entity.window_state == STATE_OFF
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
# PR - Adding Window bypass for window auto algorithm
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_auto_bypass(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the Window auto management"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 21,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
|
|
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
|
|
CONF_WINDOW_AUTO_MAX_DURATION: 1, # Should be > 0 to activate window_auto
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 21
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.is_window_auto_configured
|
|
|
|
# Initialize the slope algo with 2 measurements
|
|
event_timestamp = now + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# Make the temperature down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert entity.is_device_active is True
|
|
assert entity.last_temperature_slope == 0.0
|
|
assert entity._window_auto_algo.is_window_open_detected() is False
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
|
|
# send one degre down in one minute with window bypass on
|
|
await entity.service_set_window_bypass_state(True)
|
|
assert entity.is_window_bypass is True
|
|
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 18, event_timestamp, sleep=False)
|
|
|
|
# No change should have been done
|
|
assert mock_send_event.call_count == 0
|
|
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert entity.last_temperature_slope == -6.24
|
|
assert entity._window_auto_algo.is_window_open_detected() is True
|
|
assert entity._window_auto_algo.is_window_close_detected() is False
|
|
assert entity.window_auto_state == STATE_OFF
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
# PR - Adding Window bypass AFTER detection have been done should reactivate the heater
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_bypass_reactivate(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the Window management when window is open and then bypass is set to on"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
|
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 19
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
|
|
# change temperature to force turning on the heater
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
await send_temperature_change_event(entity, 15, datetime.now())
|
|
|
|
# Heater shoud turn-on
|
|
assert mock_heater_on.call_count >= 1
|
|
assert mock_heater_off.call_count == 0
|
|
assert mock_send_event.call_count == 0
|
|
|
|
# Open the window, condition of time is satisfied, check the thermostat and heater turns off
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
await send_window_change_event(entity, True, False, datetime.now())
|
|
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})]
|
|
)
|
|
|
|
# Heater should not be on
|
|
assert mock_heater_on.call_count == 0
|
|
# One call in set_hvac_mode turn_off and one call in the control_heating for security
|
|
assert mock_heater_off.call_count == 2
|
|
assert mock_condition.call_count == 1
|
|
assert entity.hvac_mode is HVACMode.OFF
|
|
assert entity.window_state == STATE_ON
|
|
|
|
# Call the set bypass service to set bypass ON
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
await entity.service_set_window_bypass_state(True)
|
|
|
|
assert entity.window_state == STATE_ON
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
# assert mock_heater_on.call_count == 1
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
|
|
),
|
|
],
|
|
any_order=False,
|
|
)
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the Window management with the fan_only option"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverClimateMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverClimateMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_CLIMATE: "climate.mock_climate",
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
|
CONF_WINDOW_DELAY: 1,
|
|
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
|
|
# CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
|
|
# CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
|
|
# CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection
|
|
},
|
|
)
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now: datetime = datetime.now(tz=tz)
|
|
|
|
fake_underlying_climate = MockClimate(
|
|
hass=hass,
|
|
unique_id="mockUniqueId",
|
|
name="MockClimateName",
|
|
hvac_modes=[HVACMode.HEAT, HVACMode.COOL, HVACMode.FAN_ONLY],
|
|
)
|
|
|
|
# 1. intialize climate entity
|
|
with patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
|
return_value=fake_underlying_climate,
|
|
):
|
|
entry.add_to_hass(hass)
|
|
await hass.config_entries.async_setup(entry.entry_id)
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
|
|
entity: ThermostatOverClimate = search_entity(
|
|
hass, "climate.theoverclimatemockname", "climate"
|
|
)
|
|
|
|
assert entity
|
|
|
|
assert entity.is_over_climate is True
|
|
assert entity.window_action == CONF_WINDOW_FAN_ONLY
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
assert entity.hvac_mode == HVACMode.HEAT
|
|
await entity.async_set_preset_mode(PRESET_COMFORT)
|
|
assert entity.preset_mode == PRESET_COMFORT
|
|
assert entity.target_temperature == 18
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
|
|
# 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
), patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
|
) as mock_underlying_set_hvac_mode:
|
|
event_timestamp = now - timedelta(minutes=2)
|
|
try_window_condition = await send_window_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
await try_window_condition(None)
|
|
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.FAN_ONLY}
|
|
)
|
|
]
|
|
)
|
|
|
|
# The underlying should be in OFF hvac_mode
|
|
assert mock_underlying_set_hvac_mode.call_count == 1
|
|
mock_underlying_set_hvac_mode.assert_has_calls(
|
|
[
|
|
call.set_hvac_mode(HVACMode.FAN_ONLY),
|
|
]
|
|
)
|
|
|
|
assert entity.window_state == STATE_ON
|
|
# The underlying should be in FAN_ONLY hvac_mode
|
|
assert entity.hvac_mode is HVACMode.FAN_ONLY
|
|
assert entity._saved_hvac_mode is HVACMode.HEAT
|
|
assert entity.hvac_off_reason is None # Hvac is not off
|
|
assert entity.preset_mode is PRESET_COMFORT
|
|
|
|
# 3. Close the window
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
), patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
|
) as mock_underlying_set_hvac_mode:
|
|
event_timestamp = now - timedelta(minutes=1)
|
|
try_function = await send_window_change_event(
|
|
entity, False, True, event_timestamp, sleep=False
|
|
)
|
|
|
|
await try_function(None)
|
|
|
|
# Wait for initial delay of heater
|
|
await hass.async_block_till_done()
|
|
|
|
assert entity.window_state == STATE_OFF
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
|
|
),
|
|
],
|
|
any_order=False,
|
|
)
|
|
|
|
# The underlying should be in OFF hvac_mode
|
|
assert mock_underlying_set_hvac_mode.call_count == 1
|
|
mock_underlying_set_hvac_mode.assert_has_calls(
|
|
[
|
|
call.set_hvac_mode(HVACMode.HEAT),
|
|
]
|
|
)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_COMFORT
|
|
assert entity.hvac_off_reason is None
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_action_fan_only_ko(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Window management with the fan_only option but the underlyings doesn't have the FAN_ONLY mode
|
|
So the VTherm switch to OFF which is the fallback mode"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverClimateMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverClimateMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_CLIMATE: "climate.mock_climate",
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
|
CONF_WINDOW_DELAY: 1,
|
|
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
|
|
# CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
|
|
# CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
|
|
# CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection
|
|
},
|
|
)
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now: datetime = datetime.now(tz=tz)
|
|
|
|
fake_underlying_climate = MockClimate(
|
|
hass=hass,
|
|
unique_id="mockUniqueId",
|
|
name="MockClimateName",
|
|
hvac_modes=[HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO],
|
|
)
|
|
|
|
# 1. intialize climate entity
|
|
with patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
|
return_value=fake_underlying_climate,
|
|
):
|
|
entry.add_to_hass(hass)
|
|
await hass.config_entries.async_setup(entry.entry_id)
|
|
assert entry.state is ConfigEntryState.LOADED
|
|
|
|
entity: ThermostatOverClimate = search_entity(
|
|
hass, "climate.theoverclimatemockname", "climate"
|
|
)
|
|
|
|
assert entity
|
|
|
|
assert entity.is_over_climate is True
|
|
assert entity.window_action == CONF_WINDOW_FAN_ONLY
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
assert entity.hvac_mode == HVACMode.HEAT
|
|
await entity.async_set_preset_mode(PRESET_COMFORT)
|
|
assert entity.preset_mode == PRESET_COMFORT
|
|
assert entity.target_temperature == 18
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
|
|
# 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
), patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
|
) as mock_underlying_set_hvac_mode:
|
|
event_timestamp = now - timedelta(minutes=2)
|
|
try_window_condition = await send_window_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
await try_window_condition(None)
|
|
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})]
|
|
)
|
|
|
|
assert entity.window_state == STATE_ON
|
|
assert entity.hvac_mode is HVACMode.OFF
|
|
# The underlying should be in OFF hvac_mode
|
|
assert mock_underlying_set_hvac_mode.call_count == 1
|
|
mock_underlying_set_hvac_mode.assert_has_calls(
|
|
[
|
|
call.set_hvac_mode(HVACMode.OFF),
|
|
]
|
|
)
|
|
|
|
assert entity._saved_hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_COMFORT
|
|
|
|
# 3. Close the window
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
), patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
|
) as mock_underlying_set_hvac_mode:
|
|
event_timestamp = now - timedelta(minutes=1)
|
|
try_function = await send_window_change_event(
|
|
entity, False, True, event_timestamp, sleep=False
|
|
)
|
|
|
|
await try_function(None)
|
|
|
|
# Wait for initial delay of heater
|
|
await asyncio.sleep(0.3)
|
|
|
|
assert entity.window_state == STATE_OFF
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
|
|
),
|
|
],
|
|
any_order=False,
|
|
)
|
|
|
|
# The underlying should be in OFF hvac_mode
|
|
assert mock_underlying_set_hvac_mode.call_count == 1
|
|
mock_underlying_set_hvac_mode.assert_has_calls(
|
|
[
|
|
call.set_hvac_mode(HVACMode.HEAT),
|
|
]
|
|
)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_COMFORT
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_action_eco_temp(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the Window management with the eco_temp option"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 21,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
|
|
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
|
|
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
|
|
CONF_WINDOW_ACTION: CONF_WINDOW_ECO_TEMP,
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 21
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.is_window_auto_configured is True
|
|
|
|
# 1. Initialize the slope algo with 2 measurements
|
|
event_timestamp = now + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# 2. Make the temperature down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 0
|
|
assert entity.is_device_active is True
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.window_auto_state is STATE_OFF
|
|
|
|
# 3. send one degre down in one minute
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 1
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert entity.last_temperature_slope == -6.24
|
|
assert entity.window_auto_state == STATE_ON
|
|
assert entity.window_state == STATE_OFF
|
|
# No change on HVACMode
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
# No change on preset
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
# The eco temp
|
|
assert entity.target_temperature == 17
|
|
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.WINDOW_AUTO_EVENT,
|
|
{"type": "start", "cause": "slope alert", "curve_slope": -6.24},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
|
|
# 4. send another 0.1 degre in one minute -> no change
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 17.9, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert round(entity.last_temperature_slope, 3) == -7.49
|
|
assert entity.window_auto_state == STATE_ON
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
# No change on preset
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
# The eco temp
|
|
assert entity.target_temperature == 17
|
|
|
|
# 5. send another plus 1.1 degre in one minute -> restore state
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.WINDOW_AUTO_EVENT,
|
|
{
|
|
"type": "end",
|
|
"cause": "end of slope alert",
|
|
"curve_slope": 0.42,
|
|
},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert entity.last_temperature_slope == 0.42
|
|
assert entity.window_auto_state == STATE_OFF
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
# No change on preset
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
# The eco temp
|
|
assert entity.target_temperature == 21
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the Window management with the frost_temp option"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 21,
|
|
"frost_temp": 10,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
|
|
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
|
|
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
|
|
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
|
|
assert entity.target_temperature == 21
|
|
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.is_window_auto_configured is True
|
|
|
|
# 1. Initialize the slope algo with 2 measurements
|
|
event_timestamp = now + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# 2. Make the temperature down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 0
|
|
assert entity.is_device_active is True
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.window_state is STATE_OFF
|
|
assert entity.window_auto_state is STATE_OFF
|
|
|
|
# 3. send one degre down in one minute
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=True,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 1
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert entity.last_temperature_slope == -6.24
|
|
assert entity.window_auto_state == STATE_ON
|
|
assert entity.window_state == STATE_OFF
|
|
# No change on HVACMode
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
# No change on preset
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
# The eco temp
|
|
assert entity.target_temperature == 10
|
|
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.WINDOW_AUTO_EVENT,
|
|
{"type": "start", "cause": "slope alert", "curve_slope": -6.24},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
|
|
# 4. send another 0.1 degre in one minute -> no change
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 17.9, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert round(entity.last_temperature_slope, 3) == -7.49
|
|
assert entity.window_auto_state == STATE_ON
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
# No change on preset
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
# The eco temp
|
|
assert entity.target_temperature == 10
|
|
|
|
# 5. send another plus 1.1 degre in one minute -> restore state
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
new_callable=PropertyMock,
|
|
return_value=False,
|
|
):
|
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
|
|
|
# The heater turns on
|
|
assert mock_send_event.call_count == 1
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(
|
|
EventType.WINDOW_AUTO_EVENT,
|
|
{
|
|
"type": "end",
|
|
"cause": "end of slope alert",
|
|
"curve_slope": 0.42,
|
|
},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
assert entity.last_temperature_slope == 0.42
|
|
assert entity.window_auto_state == STATE_OFF
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
# No change on preset
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
# The Boost temp
|
|
assert entity.target_temperature == 21
|
|
|
|
# Clean the entity
|
|
entity.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_bug_66(
|
|
hass: HomeAssistant,
|
|
skip_hass_states_is_state,
|
|
skip_turn_on_off_heater,
|
|
skip_send_event,
|
|
):
|
|
"""Test that it should be possible to open/close the window rapidly without side effect"""
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now: datetime = datetime.now(tz=tz)
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_HEATER: "switch.mock_switch",
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
|
|
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1, # !! here
|
|
CONF_DEVICE_POWER: 200,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
|
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
|
|
},
|
|
)
|
|
|
|
entity: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.target_temperature == 19
|
|
assert entity.window_state is STATE_OFF
|
|
|
|
# Open the window and let the thermostat shut down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=True,
|
|
):
|
|
await send_temperature_change_event(entity, 15, now)
|
|
try_window_condition = await send_window_change_event(
|
|
entity, True, False, now, False
|
|
)
|
|
|
|
# simulate the call to try_window_condition
|
|
await try_window_condition(None)
|
|
|
|
assert mock_send_event.call_count == 1
|
|
assert mock_heater_on.call_count == 1
|
|
assert mock_heater_off.call_count >= 1
|
|
assert mock_condition.call_count == 1
|
|
|
|
assert entity.window_state == STATE_ON
|
|
|
|
# Close the window but too shortly
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=False
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=False,
|
|
):
|
|
event_timestamp = now + timedelta(minutes=1)
|
|
try_window_condition = await send_window_change_event(
|
|
entity, False, True, event_timestamp
|
|
)
|
|
# simulate the call to try_window_condition
|
|
await try_window_condition(None)
|
|
|
|
# window state should not have change
|
|
assert entity.window_state == STATE_ON
|
|
|
|
# Reopen immediatly with sufficient time
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=False,
|
|
):
|
|
try_window_condition = await send_window_change_event(
|
|
entity, True, False, event_timestamp
|
|
)
|
|
# simulate the call to try_window_condition
|
|
await try_window_condition(None)
|
|
|
|
# still no change
|
|
assert entity.window_state == STATE_ON
|
|
assert entity.hvac_mode == HVACMode.OFF
|
|
|
|
# Close the window but with sufficient time this time
|
|
with patch(
|
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
|
) as mock_send_event, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
|
) as mock_heater_on, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
|
) as mock_heater_off, patch(
|
|
"homeassistant.helpers.condition.state", return_value=True
|
|
) as mock_condition, patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
|
return_value=False,
|
|
):
|
|
event_timestamp = now + timedelta(minutes=2)
|
|
try_window_condition = await send_window_change_event(
|
|
entity, False, True, event_timestamp
|
|
)
|
|
# simulate the call to try_window_condition
|
|
await try_window_condition(None)
|
|
|
|
# window state should be Off this time and old state should have been restored
|
|
assert entity.window_state == STATE_OFF
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_action_frost_temp_preset_change(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Window management with the frost_temp option and change the preset during
|
|
the window is open. This should restore the new preset temperature"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.fake_window_sensor",
|
|
CONF_WINDOW_DELAY: 1,
|
|
},
|
|
)
|
|
|
|
vtherm: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert vtherm
|
|
|
|
await set_all_climate_preset_temp(
|
|
hass, vtherm, default_temperatures, "theoverswitchmockname"
|
|
)
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
|
|
await vtherm.async_set_preset_mode(PRESET_BOOST)
|
|
await hass.async_block_till_done()
|
|
|
|
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
assert vtherm.preset_mode is PRESET_BOOST
|
|
assert vtherm.target_temperature == 21
|
|
|
|
assert vtherm.window_state is STATE_OFF
|
|
assert vtherm.is_window_auto_configured is False
|
|
|
|
# 1. Turn on the window sensor
|
|
now = now + timedelta(minutes=1)
|
|
vtherm._set_now(now)
|
|
with patch("homeassistant.helpers.condition.state", return_value=True):
|
|
|
|
try_function = await send_window_change_event(vtherm, True, False, now)
|
|
|
|
now = now + timedelta(minutes=2)
|
|
vtherm._set_now(now)
|
|
await try_function(None)
|
|
|
|
# VTherm should have taken the window action
|
|
assert vtherm.target_temperature == 7 # Frost
|
|
# No change
|
|
assert vtherm.preset_mode is PRESET_BOOST
|
|
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
|
|
# 2. Change the preset to comfort
|
|
now = now + timedelta(minutes=1)
|
|
vtherm._set_now(now)
|
|
|
|
await vtherm.async_set_preset_mode(PRESET_COMFORT)
|
|
await hass.async_block_till_done()
|
|
|
|
# VTherm should have taken the new preset temperature
|
|
assert vtherm.target_temperature == 7 # frost (window is still open)
|
|
assert vtherm.preset_mode is PRESET_COMFORT
|
|
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
|
|
# 3.Turn off the window sensor
|
|
now = now + timedelta(minutes=1)
|
|
vtherm._set_now(now)
|
|
with patch("homeassistant.helpers.condition.state", return_value=True):
|
|
|
|
try_function = await send_window_change_event(vtherm, False, True, now)
|
|
|
|
now = now + timedelta(minutes=2)
|
|
vtherm._set_now(now)
|
|
await try_function(None)
|
|
|
|
# VTherm should have restore the Comfort preset temperature
|
|
assert vtherm.target_temperature == 19 # restore comfort
|
|
# No change
|
|
assert vtherm.preset_mode is PRESET_COMFORT
|
|
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
|
|
# Clean the entity
|
|
vtherm.remove_thermostat()
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_window_action_frost_temp_temp_change(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Window management with the frost_temp option and change the target temp during
|
|
the window is open. This should restore the new temperature"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
CONF_USE_WINDOW_FEATURE: True,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: False,
|
|
CONF_USE_PRESENCE_FEATURE: False,
|
|
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
|
CONF_TPI_COEF_INT: 0.3,
|
|
CONF_TPI_COEF_EXT: 0.01,
|
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
|
CONF_SECURITY_DELAY_MIN: 5,
|
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
|
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
|
|
CONF_WINDOW_SENSOR: "binary_sensor.fake_window_sensor",
|
|
CONF_WINDOW_DELAY: 1,
|
|
},
|
|
)
|
|
|
|
vtherm: BaseThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert vtherm
|
|
|
|
await set_all_climate_preset_temp(
|
|
hass, vtherm, default_temperatures, "theoverswitchmockname"
|
|
)
|
|
|
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
|
now = datetime.now(tz)
|
|
|
|
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
|
|
await vtherm.async_set_preset_mode(PRESET_BOOST)
|
|
await hass.async_block_till_done()
|
|
|
|
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
assert vtherm.preset_mode is PRESET_BOOST
|
|
assert vtherm.target_temperature == 21
|
|
|
|
assert vtherm.window_state is STATE_OFF
|
|
assert vtherm.is_window_auto_configured is False
|
|
|
|
# 1. Turn on the window sensor
|
|
now = now + timedelta(minutes=1)
|
|
vtherm._set_now(now)
|
|
with patch("homeassistant.helpers.condition.state", return_value=True):
|
|
|
|
try_function = await send_window_change_event(vtherm, True, False, now)
|
|
|
|
now = now + timedelta(minutes=2)
|
|
vtherm._set_now(now)
|
|
await try_function(None)
|
|
|
|
# VTherm should have taken the window action
|
|
assert vtherm.target_temperature == 7 # Frost
|
|
# No change
|
|
assert vtherm.preset_mode is PRESET_BOOST
|
|
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
|
|
# 2. Change the target temperature
|
|
now = now + timedelta(minutes=1)
|
|
vtherm._set_now(now)
|
|
|
|
await vtherm.async_set_temperature(temperature=18.5)
|
|
await hass.async_block_till_done()
|
|
|
|
# VTherm should have taken the new preset temperature
|
|
assert vtherm.target_temperature == 7 # frost (window is still open)
|
|
assert vtherm.preset_mode is PRESET_NONE
|
|
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
|
|
# 3.Turn off the window sensor
|
|
now = now + timedelta(minutes=1)
|
|
vtherm._set_now(now)
|
|
with patch("homeassistant.helpers.condition.state", return_value=True):
|
|
|
|
try_function = await send_window_change_event(vtherm, False, True, now)
|
|
|
|
now = now + timedelta(minutes=2)
|
|
vtherm._set_now(now)
|
|
await try_function(None)
|
|
|
|
# VTherm should have restore the new target temperature
|
|
assert vtherm.target_temperature == 18.5 # restore new target temperature
|
|
# No change
|
|
assert vtherm.preset_mode is PRESET_NONE
|
|
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
|
|
# Clean the entity
|
|
vtherm.remove_thermostat()
|