Issue-739-refactor-to-modularize (#742)

* Refactor Presence Feature

* Add PresenceFeatureManager ok

* Python 3.13

* Fix presence test

* Refactor power feature

* Add Motion manager. All tests ok

* Tests ok. But tests are not complete

* All tests Window Feature Manager ok.

* All windows tests ok

* Fix all testus with feature_window_manager ok

* Add test_auto_start_stop feature manager. All tests ok

* Add safety feature_safety_manager
Rename config attribute from security_ to safety_

* Documentation and release

* Add safety manager direct tests

* Typo

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
Jean-Marc Collin
2025-01-01 16:30:18 +01:00
committed by GitHub
parent d2a94d33e8
commit 9e52c843bc
78 changed files with 5116 additions and 2382 deletions
+32 -23
View File
@@ -1,4 +1,4 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, abstract-method, too-many-lines, redefined-builtin
# pylint: disable=wildcard-import, unused-wildcard-import, unused-import, protected-access, unused-argument, line-too-long, abstract-method, too-many-lines, redefined-builtin
""" Some common resources """
import asyncio
@@ -8,7 +8,16 @@ from unittest.mock import patch, MagicMock # pylint: disable=unused-import
import pytest # pylint: disable=unused-import
from homeassistant.core import HomeAssistant, Event, EVENT_STATE_CHANGED, State
from homeassistant.const import UnitOfTemperature, STATE_ON, STATE_OFF, ATTR_TEMPERATURE
from homeassistant.const import (
UnitOfTemperature,
STATE_ON,
STATE_OFF,
ATTR_TEMPERATURE,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
STATE_HOME,
STATE_NOT_HOME,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.helpers.entity import Entity
@@ -188,9 +197,9 @@ FULL_CENTRAL_CONFIG = {
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: False,
}
@@ -229,9 +238,9 @@ FULL_CENTRAL_CONFIG_WITH_BOILER = {
CONF_MAX_POWER_SENSOR: "sensor.mock_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: True,
CONF_CENTRAL_BOILER_ACTIVATION_SRV: "switch.pompe_chaudiere/switch.turn_on",
CONF_CENTRAL_BOILER_DEACTIVATION_SRV: "switch.pompe_chaudiere/switch.turn_off",
@@ -590,12 +599,7 @@ async def create_thermostat(
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED
# We should reload the VTherm links
# vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api()
# central_config = vtherm_api.find_central_configuration()
entity = search_entity(hass, entity_id, CLIMATE_DOMAIN)
# if entity and hasattr(entity, "init_presets")::
# await entity.init_presets(central_config)
return entity
@@ -737,7 +741,7 @@ async def send_power_change_event(entity: BaseThermostat, new_power, date, sleep
)
},
)
await entity._async_power_changed(power_event)
await entity.power_manager._async_power_sensor_changed(power_event)
if sleep:
await asyncio.sleep(0.1)
@@ -763,7 +767,7 @@ async def send_max_power_change_event(
)
},
)
await entity._async_max_power_changed(power_event)
await entity.power_manager._async_max_power_sensor_changed(power_event)
if sleep:
await asyncio.sleep(0.1)
@@ -796,7 +800,7 @@ async def send_window_change_event(
),
},
)
ret = await entity._async_windows_changed(window_event)
ret = await entity.window_manager._window_sensor_changed(window_event)
if sleep:
await asyncio.sleep(0.1)
return ret
@@ -830,14 +834,14 @@ async def send_motion_change_event(
),
},
)
ret = await entity._async_motion_changed(motion_event)
ret = await entity.motion_manager._motion_sensor_changed(motion_event)
if sleep:
await asyncio.sleep(0.1)
return ret
async def send_presence_change_event(
entity: BaseThermostat, new_state: bool, old_state: bool, date, sleep=True
vtherm: BaseThermostat, new_state: bool, old_state: bool, date, sleep=True
):
"""Sending a new presence event simulating a change on the window state"""
_LOGGER.info(
@@ -845,26 +849,26 @@ async def send_presence_change_event(
new_state,
old_state,
date,
entity,
vtherm,
)
presence_event = Event(
EVENT_STATE_CHANGED,
{
"new_state": State(
entity_id=entity.entity_id,
entity_id=vtherm.entity_id,
state=STATE_ON if new_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
"old_state": State(
entity_id=entity.entity_id,
entity_id=vtherm.entity_id,
state=STATE_ON if old_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
},
)
ret = await entity._async_presence_changed(presence_event)
ret = await vtherm._presence_manager._presence_sensor_changed(presence_event)
if sleep:
await asyncio.sleep(0.1)
return ret
@@ -1000,7 +1004,7 @@ async def set_climate_preset_temp(
await temp_entity.async_set_native_value(temp)
else:
_LOGGER.warning(
"commons tests set_cliamte_preset_temp: cannot find number entity with entity_id '%s'",
"commons tests set_climate_preset_temp: cannot find number entity with entity_id '%s'",
number_entity_id,
)
@@ -1062,9 +1066,14 @@ async def set_all_climate_preset_temp(
NUMBER_DOMAIN,
)
assert temp_entity
if not temp_entity:
raise ConfigurationNotCompleteError(
f"'{number_entity_name}' don't exists as number entity"
)
# Because set_value is not implemented in Number class (really don't understand why...)
assert temp_entity.state == value
await hass.async_block_till_done()
#
# Side effects management
+9 -4
View File
@@ -89,7 +89,12 @@ MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG = {
}
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
CONF_UNDERLYING_LIST: ["switch.mock_4switch0", "switch.mock_4switch1","switch.mock_4switch2","switch.mock_4switch3"],
CONF_UNDERLYING_LIST: [
"switch.mock_4switch0",
"switch.mock_4switch1",
"switch.mock_4switch2",
"switch.mock_4switch3",
],
CONF_HEATER_KEEP_ALIVE: 0,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
@@ -195,9 +200,9 @@ MOCK_PRESENCE_AC_CONFIG = {
MOCK_ADVANCED_CONFIG = {
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
}
MOCK_DEFAULT_FEATURE_CONFIG = {
+6 -6
View File
@@ -53,8 +53,8 @@ async def test_over_climate_auto_fan_mode_turbo(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
},
)
@@ -119,8 +119,8 @@ async def test_over_climate_auto_fan_mode_not_turbo(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
},
)
@@ -189,8 +189,8 @@ async def test_over_climate_auto_fan_mode_turbo_activation(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
},
+96 -51
View File
@@ -1,4 +1,4 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable, too-many-lines
""" Test the Auto Start Stop algorithm management """
from datetime import datetime, timedelta
@@ -335,8 +335,8 @@ async def test_auto_start_stop_none_vtherm(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
@@ -363,15 +363,14 @@ async def test_auto_start_stop_none_vtherm(
# Initialize all temps
await set_all_climate_preset_temp(hass, vtherm, temps, "overclimate")
# Check correct initialization of auto_start_stop attributes
assert (
vtherm._attr_extra_state_attributes["auto_start_stop_level"]
== AUTO_START_STOP_LEVEL_NONE
)
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] is None
assert vtherm._attr_extra_state_attributes.get("auto_start_stop_level") is None
assert vtherm._attr_extra_state_attributes.get("auto_start_stop_dtmin") is None
# 1. Vtherm auto-start/stop should be in NONE mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_NONE
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_NONE
)
# 2. We should not find any switch Enable entity
assert (
@@ -427,8 +426,8 @@ async def test_auto_start_stop_medium_heat_vtherm(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_MEDIUM,
@@ -464,7 +463,10 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 15
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_MEDIUM
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_MEDIUM
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -488,7 +490,7 @@ async def test_auto_start_stop_medium_heat_vtherm(
# 3. Set current temperature to 19 5 min later
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -500,7 +502,7 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_mode == HVACMode.HEAT
assert mock_send_event.call_count == 0
assert (
vtherm._auto_start_stop_algo.accumulated_error == 0
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 0
) # target = current = 19
# 4. Set current temperature to 20 5 min later
@@ -516,7 +518,10 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_mode == HVACMode.HEAT
assert mock_send_event.call_count == 0
# accumulated_error = target - current = -1 x 5 min / 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2.5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error
== -2.5
)
# 5. Set current temperature to 21 5 min later -> should turn off
now = now + timedelta(minutes=5)
@@ -532,7 +537,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
# accumulated_error = -2.5 + target - current = -2 x 5 min / 2 capped to 5
assert vtherm._auto_start_stop_algo.accumulated_error == -5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -5
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -577,7 +584,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
await hass.async_block_till_done()
# accumulated_error = .... capped to -5
assert vtherm._auto_start_stop_algo.accumulated_error == -5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -5
)
# VTherm should stay stopped cause slope is too low to allow the turn to On
assert vtherm.hvac_mode == HVACMode.OFF
@@ -593,7 +602,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
await hass.async_block_till_done()
# accumulated_error = -5/2 + target - current = 1 x 20 min / 2 capped to 5
assert vtherm._auto_start_stop_algo.accumulated_error == 5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 5
)
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.HEAT
@@ -680,8 +691,8 @@ async def test_auto_start_stop_fast_ac_vtherm(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -717,7 +728,10 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
@@ -736,7 +750,7 @@ async def test_auto_start_stop_fast_ac_vtherm(
# 3. Set current temperature to 19 5 min later
now = now + timedelta(minutes=5)
# reset accumulated error for test
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -748,7 +762,8 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm.hvac_mode == HVACMode.COOL
assert mock_send_event.call_count == 0
assert (
vtherm._auto_start_stop_algo.accumulated_error == 0 # target = current = 25
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error
== 0 # target = current = 25
)
# 4. Set current temperature to 23 5 min later -> should turn off
@@ -764,7 +779,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm.hvac_mode == HVACMode.OFF
# accumulated_error = target - current = 2 x 5 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == 2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 2
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -809,7 +826,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
await hass.async_block_till_done()
# accumulated_error = 2/2 + target - current = -1 x 20 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# VTherm should stay stopped
assert vtherm.hvac_mode == HVACMode.OFF
@@ -826,7 +845,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
await hass.async_block_till_done()
# accumulated_error = 2/2 + target - current = -1 x 20 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.COOL
@@ -911,8 +932,8 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -948,7 +969,10 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
@@ -966,7 +990,7 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
# 3. Set current temperature to 21 5 min later to auto-stop
now = now + timedelta(minutes=5)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -977,7 +1001,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.OFF
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -1032,7 +1058,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
await hass.async_block_till_done()
assert vtherm.target_temperature == 21
assert vtherm._auto_start_stop_algo.accumulated_error == 2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 2
)
# VTherm should have been restarted
assert vtherm.hvac_mode == HVACMode.HEAT
@@ -1117,8 +1145,8 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1154,7 +1182,10 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in FAST mode and enable should be on
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1185,7 +1216,7 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
# 3. Set current temperature to 21 5 min later to auto-stop
now = now + timedelta(minutes=5)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1197,7 +1228,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
assert vtherm.hvac_mode == HVACMode.HEAT
# Not calculated cause enable = false
assert vtherm._auto_start_stop_algo.accumulated_error == 0
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 0
)
# a message should have been sent
assert mock_send_event.call_count == 0
@@ -1251,8 +1284,8 @@ async def test_auto_start_stop_fast_heat_window(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1288,7 +1321,10 @@ async def test_auto_start_stop_fast_heat_window(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1310,12 +1346,12 @@ async def test_auto_start_stop_fast_heat_window(
# VTherm should be heating
assert vtherm.hvac_mode == HVACMode.HEAT
# VTherm window_state should be off
assert vtherm.window_state == STATE_OFF
assert vtherm.window_state == STATE_UNKNOWN # cause condition is not evaluated
# 3. Set current temperature to 21 5 min later -> should turn off VTherm
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1426,8 +1462,8 @@ async def test_auto_start_stop_fast_heat_window_mixed(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1463,7 +1499,10 @@ async def test_auto_start_stop_fast_heat_window_mixed(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1485,7 +1524,7 @@ async def test_auto_start_stop_fast_heat_window_mixed(
# VTherm should be heating
assert vtherm.hvac_mode == HVACMode.HEAT
# VTherm window_state should be off
assert vtherm.window_state == STATE_OFF
assert vtherm.window_state == STATE_UNKNOWN # cause try_condition is not evaluated
# 3. Open the window and wait for the delay
now = now + timedelta(minutes=2)
@@ -1513,7 +1552,7 @@ async def test_auto_start_stop_fast_heat_window_mixed(
# 4. Set current temperature to 21 5 min later -> should turn off VTherm
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1605,8 +1644,8 @@ async def test_auto_start_stop_disable_vtherm_off(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: False,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1637,6 +1676,9 @@ async def test_auto_start_stop_disable_vtherm_off(
await set_all_climate_preset_temp(hass, vtherm, temps, "overclimate")
# Check correct initialization of auto_start_stop attributes
assert (
vtherm._attr_extra_state_attributes["is_auto_start_stop_configured"] is True
)
assert (
vtherm._attr_extra_state_attributes["auto_start_stop_level"]
== AUTO_START_STOP_LEVEL_FAST
@@ -1646,7 +1688,10 @@ async def test_auto_start_stop_disable_vtherm_off(
# 1. Vtherm auto-start/stop should be in FAST mode and enable should be on
vtherm._set_now(now)
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -0,0 +1,120 @@
# pylint: disable=unused-argument, line-too-long, protected-access, too-many-lines
""" Test the Window management """
import logging
from unittest.mock import PropertyMock, MagicMock
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.feature_auto_start_stop_manager import (
FeatureAutoStartStopManager,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
async def test_auto_start_stop_feature_manager_create(
hass: HomeAssistant,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
auto_start_stop_manager = FeatureAutoStartStopManager(fake_vtherm, hass)
assert auto_start_stop_manager is not None
assert auto_start_stop_manager.is_configured is False
assert auto_start_stop_manager.is_auto_stopped is False
assert auto_start_stop_manager.auto_start_stop_enable is False
assert auto_start_stop_manager.name == "the name"
assert len(auto_start_stop_manager._active_listener) == 0
custom_attributes = {}
auto_start_stop_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["is_auto_start_stop_configured"] is False
# assert custom_attributes["auto_start_stop_enable"] is False
# assert custom_attributes["auto_start_stop_level"] == AUTO_START_STOP_LEVEL_NONE
# assert custom_attributes["auto_start_stop_dtmin"] is None
# assert custom_attributes["auto_start_stop_accumulated_error"] is None
# assert custom_attributes["auto_start_stop_accumulated_error_threshold"] is None
# assert custom_attributes["auto_start_stop_last_switch_date"] is None
@pytest.mark.parametrize(
"use_auto_start_stop_feature, level, is_configured",
[
# fmt: off
( True, AUTO_START_STOP_LEVEL_NONE, True),
( True, AUTO_START_STOP_LEVEL_SLOW, True),
( True, AUTO_START_STOP_LEVEL_MEDIUM, True),
( True, AUTO_START_STOP_LEVEL_FAST, True),
# Level is missing , will be set to None
( True, None, True),
( False, AUTO_START_STOP_LEVEL_NONE, False),
( False, AUTO_START_STOP_LEVEL_SLOW, False),
( False, AUTO_START_STOP_LEVEL_MEDIUM, False),
( False, AUTO_START_STOP_LEVEL_FAST, False),
# Level is missing , will be set to None
( False, None, False),
# fmt: on
],
)
async def test_auto_start_stop_feature_manager_post_init(
hass: HomeAssistant, use_auto_start_stop_feature, level, is_configured
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
auto_start_stop_manager = FeatureAutoStartStopManager(fake_vtherm, hass)
assert auto_start_stop_manager is not None
# 2. post_init
auto_start_stop_manager.post_init(
{
CONF_USE_AUTO_START_STOP_FEATURE: use_auto_start_stop_feature,
CONF_AUTO_START_STOP_LEVEL: level,
}
)
assert auto_start_stop_manager.is_configured is is_configured
assert (
auto_start_stop_manager.auto_start_stop_level == level
if level and is_configured
else AUTO_START_STOP_LEVEL_NONE
)
assert auto_start_stop_manager.auto_start_stop_enable is False
assert auto_start_stop_manager._auto_start_stop_algo is not None
custom_attributes = {}
auto_start_stop_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["is_auto_start_stop_configured"] is is_configured
if auto_start_stop_manager.is_configured:
assert custom_attributes["auto_start_stop_enable"] is False
assert (
custom_attributes["auto_start_stop_level"] == level
if level and is_configured
else AUTO_START_STOP_LEVEL_NONE
)
assert (
custom_attributes["auto_start_stop_dtmin"]
== auto_start_stop_manager._auto_start_stop_algo.dt_min
)
assert (
custom_attributes["auto_start_stop_accumulated_error"]
== auto_start_stop_manager._auto_start_stop_algo.accumulated_error
)
assert (
custom_attributes["auto_start_stop_accumulated_error_threshold"]
== auto_start_stop_manager._auto_start_stop_algo.accumulated_error_threshold
)
assert (
custom_attributes["auto_start_stop_last_switch_date"]
== auto_start_stop_manager._auto_start_stop_algo.last_switch_date
)
+26 -24
View File
@@ -57,8 +57,8 @@ async def test_security_binary_sensors(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
@@ -84,17 +84,17 @@ async def test_security_binary_sensors(
# Set temperature in the past
event_timestamp = now - timedelta(minutes=6)
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
# set temperature to 15 so that on_percent will be > safety_min_on_percent (0.2)
await send_temperature_change_event(entity, 15, event_timestamp)
assert entity.security_state is True
assert entity.safety_state is STATE_ON
# Simulate the event reception
await security_binary_sensor.async_my_climate_changed()
assert security_binary_sensor.state == STATE_ON
# set temperature now
await send_temperature_change_event(entity, 15, now)
assert entity.security_state is False
assert entity.safety_state is not STATE_ON
# Simulate the event reception
await security_binary_sensor.async_my_climate_changed()
assert security_binary_sensor.state == STATE_OFF
@@ -134,8 +134,8 @@ async def test_overpowering_binary_sensors(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
@@ -159,8 +159,8 @@ async def test_overpowering_binary_sensors(
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert await entity.check_overpowering() is False
assert entity.overpowering_state is None
assert await entity.power_manager.check_overpowering() is False
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state is STATE_OFF
@@ -168,8 +168,8 @@ async def test_overpowering_binary_sensors(
await send_power_change_event(entity, 100, now)
await send_max_power_change_event(entity, 150, now)
assert await entity.check_overpowering() is True
assert entity.overpowering_state is True
assert await entity.power_manager.check_overpowering() is True
assert entity.power_manager.overpowering_state is STATE_ON
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
@@ -177,8 +177,8 @@ async def test_overpowering_binary_sensors(
# set max power to a low value
await send_max_power_change_event(entity, 201, now)
assert await entity.check_overpowering() is False
assert entity.overpowering_state is False
assert await entity.power_manager.check_overpowering() is False
assert entity.power_manager.overpowering_state is STATE_OFF
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_OFF
@@ -218,8 +218,8 @@ async def test_window_binary_sensors(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
},
@@ -241,7 +241,7 @@ async def test_window_binary_sensors(
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
await window_binary_sensor.async_my_climate_changed()
assert window_binary_sensor.state is STATE_OFF
@@ -306,10 +306,12 @@ async def test_motion_binary_sensors(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 0, # important to not been obliged to wait
CONF_MOTION_PRESET: PRESET_BOOST,
CONF_NO_MOTION_PRESET: PRESET_ECO,
},
)
@@ -329,7 +331,7 @@ async def test_motion_binary_sensors(
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.motion_state is None
assert entity.motion_state is STATE_UNKNOWN
await motion_binary_sensor.async_my_climate_changed()
assert motion_binary_sensor.state is STATE_OFF
@@ -397,8 +399,8 @@ async def test_presence_binary_sensors(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
},
)
@@ -419,7 +421,7 @@ async def test_presence_binary_sensors(
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.presence_state is None
assert entity.presence_state is STATE_UNKNOWN
await presence_binary_sensor.async_my_climate_changed()
assert presence_binary_sensor.state is STATE_OFF
@@ -480,8 +482,8 @@ async def test_binary_sensors_over_climate_minimal(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
+21 -21
View File
@@ -37,7 +37,7 @@ async def test_bug_63(
skip_turn_on_off_heater,
skip_send_event,
):
"""Test that it should be possible to set the security_default_on_percent to 0"""
"""Test that it should be possible to set the safety_default_on_percent to 0"""
entry = MockConfigEntry(
domain=DOMAIN,
@@ -63,9 +63,9 @@ async def test_bug_63(
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.0, # !! here
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.0, # !! here
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.0, # !! here
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.0, # !! here
CONF_DEVICE_POWER: 200,
},
)
@@ -75,8 +75,8 @@ async def test_bug_63(
)
assert entity
assert entity._security_min_on_percent == 0
assert entity._security_default_on_percent == 0
assert entity.safety_manager.safety_min_on_percent == 0
assert entity.safety_manager.safety_default_on_percent == 0
# Waiting for answer in https://github.com/jmcollin78/versatile_thermostat/issues/64
@@ -89,7 +89,7 @@ async def test_bug_64(
skip_turn_on_off_heater,
skip_send_event,
):
"""Test that it should be possible to set the security_default_on_percent to 0"""
"""Test that it should be possible to set the safety_default_on_percent to 0"""
entry = MockConfigEntry(
domain=DOMAIN,
@@ -115,9 +115,9 @@ async def test_bug_64(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1, # !! here
CONF_DEVICE_POWER: 200,
},
)
@@ -299,8 +299,8 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
@@ -334,7 +334,7 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
await entity.async_set_preset_mode(PRESET_COMFORT)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_COMFORT
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
assert entity.target_temperature == 18
# waits that the heater starts
await asyncio.sleep(0.1)
@@ -346,10 +346,10 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
# Send power mesurement (theheater is already in the power measurement)
await send_power_change_event(entity, 100, datetime.now())
# No overpowering yet
assert await entity.check_overpowering() is False
assert await entity.power_manager.check_overpowering() is False
# All configuration is complete and power is < power_max
assert entity.preset_mode is PRESET_COMFORT
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
assert entity.is_device_active is True
# 2. An already active heater that switch preset will not switch to overpowering
@@ -365,10 +365,10 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
# waits that the heater starts
await asyncio.sleep(0.1)
assert await entity.check_overpowering() is False
assert await entity.power_manager.check_overpowering() is False
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
assert entity.target_temperature == 19
assert mock_service_call.call_count >= 1
@@ -385,10 +385,10 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
# waits that the heater starts
await asyncio.sleep(0.1)
assert await entity.check_overpowering() is True
assert await entity.power_manager.check_overpowering() is True
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_POWER
assert entity.overpowering_state is True
assert entity.power_manager.overpowering_state is STATE_ON
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -510,8 +510,8 @@ async def test_bug_465(hass: HomeAssistant, skip_hass_states_is_state):
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
},
+18 -18
View File
@@ -111,9 +111,9 @@ async def test_update_central_boiler_state_simple(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
@@ -298,9 +298,9 @@ async def test_update_central_boiler_state_multiple(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
@@ -626,9 +626,9 @@ async def test_update_central_boiler_state_simple_valve(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
@@ -800,9 +800,9 @@ async def test_update_central_boiler_state_simple_climate(
CONF_USE_PRESENCE_FEATURE: False,
CONF_UNDERLYING_LIST: [climate1.entity_id],
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
@@ -990,9 +990,9 @@ async def test_update_central_boiler_state_simple_climate_valve_regulation(
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH,
CONF_AUTO_REGULATION_USE_DEVICE_TEMP: False,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
@@ -1255,9 +1255,9 @@ async def test_bug_339(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: climate1.entity_id,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
+80 -54
View File
@@ -67,9 +67,9 @@ async def test_add_a_central_config(hass: HomeAssistant, skip_hass_states_is_sta
CONF_MAX_POWER_SENSOR: "sensor.mock_central_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: False,
},
)
@@ -135,9 +135,9 @@ async def test_minimal_over_switch_wo_central_config(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
# CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
# CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
# CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test
@@ -167,16 +167,16 @@ async def test_minimal_over_switch_wo_central_config(
assert entity.max_temp == 18
assert entity.target_temperature_step == 0.3
assert entity.preset_modes == ["none", "frost", "eco", "comfort", "boost"]
assert entity.is_window_auto_enabled is False
assert entity.window_manager.is_window_auto_configured is False
assert entity.nb_underlying_entities == 1
assert entity.underlying_entity_id(0) == "switch.mock_switch"
assert entity.proportional_algorithm is not None
assert entity.proportional_algorithm._tpi_coef_int == 0.3
assert entity.proportional_algorithm._tpi_coef_ext == 0.01
assert entity.proportional_algorithm._minimal_activation_delay == 30
assert entity._security_delay_min == 5
assert entity._security_min_on_percent == 0.3
assert entity._security_default_on_percent == 0.1
assert entity.safety_manager.safety_delay_min == 5
assert entity.safety_manager.safety_min_on_percent == 0.3
assert entity.safety_manager.safety_default_on_percent == 0.1
assert entity.is_inversed
entity.remove_thermostat()
@@ -220,9 +220,9 @@ async def test_full_over_switch_wo_central_config(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 30,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 3,
@@ -274,28 +274,42 @@ async def test_full_over_switch_wo_central_config(
assert entity.proportional_algorithm._tpi_coef_int == 0.3
assert entity.proportional_algorithm._tpi_coef_ext == 0.01
assert entity.proportional_algorithm._minimal_activation_delay == 30
assert entity._security_delay_min == 5
assert entity._security_min_on_percent == 0.3
assert entity._security_default_on_percent == 0.1
assert entity.safety_manager.safety_delay_min == 5
assert entity.safety_manager.safety_min_on_percent == 0.3
assert entity.safety_manager.safety_default_on_percent == 0.1
assert entity.is_inversed is False
assert entity.is_window_auto_enabled is False # we have an entity_id
assert entity._window_sensor_entity_id == "binary_sensor.mock_window_sensor"
assert entity._window_delay_sec == 30
assert entity._window_auto_close_threshold == 0.1
assert entity._window_auto_open_threshold == 3
assert entity._window_auto_max_duration == 5
assert (
entity.window_manager.is_window_auto_configured is False
) # we have an entity_id
assert (
entity.window_manager._window_sensor_entity_id
== "binary_sensor.mock_window_sensor"
)
assert entity.window_manager.window_delay_sec == 30
assert entity.window_manager.window_auto_close_threshold == 0.1
assert entity.window_manager.window_auto_open_threshold == 3
assert entity.window_manager.window_auto_max_duration == 5
assert entity._motion_sensor_entity_id == "binary_sensor.mock_motion_sensor"
assert entity._motion_delay_sec == 10
assert entity._motion_off_delay_sec == 29
assert entity._motion_preset == "comfort"
assert entity._no_motion_preset == "eco"
assert (
entity.motion_manager.motion_sensor_entity_id
== "binary_sensor.mock_motion_sensor"
)
assert entity.motion_manager.motion_delay_sec == 10
assert entity.motion_manager.motion_off_delay_sec == 29
assert entity.motion_manager.motion_preset == "comfort"
assert entity.motion_manager.no_motion_preset == "eco"
assert entity._power_sensor_entity_id == "sensor.mock_power_sensor"
assert entity._max_power_sensor_entity_id == "sensor.mock_max_power_sensor"
assert entity.power_manager.power_sensor_entity_id == "sensor.mock_power_sensor"
assert (
entity.power_manager.max_power_sensor_entity_id
== "sensor.mock_max_power_sensor"
)
assert entity._presence_sensor_entity_id == "binary_sensor.mock_presence_sensor"
assert (
entity._presence_manager.presence_sensor_entity_id
== "binary_sensor.mock_presence_sensor"
)
entity.remove_thermostat()
@@ -334,9 +348,9 @@ async def test_full_over_switch_with_central_config(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 30,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 3,
@@ -387,29 +401,41 @@ async def test_full_over_switch_with_central_config(
assert entity.proportional_algorithm._tpi_coef_int == 0.5
assert entity.proportional_algorithm._tpi_coef_ext == 0.02
assert entity.proportional_algorithm._minimal_activation_delay == 11
assert entity._security_delay_min == 61
assert entity._security_min_on_percent == 0.5
assert entity._security_default_on_percent == 0.2
assert entity.safety_manager.safety_delay_min == 61
assert entity.safety_manager.safety_min_on_percent == 0.5
assert entity.safety_manager.safety_default_on_percent == 0.2
assert entity.is_inversed is False
# We have an entity so window auto is not enabled
assert entity.is_window_auto_enabled is False
assert entity._window_sensor_entity_id == "binary_sensor.mock_window_sensor"
assert entity._window_delay_sec == 15
assert entity._window_auto_close_threshold == 1
assert entity._window_auto_open_threshold == 4
assert entity._window_auto_max_duration == 31
assert entity.window_manager.is_window_auto_configured is False
assert (
entity.window_manager._window_sensor_entity_id
== "binary_sensor.mock_window_sensor"
)
assert entity.window_manager.window_delay_sec == 15
assert entity.window_manager.window_auto_close_threshold == 1
assert entity.window_manager.window_auto_open_threshold == 4
assert entity.window_manager.window_auto_max_duration == 31
assert entity._motion_sensor_entity_id == "binary_sensor.mock_motion_sensor"
assert entity._motion_delay_sec == 31
assert entity._motion_off_delay_sec == 301
assert entity._motion_preset == "boost"
assert entity._no_motion_preset == "frost"
assert (
entity.motion_manager.motion_sensor_entity_id
== "binary_sensor.mock_motion_sensor"
)
assert entity.motion_manager.motion_delay_sec == 31
assert entity.motion_manager.motion_off_delay_sec == 301
assert entity.motion_manager.motion_preset == "boost"
assert entity.motion_manager.no_motion_preset == "frost"
assert entity._power_sensor_entity_id == "sensor.mock_power_sensor"
assert entity._max_power_sensor_entity_id == "sensor.mock_max_power_sensor"
assert entity.power_manager.power_sensor_entity_id == "sensor.mock_power_sensor"
assert (
entity.power_manager.max_power_sensor_entity_id
== "sensor.mock_max_power_sensor"
)
assert entity._presence_sensor_entity_id == "binary_sensor.mock_presence_sensor"
assert (
entity._presence_manager.presence_sensor_entity_id
== "binary_sensor.mock_presence_sensor"
)
entity.remove_thermostat()
@@ -469,7 +495,7 @@ async def test_migration_of_central_config(
central_config_entry = MockConfigEntry(
version=CONFIG_VERSION,
# An old minor version
minor_version=1,
minor_version=0,
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
@@ -483,9 +509,9 @@ async def test_migration_of_central_config(
CONF_TPI_COEF_INT: 0.5,
CONF_TPI_COEF_EXT: 0.02,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
# The old central_boiler parameter
"add_central_boiler_control": True,
CONF_CENTRAL_BOILER_ACTIVATION_SRV: "switch.pompe_chaudiere/switch.turn_on",
+29 -29
View File
@@ -56,9 +56,9 @@ async def test_config_with_central_mode_true(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
},
)
@@ -103,9 +103,9 @@ async def test_config_with_central_mode_false(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
},
)
@@ -153,9 +153,9 @@ async def test_config_with_central_mode_none(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
},
)
@@ -205,9 +205,9 @@ async def test_switch_change_central_mode_true(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
},
)
@@ -347,9 +347,9 @@ async def test_switch_ac_change_central_mode_true(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_AC_MODE: True,
},
)
@@ -482,9 +482,9 @@ async def test_climate_ac_change_central_mode_false(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
},
)
@@ -624,9 +624,9 @@ async def test_climate_ac_only_change_central_mode_true(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
},
)
@@ -778,9 +778,9 @@ async def test_switch_change_central_mode_true_with_window(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_WINDOW_SENSOR: "binary_sensor.window_sensor",
CONF_WINDOW_DELAY: 0, # To be not obliged to wait
CONF_MOTION_SENSOR: "input_boolean.motion_sensor",
@@ -816,7 +816,7 @@ async def test_switch_change_central_mode_true_with_window(
assert entity.hvac_mode == HVACMode.HEAT
assert entity.preset_mode == PRESET_ACTIVITY
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
# 2 Open the window
with patch(
@@ -935,9 +935,9 @@ async def test_switch_change_central_mode_true_with_cool_only_and_window(
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_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_WINDOW_SENSOR: "binary_sensor.window_sensor",
CONF_WINDOW_DELAY: 0, # To be not obliged to wait
CONF_MOTION_SENSOR: "input_boolean.motion_sensor",
@@ -973,7 +973,7 @@ async def test_switch_change_central_mode_true_with_cool_only_and_window(
assert entity.hvac_mode == HVACMode.HEAT
assert entity.preset_mode == PRESET_ACTIVITY
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
# 2 Change central_mode to COOL_ONLY
with patch("homeassistant.core.ServiceRegistry.async_call"):
+25 -26
View File
@@ -470,9 +470,9 @@ async def test_user_config_flow_over_climate(
result["flow_id"],
user_input={
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
},
)
assert result["type"] == FlowResultType.MENU
@@ -496,9 +496,9 @@ async def test_user_config_flow_over_climate(
"data"
] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | {
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
} | MOCK_DEFAULT_FEATURE_CONFIG | {
CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_PRESETS_CENTRAL_CONFIG: False,
@@ -1077,9 +1077,9 @@ async def test_user_config_flow_over_climate_auto_start_stop(
result["flow_id"],
user_input={
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
},
)
assert result["type"] == FlowResultType.MENU
@@ -1104,9 +1104,9 @@ async def test_user_config_flow_over_climate_auto_start_stop(
"data"
] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | {
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
} | MOCK_DEFAULT_FEATURE_CONFIG | {
CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_TPI_CENTRAL_CONFIG: False,
@@ -1274,9 +1274,9 @@ async def test_user_config_flow_over_switch_bug_552_tpi(
result["flow_id"],
user_input={
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
},
)
@@ -1359,9 +1359,9 @@ async def test_user_config_flow_over_switch_bug_552_tpi(
CONF_TEMP_MAX: 30,
CONF_STEP_TEMPERATURE: 0.5,
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_TPI_CENTRAL_CONFIG: False,
CONF_USE_PRESETS_CENTRAL_CONFIG: False,
@@ -1388,8 +1388,7 @@ async def test_user_config_flow_over_switch_bug_552_tpi(
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
# @pytest.mark.parametrize("expected_lingering_timers", [True])
# @pytest.mark.skip
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_user_config_flow_over_climate_valve(
hass: HomeAssistant, skip_hass_states_get
): # pylint: disable=unused-argument
@@ -1658,9 +1657,9 @@ async def test_user_config_flow_over_climate_valve(
result["flow_id"],
user_input={
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
},
)
assert result["type"] == FlowResultType.MENU
@@ -1686,9 +1685,9 @@ async def test_user_config_flow_over_climate_valve(
"data"
] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | {
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
} | MOCK_DEFAULT_FEATURE_CONFIG | {
CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_PRESETS_CENTRAL_CONFIG: False,
+2 -2
View File
@@ -42,8 +42,8 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test
+19 -15
View File
@@ -25,6 +25,12 @@ async def test_last_seen_feature(hass: HomeAssistant, skip_hass_states_is_state)
"""
tz = get_tz(hass) # pylint: disable=invalid-name
temps = {
"frost": 7,
"eco": 17,
"comfort": 18,
"boost": 19,
}
entry = MockConfigEntry(
domain=DOMAIN,
@@ -39,22 +45,18 @@ async def test_last_seen_feature(hass: HomeAssistant, skip_hass_states_is_state)
"cycle_min": 5,
"temp_min": 15,
"temp_max": 30,
"frost_temp": 7,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
"use_window_feature": False,
"use_motion_feature": False,
"use_power_feature": False,
"use_presence_feature": False,
"heater_entity_id": "switch.mock_switch",
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
"proportional_function": "tpi",
"tpi_coef_int": 0.3,
"tpi_coef_ext": 0.01,
"minimal_activation_delay": 30,
"security_delay_min": 5, # 5 minutes
"security_min_on_percent": 0.2,
"security_default_on_percent": 0.1,
CONF_SAFETY_DELAY_MIN: 5, # 5 minutes
CONF_SAFETY_MIN_ON_PERCENT: 0.2,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
},
)
@@ -65,8 +67,10 @@ async def test_last_seen_feature(hass: HomeAssistant, skip_hass_states_is_state)
)
assert entity
assert entity._security_state is False
assert entity.preset_mode is not PRESET_SECURITY
await set_all_climate_preset_temp(hass, entity, temps, "theoverswitchmockname")
assert entity.safety_manager.is_safety_detected is False
assert entity.preset_mode is not PRESET_SAFETY
assert entity._last_ext_temperature_measure is not None
assert entity._last_temperature_measure is not None
assert (entity._last_temperature_measure.astimezone(tz) - now).total_seconds() < 1
@@ -94,15 +98,15 @@ async def test_last_seen_feature(hass: HomeAssistant, skip_hass_states_is_state)
) as mock_heater_on:
event_timestamp = now - timedelta(minutes=6)
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
# set temperature to 15 so that on_percent will be > safety_min_on_percent (0.2)
await send_temperature_change_event(entity, 15, event_timestamp)
assert entity.security_state is True
assert entity.preset_mode == PRESET_SECURITY
assert entity.safety_state is STATE_ON
assert entity.preset_mode == PRESET_SAFETY
assert mock_send_event.call_count == 3
mock_send_event.assert_has_calls(
[
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_SECURITY}),
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_SAFETY}),
call.send_event(
EventType.TEMPERATURE_EVENT,
{
@@ -135,7 +139,7 @@ async def test_last_seen_feature(hass: HomeAssistant, skip_hass_states_is_state)
# 3. change the last seen sensor
event_timestamp = now - timedelta(minutes=4)
await send_last_seen_temperature_change_event(entity, event_timestamp)
assert entity.security_state is False
assert entity.safety_state is not STATE_ON
assert entity.preset_mode is PRESET_COMFORT
assert entity._last_temperature_measure == event_timestamp
+327 -61
View File
@@ -1,22 +1,288 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable, too-many-lines
""" Test the Window management """
from datetime import datetime, timedelta
import logging
from unittest.mock import patch
from unittest.mock import patch, call, AsyncMock, MagicMock, PropertyMock
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.feature_motion_manager import (
FeatureMotionManager,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
@pytest.mark.parametrize(
"current_state, new_state, temp, nb_call, motion_state, is_motion_detected, preset_refresh, changed",
[
(STATE_OFF, STATE_ON, 21, 1, STATE_ON, True, PRESET_BOOST, True),
# motion is ON. So is_motion_detected is true and preset is BOOST
(STATE_OFF, STATE_ON, 21, 1, STATE_ON, True, PRESET_BOOST, True),
# current_state is ON and motion is OFF. So is_motion_detected is false and preset is ECO
(STATE_ON, STATE_OFF, 17, 1, STATE_OFF, False, PRESET_ECO, True),
],
)
async def test_motion_feature_manager_refresh(
hass: HomeAssistant,
current_state,
new_state, # new state of motion event
temp,
nb_call,
motion_state,
is_motion_detected,
preset_refresh,
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_ACTIVITY)
# 1. creation
motion_manager = FeatureMotionManager(fake_vtherm, hass)
assert motion_manager is not None
assert motion_manager.is_configured is False
assert motion_manager.is_motion_detected is False
assert motion_manager.motion_state == STATE_UNAVAILABLE
assert motion_manager.name == "the name"
assert len(motion_manager._active_listener) == 0
custom_attributes = {}
motion_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["motion_sensor_entity_id"] is None
assert custom_attributes["motion_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_motion_configured"] is False
assert custom_attributes["motion_preset"] is None
assert custom_attributes["no_motion_preset"] is None
assert custom_attributes["motion_delay_sec"] == 0
assert custom_attributes["motion_off_delay_sec"] == 0
# 2. post_init
motion_manager.post_init(
{
CONF_MOTION_SENSOR: "sensor.the_motion_sensor",
CONF_USE_MOTION_FEATURE: True,
CONF_MOTION_DELAY: 10,
CONF_MOTION_OFF_DELAY: 30,
CONF_MOTION_PRESET: PRESET_BOOST,
CONF_NO_MOTION_PRESET: PRESET_ECO,
}
)
assert motion_manager.is_configured is True
assert motion_manager.motion_state == STATE_UNKNOWN
assert motion_manager.is_motion_detected is False
custom_attributes = {}
motion_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["motion_sensor_entity_id"] == "sensor.the_motion_sensor"
assert custom_attributes["motion_state"] == STATE_UNKNOWN
assert custom_attributes["is_motion_configured"] is True
assert custom_attributes["motion_preset"] is PRESET_BOOST
assert custom_attributes["no_motion_preset"] is PRESET_ECO
assert custom_attributes["motion_delay_sec"] == 10
assert custom_attributes["motion_off_delay_sec"] == 30
# 3. start listening
motion_manager.start_listening()
assert motion_manager.is_configured is True
assert motion_manager.motion_state == STATE_UNKNOWN
assert motion_manager.is_motion_detected is False
assert len(motion_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.find_preset_temp.return_value = temp
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.async_control_heating = AsyncMock()
fake_vtherm.recalculate = MagicMock()
# force old state for the test
motion_manager._motion_state = current_state
ret = await motion_manager.refresh_state()
assert ret == changed
assert motion_manager.is_configured is True
# in the refresh there is no delay
assert motion_manager.motion_state == new_state
assert motion_manager.is_motion_detected is is_motion_detected
assert mock_get_state.call_count == 1
assert fake_vtherm.find_preset_temp.call_count == nb_call
if nb_call == 1:
fake_vtherm.find_preset_temp.assert_has_calls(
[
call.find_preset_temp(preset_refresh),
]
)
assert fake_vtherm.change_target_temperature.call_count == nb_call
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.find_preset_temp(temp),
]
)
# We do not call control_heating at startup
assert fake_vtherm.recalculate.call_count == 0
assert fake_vtherm.async_control_heating.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
motion_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["motion_sensor_entity_id"] == "sensor.the_motion_sensor"
assert custom_attributes["motion_state"] == new_state
assert custom_attributes["is_motion_configured"] is True
assert custom_attributes["motion_preset"] is PRESET_BOOST
assert custom_attributes["no_motion_preset"] is PRESET_ECO
assert custom_attributes["motion_delay_sec"] == 10
assert custom_attributes["motion_off_delay_sec"] == 30
motion_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, long_enough, new_state, temp, nb_call, motion_state, is_motion_detected, preset_event, changed",
[
(STATE_OFF, True, STATE_ON, 21, 1, STATE_ON, True, PRESET_BOOST, True),
# motion is ON but for not enough time but sensor is on at the end. So is_motion_detected is true and preset is BOOST
(STATE_OFF, False, STATE_ON, 21, 1, STATE_ON, True, PRESET_BOOST, True),
# motion is OFF for enough time. So is_motion_detected is false and preset is ECO
(STATE_ON, True, STATE_OFF, 17, 1, STATE_OFF, False, PRESET_ECO, True),
# motion is OFF for not enough time. So is_motion_detected is false and preset is ECO
(STATE_ON, False, STATE_OFF, 21, 1, STATE_ON, True, PRESET_BOOST, True),
],
)
async def test_motion_feature_manager_event(
hass: HomeAssistant,
current_state,
long_enough,
new_state, # new state of motion event
temp,
nb_call,
motion_state,
is_motion_detected,
preset_event,
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_ACTIVITY)
# 1. iniitialization creation, post_init, start_listening
motion_manager = FeatureMotionManager(fake_vtherm, hass)
motion_manager.post_init(
{
CONF_MOTION_SENSOR: "sensor.the_motion_sensor",
CONF_USE_MOTION_FEATURE: True,
CONF_MOTION_DELAY: 10,
CONF_MOTION_OFF_DELAY: 30,
CONF_MOTION_PRESET: PRESET_BOOST,
CONF_NO_MOTION_PRESET: PRESET_ECO,
}
)
motion_manager.start_listening()
# 2. test _motion_sensor_changed with the parametrized
# fmt: off
with patch("homeassistant.helpers.condition.state", return_value=long_enough), \
patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)):
# fmt: on
fake_vtherm.find_preset_temp.return_value = temp
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.async_control_heating = AsyncMock()
fake_vtherm.recalculate = MagicMock()
# force old state for the test
motion_manager._motion_state = current_state
delay = await motion_manager._motion_sensor_changed(
event=Event(
event_type=EVENT_STATE_CHANGED,
data={
"entity_id": "sensor.the_motion_sensor",
"new_state": State("sensor.the_motion_sensor", new_state),
"old_state": State("sensor.the_motion_sensor", STATE_UNAVAILABLE),
}))
assert delay is not None
await delay(None)
assert motion_manager.is_configured is True
assert motion_manager.motion_state == motion_state
assert motion_manager.is_motion_detected is is_motion_detected
assert fake_vtherm.find_preset_temp.call_count == nb_call
if nb_call == 1:
fake_vtherm.find_preset_temp.assert_has_calls(
[
call.find_preset_temp(preset_event),
]
)
assert fake_vtherm.change_target_temperature.call_count == nb_call
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.find_preset_temp(temp),
]
)
assert fake_vtherm.recalculate.call_count == 1
assert fake_vtherm.async_control_heating.call_count == 1
fake_vtherm.async_control_heating.assert_has_calls([
call.async_control_heating(force=True)
])
fake_vtherm.reset_mock()
# 3. Check custom_attributes
custom_attributes = {}
motion_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["motion_sensor_entity_id"] == "sensor.the_motion_sensor"
assert custom_attributes["motion_state"] == motion_state
assert custom_attributes["is_motion_configured"] is True
assert custom_attributes["motion_preset"] is PRESET_BOOST
assert custom_attributes["no_motion_preset"] is PRESET_ECO
assert custom_attributes["motion_delay_sec"] == 10
assert custom_attributes["motion_off_delay_sec"] == 30
motion_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_movement_management_time_not_enough(
async def test_motion_management_time_not_enough(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test the Presence management when time is not enough"""
temps = {
"frost": 10,
"eco": 17,
"comfort": 18,
"boost": 19,
"frost_away": 10,
"eco_away": 17,
"comfort_away": 18,
"boost_away": 19,
}
entry = MockConfigEntry(
domain=DOMAIN,
@@ -30,23 +296,17 @@ async def test_movement_management_time_not_enough(
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
"eco_away_temp": 17,
"comfort_away_temp": 18,
"boost_away_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: True,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: True,
CONF_HEATER: "switch.mock_switch",
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: 10,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 10,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 10, # important to not been obliged to wait
CONF_MOTION_OFF_DELAY: 30,
@@ -60,11 +320,12 @@ async def test_movement_management_time_not_enough(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
await set_all_climate_preset_temp(hass, entity, temps, "theoverswitchmockname")
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
# start heating, in boost mode, when someone is present. We block the control_heating to avoid running a cycle
# 1. start heating, in boost mode, when someone is present. We block the control_heating to avoid running a cycle
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
):
@@ -75,17 +336,17 @@ async def test_movement_management_time_not_enough(
assert entity.preset_mode is PRESET_ACTIVITY
# because no motion is detected yet
assert entity.target_temperature == 18
assert entity.motion_state is None
assert entity.presence_state is None
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state is STATE_UNKNOWN
event_timestamp = now - timedelta(minutes=5)
await send_temperature_change_event(entity, 18, event_timestamp)
await send_ext_temperature_change_event(entity, 10, event_timestamp)
await send_presence_change_event(entity, True, False, event_timestamp)
assert entity.presence_state == "on"
assert entity.presence_state == STATE_ON
# starts detecting motion with time not enough
# 2. starts detecting motion with time not enough
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
@@ -104,7 +365,9 @@ async def test_movement_management_time_not_enough(
),
):
event_timestamp = now - timedelta(minutes=4)
try_condition = await send_motion_change_event(entity, True, False, event_timestamp)
try_condition = await send_motion_change_event(
entity, True, False, event_timestamp
)
# Will return False -> we will stay on movement False
await try_condition(None)
@@ -137,7 +400,9 @@ async def test_movement_management_time_not_enough(
"homeassistant.helpers.condition.state", return_value=True
) as mock_condition:
event_timestamp = now - timedelta(minutes=3)
try_condition = await send_motion_change_event(entity, True, False, event_timestamp)
try_condition = await send_motion_change_event(
entity, True, False, event_timestamp
)
# Will return True -> we will switch to movement On
await try_condition(None)
@@ -168,7 +433,9 @@ async def test_movement_management_time_not_enough(
),
):
event_timestamp = now - timedelta(minutes=2)
try_condition = await send_motion_change_event(entity, False, True, event_timestamp)
try_condition = await send_motion_change_event(
entity, False, True, event_timestamp
)
# Will return False -> we will stay to movement On
await try_condition(None)
@@ -200,7 +467,9 @@ async def test_movement_management_time_not_enough(
"homeassistant.helpers.condition.state", return_value=True
) as mock_condition:
event_timestamp = now - timedelta(minutes=1)
try_condition = await send_motion_change_event(entity, False, True, event_timestamp)
try_condition = await send_motion_change_event(
entity, False, True, event_timestamp
)
# Will return True -> we will switch to movement Off
await try_condition(None)
@@ -221,7 +490,7 @@ async def test_movement_management_time_not_enough(
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_movement_management_time_enough_and_presence(
async def test_motion_management_time_enough_and_presence(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test the Motion management when time is not enough"""
@@ -253,8 +522,8 @@ async def test_movement_management_time_enough_and_presence(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 0, # important to not been obliged to wait
CONF_MOTION_PRESET: "boost",
@@ -282,8 +551,8 @@ async def test_movement_management_time_enough_and_presence(
assert entity.preset_mode is PRESET_ACTIVITY
# because no motion is detected yet
assert entity.target_temperature == 18
assert entity.motion_state is None
assert entity.presence_state is None
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state is STATE_UNKNOWN
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 18, event_timestamp)
@@ -312,9 +581,8 @@ async def test_movement_management_time_enough_and_presence(
assert entity.preset_mode is PRESET_ACTIVITY
# because motion is detected yet -> switch to Boost mode
assert entity.target_temperature == 19
assert entity.motion_state == "on"
assert entity.presence_state == "on"
assert entity.motion_state == STATE_ON
assert entity.presence_state == STATE_ON
assert mock_send_event.call_count == 0
# Change is confirmed. Heater should be started
assert mock_heater_on.call_count == 1
@@ -341,8 +609,8 @@ async def test_movement_management_time_enough_and_presence(
assert entity.preset_mode is PRESET_ACTIVITY
# because no motion is detected yet
assert entity.target_temperature == 18
assert entity.motion_state == "off"
assert entity.presence_state == "on"
assert entity.motion_state == STATE_OFF
assert entity.presence_state == STATE_ON
assert mock_send_event.call_count == 0
assert mock_heater_on.call_count == 0
@@ -353,7 +621,7 @@ async def test_movement_management_time_enough_and_presence(
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_movement_management_time_enoughand_not_presence(
async def test_motion_management_time_enough_and_not_presence(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test the Presence management when time is not enough"""
@@ -385,8 +653,8 @@ async def test_movement_management_time_enoughand_not_presence(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 0, # important to not been obliged to wait
CONF_MOTION_PRESET: "boost",
@@ -414,15 +682,15 @@ async def test_movement_management_time_enoughand_not_presence(
assert entity.preset_mode is PRESET_ACTIVITY
# because no motion is detected yet and presence is unknown
assert entity.target_temperature == 18
assert entity.motion_state is None
assert entity.presence_state is None
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state is STATE_UNKNOWN
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 18, event_timestamp)
await send_ext_temperature_change_event(entity, 10, event_timestamp)
await send_presence_change_event(entity, False, True, event_timestamp)
assert entity.presence_state == "off"
assert entity.presence_state == STATE_OFF
# starts detecting motion
with patch(
@@ -444,8 +712,8 @@ async def test_movement_management_time_enoughand_not_presence(
assert entity.preset_mode is PRESET_ACTIVITY
# because motion is detected yet -> switch to Boost away mode
assert entity.target_temperature == 19.1
assert entity.motion_state == "on"
assert entity.presence_state == "off"
assert entity.motion_state == STATE_ON
assert entity.presence_state == STATE_OFF
assert mock_send_event.call_count == 0
# Change is confirmed. Heater should be started
@@ -473,9 +741,8 @@ async def test_movement_management_time_enoughand_not_presence(
assert entity.preset_mode is PRESET_ACTIVITY
# because no motion is detected yet
assert entity.target_temperature == 18.1
assert entity.motion_state == "off"
assert entity.presence_state == "off"
assert entity.motion_state == STATE_OFF
assert entity.presence_state == STATE_OFF
assert mock_send_event.call_count == 0
# 18.1 starts heating with a low on_percent
assert mock_heater_on.call_count == 1
@@ -486,7 +753,7 @@ async def test_movement_management_time_enoughand_not_presence(
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_movement_management_with_stop_during_condition(
async def test_motion_management_with_stop_during_condition(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test the Motion management when the movement sensor switch to off and then to on during the test condition"""
@@ -518,8 +785,8 @@ async def test_movement_management_with_stop_during_condition(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 10,
CONF_MOTION_OFF_DELAY: 30,
@@ -548,15 +815,15 @@ async def test_movement_management_with_stop_during_condition(
assert entity.preset_mode is PRESET_ACTIVITY
# because no motion is detected yet
assert entity.target_temperature == 18
assert entity.motion_state is None
assert entity.presence_state is None
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state is STATE_UNKNOWN
event_timestamp = now - timedelta(minutes=6)
await send_temperature_change_event(entity, 18, event_timestamp)
await send_ext_temperature_change_event(entity, 10, event_timestamp)
await send_presence_change_event(entity, False, True, event_timestamp)
assert entity.presence_state == "off"
assert entity.presence_state == STATE_OFF
# starts detecting motion
with patch(
@@ -582,9 +849,8 @@ async def test_movement_management_with_stop_during_condition(
assert entity.preset_mode is PRESET_ACTIVITY
# because motion is detected yet -> switch to Boost mode
assert entity.target_temperature == 18
assert entity.motion_state is None
assert entity.presence_state == "off"
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state == STATE_OFF
# Send a stop detection
event_timestamp = now - timedelta(minutes=4)
try_condition = await send_motion_change_event(
@@ -595,8 +861,8 @@ async def test_movement_management_with_stop_during_condition(
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_ACTIVITY
assert entity.target_temperature == 18
assert entity.motion_state is None
assert entity.presence_state == "off"
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state == STATE_OFF
# Resend a start detection
event_timestamp = now - timedelta(minutes=3)
@@ -611,19 +877,19 @@ async def test_movement_management_with_stop_during_condition(
assert entity.preset_mode is PRESET_ACTIVITY
# still no motion detected
assert entity.target_temperature == 18
assert entity.motion_state is None
assert entity.presence_state == "off"
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state == STATE_OFF
await try_condition1(None)
# We should have switch this time
assert entity.target_temperature == 19 # Boost
assert entity.motion_state == "on" # switch to movement on
assert entity.presence_state == "off" # Non change
assert entity.motion_state == STATE_ON # switch to movement on
assert entity.presence_state == STATE_OFF # Non change
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_movement_management_with_stop_during_condition_last_state_on(
async def test_motion_management_with_stop_during_condition_last_state_on(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test the Motion management when the movement sensor switch to off and then to on during the test condition"""
@@ -655,8 +921,8 @@ async def test_movement_management_with_stop_during_condition_last_state_on(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 10,
CONF_MOTION_OFF_DELAY: 30,
@@ -684,7 +950,7 @@ async def test_movement_management_with_stop_during_condition_last_state_on(
assert entity.preset_mode is PRESET_ACTIVITY
# because no motion is detected yet
assert entity.target_temperature == 18
assert entity.motion_state is None
assert entity.motion_state is STATE_UNKNOWN
event_timestamp = now - timedelta(minutes=6)
await send_temperature_change_event(entity, 18, event_timestamp)
+27 -27
View File
@@ -45,8 +45,8 @@ async def test_one_switch_cycle(
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch1",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
@@ -69,7 +69,7 @@ async def test_one_switch_cycle(
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
assert entity.window_state is STATE_UNAVAILABLE
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 15, event_timestamp)
@@ -262,8 +262,8 @@ async def test_multiple_switchs(
CONF_HEATER_4: "switch.mock_switch4",
CONF_HEATER_KEEP_ALIVE: 0,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
@@ -289,7 +289,7 @@ async def test_multiple_switchs(
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
assert entity.window_state is STATE_UNAVAILABLE
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 15, event_timestamp)
@@ -402,8 +402,8 @@ async def test_multiple_climates(
CONF_CLIMATE_3: "switch.mock_climate3",
CONF_CLIMATE_4: "switch.mock_climate4",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
@@ -426,7 +426,7 @@ async def test_multiple_climates(
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
assert entity.window_state is STATE_UNAVAILABLE
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 15, event_timestamp)
@@ -451,7 +451,7 @@ async def test_multiple_climates(
assert entity.hvac_mode is HVACMode.OFF
assert entity.preset_mode is PRESET_BOOST
assert entity.target_temperature == 19
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNAVAILABLE
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 15, event_timestamp)
@@ -503,8 +503,8 @@ async def test_multiple_climates_underlying_changes(
CONF_CLIMATE_3: "switch.mock_climate3",
CONF_CLIMATE_4: "switch.mock_climate4",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
@@ -527,7 +527,7 @@ async def test_multiple_climates_underlying_changes(
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
assert entity.window_state is STATE_UNAVAILABLE
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 15, event_timestamp)
@@ -648,8 +648,8 @@ async def test_multiple_climates_underlying_changes_not_aligned(
CONF_CLIMATE_3: "switch.mock_climate3",
CONF_CLIMATE_4: "switch.mock_climate4",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
@@ -672,7 +672,7 @@ async def test_multiple_climates_underlying_changes_not_aligned(
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
assert entity.window_state is STATE_UNAVAILABLE
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 15, event_timestamp)
@@ -750,8 +750,8 @@ async def test_multiple_switch_power_management(
CONF_HEATER_4: "switch.mock_switch4",
CONF_HEATER_KEEP_ALIVE: 0,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
@@ -776,17 +776,17 @@ async def test_multiple_switch_power_management(
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
assert entity.target_temperature == 19
# 1. Send power mesurement
await send_power_change_event(entity, 50, datetime.now())
# Send power max mesurement
await send_max_power_change_event(entity, 300, datetime.now())
assert await entity.check_overpowering() is False
assert await entity.power_manager.check_overpowering() is False
# All configuration is complete and power is < power_max
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
# 2. Send power max mesurement too low and HVACMode is on
with patch(
@@ -798,10 +798,10 @@ async def test_multiple_switch_power_management(
) as mock_heater_off:
# 100 of the device / 4 -> 25, current power 50 so max is 75
await send_max_power_change_event(entity, 74, datetime.now())
assert await entity.check_overpowering() is True
assert await entity.power_manager.check_overpowering() is True
# All configuration is complete and power is > power_max we switch to POWER preset
assert entity.preset_mode is PRESET_POWER
assert entity.overpowering_state is True
assert entity.power_manager.overpowering_state is STATE_ON
assert entity.target_temperature == 12
assert mock_send_event.call_count == 2
@@ -814,7 +814,7 @@ async def test_multiple_switch_power_management(
"type": "start",
"current_power": 50,
"device_power": 100,
"current_power_max": 74,
"current_max_power": 74,
"current_power_consumption": 25.0,
},
),
@@ -831,7 +831,7 @@ async def test_multiple_switch_power_management(
await entity.async_set_preset_mode(PRESET_ECO)
assert entity.preset_mode is PRESET_ECO
# No change
assert entity.overpowering_state is True
assert entity.power_manager.overpowering_state is STATE_ON
# 4. Send hugh power max mesurement to release overpowering
with patch(
@@ -843,10 +843,10 @@ async def test_multiple_switch_power_management(
) as mock_heater_off:
# 100 of the device / 4 -> 25, current power 50 so max is 75. With 150 no overheating
await send_max_power_change_event(entity, 150, datetime.now())
assert await entity.check_overpowering() is False
assert await entity.power_manager.check_overpowering() is False
# All configuration is complete and power is > power_max we switch to POWER preset
assert entity.preset_mode is PRESET_ECO
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
assert entity.target_temperature == 17
assert (
+21 -15
View File
@@ -62,8 +62,8 @@ async def test_bug_56(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
@@ -151,7 +151,7 @@ async def test_bug_82(
PRESET_BOOST,
]
assert entity.preset_mode is PRESET_NONE
assert entity._security_state is False
assert entity.safety_manager.is_safety_detected is False
# should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT
assert mock_send_event.call_count == 2
@@ -191,10 +191,10 @@ async def test_bug_82(
):
event_timestamp = now - timedelta(minutes=6)
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
# set temperature to 15 so that on_percent will be > safety_min_on_percent (0.2)
await send_temperature_change_event(entity, 15, event_timestamp)
# Should stay False
assert entity.security_state is False
assert entity.safety_state is not STATE_ON
assert entity.preset_mode == "none"
assert entity._saved_preset_mode == "none"
@@ -641,8 +641,8 @@ async def test_bug_524(hass: HomeAssistant, skip_hass_states_is_state):
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
},
@@ -904,8 +904,8 @@ async def test_manual_hvac_off_should_take_the_lead_over_window(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -938,7 +938,10 @@ async def test_manual_hvac_off_should_take_the_lead_over_window(
== AUTO_START_STOP_LEVEL_FAST
)
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -960,7 +963,7 @@ async def test_manual_hvac_off_should_take_the_lead_over_window(
# VTherm should be heating
assert vtherm.hvac_mode == HVACMode.HEAT
# VTherm window_state should be off
assert vtherm.window_state == STATE_OFF
assert vtherm.window_state == STATE_UNKNOWN # Cause try_condition is not called
# 2. Open the window and wait for the delay
now = now + timedelta(minutes=2)
@@ -1078,8 +1081,8 @@ async def test_manual_hvac_off_should_take_the_lead_over_auto_start_stop(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1112,7 +1115,10 @@ async def test_manual_hvac_off_should_take_the_lead_over_auto_start_stop(
== AUTO_START_STOP_LEVEL_FAST
)
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1138,7 +1144,7 @@ async def test_manual_hvac_off_should_take_the_lead_over_auto_start_stop(
now = now + timedelta(minutes=5)
vtherm._set_now(now)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
+4 -4
View File
@@ -111,10 +111,10 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get
PRESET_BOOST,
]
assert vtherm.preset_mode is PRESET_NONE
assert vtherm._security_state is False
assert vtherm._window_state is None
assert vtherm._motion_state is None
assert vtherm._presence_state is None
assert vtherm.safety_manager.is_safety_detected is False
assert vtherm.window_state is STATE_UNAVAILABLE
assert vtherm.motion_state is STATE_UNAVAILABLE
assert vtherm.presence_state is STATE_UNAVAILABLE
assert vtherm.is_device_active is False
assert vtherm.valve_open_percent == 0
+255 -29
View File
@@ -1,17 +1,243 @@
# pylint: disable=protected-access, unused-argument, line-too-long
""" Test the Power management """
from unittest.mock import patch, call
from unittest.mock import patch, call, AsyncMock, MagicMock, PropertyMock
from datetime import datetime, timedelta
import logging
from custom_components.versatile_thermostat.thermostat_switch import (
ThermostatOverSwitch,
)
from custom_components.versatile_thermostat.feature_power_manager import (
FeaturePowerManager,
)
from custom_components.versatile_thermostat.prop_algorithm import PropAlgorithm
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
@pytest.mark.parametrize(
"is_over_climate, is_device_active, power, max_power, current_overpowering_state, overpowering_state, nb_call, changed, check_overpowering_ret",
[
# don't switch to overpower (power is enough)
(False, False, 1000, 3000, STATE_OFF, STATE_OFF, 0, True, False),
# switch to overpower (power is not enough)
(False, False, 2000, 3000, STATE_OFF, STATE_ON, 1, True, True),
# don't switch to overpower (power is not enough but device is already on)
(False, True, 2000, 3000, STATE_OFF, STATE_OFF, 0, True, False),
# Same with a over_climate
# don't switch to overpower (power is enough)
(True, False, 1000, 3000, STATE_OFF, STATE_OFF, 0, True, False),
# switch to overpower (power is not enough)
(True, False, 2000, 3000, STATE_OFF, STATE_ON, 1, True, True),
# don't switch to overpower (power is not enough but device is already on)
(True, True, 2000, 3000, STATE_OFF, STATE_OFF, 0, True, False),
# Leave overpowering state
# switch to not overpower (power is enough)
(False, False, 1000, 3000, STATE_ON, STATE_OFF, 1, True, False),
# don't switch to overpower (power is still not enough)
(False, False, 2000, 3000, STATE_ON, STATE_ON, 0, True, True),
# keep overpower (power is not enough but device is already on)
(False, True, 3000, 3000, STATE_ON, STATE_ON, 0, True, True),
],
)
async def test_power_feature_manager(
hass: HomeAssistant,
is_over_climate,
is_device_active,
power,
max_power,
current_overpowering_state,
overpowering_state,
nb_call,
changed,
check_overpowering_ret,
):
"""Test the FeaturePresenceManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
power_manager = FeaturePowerManager(fake_vtherm, hass)
assert power_manager is not None
assert power_manager.is_configured is False
assert power_manager.overpowering_state == STATE_UNAVAILABLE
assert power_manager.name == "the name"
assert len(power_manager._active_listener) == 0
custom_attributes = {}
power_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["power_sensor_entity_id"] is None
assert custom_attributes["max_power_sensor_entity_id"] is None
assert custom_attributes["overpowering_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_power_configured"] is False
assert custom_attributes["device_power"] is 0
assert custom_attributes["power_temp"] is None
assert custom_attributes["current_power"] is None
assert custom_attributes["current_max_power"] is None
# 2. post_init
power_manager.post_init(
{
CONF_POWER_SENSOR: "sensor.the_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.the_max_power_sensor",
CONF_USE_POWER_FEATURE: True,
CONF_PRESET_POWER: 10,
CONF_DEVICE_POWER: 1234,
}
)
assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN
custom_attributes = {}
power_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["power_sensor_entity_id"] == "sensor.the_power_sensor"
assert (
custom_attributes["max_power_sensor_entity_id"] == "sensor.the_max_power_sensor"
)
assert custom_attributes["overpowering_state"] == STATE_UNKNOWN
assert custom_attributes["is_power_configured"] is True
assert custom_attributes["device_power"] == 1234
assert custom_attributes["power_temp"] == 10
assert custom_attributes["current_power"] is None
assert custom_attributes["current_max_power"] is None
# 3. start listening
power_manager.start_listening()
assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN
assert len(power_manager._active_listener) == 2
# 4. test refresh and check_overpowering with the parametrized
side_effects = SideEffects(
{
"sensor.the_power_sensor": State("sensor.the_power_sensor", power),
"sensor.the_max_power_sensor": State(
"sensor.the_max_power_sensor", max_power
),
},
State("unknown.entity_id", "unknown"),
)
# fmt:off
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()) as mock_get_state:
# fmt:on
# Finish the mock configuration
tpi_algo = PropAlgorithm(PROPORTIONAL_FUNCTION_TPI, 0.6, 0.01, 5, 0, "climate.vtherm")
tpi_algo._on_percent = 1 # pylint: disable="protected-access"
type(fake_vtherm).hvac_mode = PropertyMock(return_value=HVACMode.HEAT)
type(fake_vtherm).is_device_active = PropertyMock(return_value=is_device_active)
type(fake_vtherm).is_over_climate = PropertyMock(return_value=is_over_climate)
type(fake_vtherm).proportional_algorithm = PropertyMock(return_value=tpi_algo)
type(fake_vtherm).nb_underlying_entities = PropertyMock(return_value=1)
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT if current_overpowering_state == STATE_OFF else PRESET_POWER)
type(fake_vtherm)._saved_preset_mode = PropertyMock(return_value=PRESET_ECO)
fake_vtherm.save_hvac_mode = MagicMock()
fake_vtherm.restore_hvac_mode = AsyncMock()
fake_vtherm.save_preset_mode = MagicMock()
fake_vtherm.restore_preset_mode = AsyncMock()
fake_vtherm.async_underlying_entity_turn_off = AsyncMock()
fake_vtherm.async_set_preset_mode_internal = AsyncMock()
fake_vtherm.send_event = MagicMock()
fake_vtherm.update_custom_attributes = MagicMock()
ret = await power_manager.refresh_state()
assert ret == changed
assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN
assert power_manager.current_power == power
assert power_manager.current_max_power == max_power
# check overpowering
power_manager._overpowering_state = current_overpowering_state
ret2 = await power_manager.check_overpowering()
assert ret2 == check_overpowering_ret
assert power_manager.overpowering_state == overpowering_state
assert mock_get_state.call_count == 2
if power_manager.overpowering_state == STATE_OFF:
assert fake_vtherm.save_hvac_mode.call_count == 0
assert fake_vtherm.save_preset_mode.call_count == 0
assert fake_vtherm.async_underlying_entity_turn_off.call_count == 0
assert fake_vtherm.async_set_preset_mode_internal.call_count == 0
assert fake_vtherm.send_event.call_count == nb_call
if current_overpowering_state == STATE_ON:
assert fake_vtherm.update_custom_attributes.call_count == 1
assert fake_vtherm.restore_preset_mode.call_count == 1
if is_over_climate:
assert fake_vtherm.restore_hvac_mode.call_count == 1
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
else:
assert fake_vtherm.update_custom_attributes.call_count == 0
if nb_call == 1:
fake_vtherm.send_event.assert_has_calls(
[
call.fake_vtherm.send_event(
EventType.POWER_EVENT,
{'type': 'end', 'current_power': power, 'device_power': 1234, 'current_max_power': max_power}),
]
)
elif power_manager.overpowering_state == STATE_ON:
if is_over_climate:
assert fake_vtherm.save_hvac_mode.call_count == 1
else:
assert fake_vtherm.save_hvac_mode.call_count == 0
if current_overpowering_state == STATE_OFF:
assert fake_vtherm.save_preset_mode.call_count == 1
assert fake_vtherm.async_underlying_entity_turn_off.call_count == 1
assert fake_vtherm.async_set_preset_mode_internal.call_count == 1
assert fake_vtherm.send_event.call_count == 1
assert fake_vtherm.update_custom_attributes.call_count == 1
else:
assert fake_vtherm.save_preset_mode.call_count == 0
assert fake_vtherm.async_underlying_entity_turn_off.call_count == 0
assert fake_vtherm.async_set_preset_mode_internal.call_count == 0
assert fake_vtherm.send_event.call_count == 0
assert fake_vtherm.update_custom_attributes.call_count == 0
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.restore_preset_mode.call_count == 0
if nb_call == 1:
fake_vtherm.send_event.assert_has_calls(
[
call.fake_vtherm.send_event(
EventType.POWER_EVENT,
{'type': 'start', 'current_power': power, 'device_power': 1234, 'current_max_power': max_power, 'current_power_consumption': 1234.0}),
]
)
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
power_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["power_sensor_entity_id"] == "sensor.the_power_sensor"
assert (
custom_attributes["max_power_sensor_entity_id"] == "sensor.the_max_power_sensor"
)
assert custom_attributes["overpowering_state"] == overpowering_state
assert custom_attributes["is_power_configured"] is True
assert custom_attributes["device_power"] == 1234
assert custom_attributes["power_temp"] == 10
assert custom_attributes["current_power"] == power
assert custom_attributes["current_max_power"] == max_power
power_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_power_management_hvac_off(
@@ -43,8 +269,8 @@ async def test_power_management_hvac_off(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
@@ -63,23 +289,23 @@ async def test_power_management_hvac_off(
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.preset_mode is PRESET_BOOST
assert entity.target_temperature == 19
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
assert entity.hvac_mode == HVACMode.OFF
# Send power mesurement
await send_power_change_event(entity, 50, datetime.now())
assert await entity.check_overpowering() is False
assert await entity.power_manager.check_overpowering() is False
# All configuration is not complete
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
# Send power max mesurement
await send_max_power_change_event(entity, 300, datetime.now())
assert await entity.check_overpowering() is False
assert await entity.power_manager.check_overpowering() is False
# All configuration is complete and power is < power_max
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
# Send power max mesurement too low but HVACMode is off
with patch(
@@ -90,10 +316,10 @@ async def test_power_management_hvac_off(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off:
await send_max_power_change_event(entity, 149, datetime.now())
assert await entity.check_overpowering() is True
assert await entity.power_manager.check_overpowering() is True
# All configuration is complete and power is > power_max but we stay in Boost cause thermostat if Off
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is True
assert entity.power_manager.overpowering_state is STATE_ON
assert mock_send_event.call_count == 0
assert mock_heater_on.call_count == 0
@@ -129,8 +355,8 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
@@ -150,17 +376,17 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
assert entity.target_temperature == 19
# Send power mesurement
await send_power_change_event(entity, 50, datetime.now())
# Send power max mesurement
await send_max_power_change_event(entity, 300, datetime.now())
assert await entity.check_overpowering() is False
assert await entity.power_manager.check_overpowering() is False
# All configuration is complete and power is < power_max
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
# Send power max mesurement too low and HVACMode is on
with patch(
@@ -171,10 +397,10 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off:
await send_max_power_change_event(entity, 149, datetime.now())
assert await entity.check_overpowering() is True
assert await entity.power_manager.check_overpowering() is True
# All configuration is complete and power is > power_max we switch to POWER preset
assert entity.preset_mode is PRESET_POWER
assert entity.overpowering_state is True
assert entity.power_manager.overpowering_state is STATE_ON
assert entity.target_temperature == 12
assert mock_send_event.call_count == 2
@@ -187,7 +413,7 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
"type": "start",
"current_power": 50,
"device_power": 100,
"current_power_max": 149,
"current_max_power": 149,
"current_power_consumption": 100.0,
},
),
@@ -206,10 +432,10 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off:
await send_power_change_event(entity, 48, datetime.now())
assert await entity.check_overpowering() is False
assert await entity.power_manager.check_overpowering() is False
# All configuration is complete and power is < power_max, we restore previous preset
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
assert entity.target_temperature == 19
assert mock_send_event.call_count == 2
@@ -222,7 +448,7 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
"type": "end",
"current_power": 48,
"device_power": 100,
"current_power_max": 149,
"current_max_power": 149,
},
),
],
@@ -265,8 +491,8 @@ async def test_power_management_energy_over_switch(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
@@ -303,7 +529,7 @@ async def test_power_management_energy_over_switch(
assert entity.current_temperature == 15
assert tpi_algo.on_percent == 1
assert entity.device_power == 100.0
assert entity.power_manager.device_power == 100.0
assert mock_send_event.call_count == 2
assert mock_heater_on.call_count == 1
@@ -324,7 +550,7 @@ async def test_power_management_energy_over_switch(
) as mock_heater_off:
await send_temperature_change_event(entity, 18, datetime.now())
assert tpi_algo.on_percent == 0.3
assert entity.mean_cycle_power == 30.0
assert entity.power_manager.mean_cycle_power == 30.0
assert mock_send_event.call_count == 0
assert mock_heater_on.call_count == 0
@@ -346,7 +572,7 @@ async def test_power_management_energy_over_switch(
) as mock_heater_off:
await send_temperature_change_event(entity, 20, datetime.now())
assert tpi_algo.on_percent == 0.0
assert entity.mean_cycle_power == 0.0
assert entity.power_manager.mean_cycle_power == 0.0
assert mock_send_event.call_count == 0
assert mock_heater_on.call_count == 0
@@ -394,8 +620,8 @@ async def test_power_management_energy_over_climate(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
@@ -421,7 +647,7 @@ async def test_power_management_energy_over_climate(
assert entity.current_temperature == 15
# Not initialised yet
assert entity.mean_cycle_power is None
assert entity.power_manager.mean_cycle_power is None
assert entity._underlying_climate_start_hvac_action_date is None
# Send a climate_change event with HVACAction=HEATING
+178
View File
@@ -0,0 +1,178 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
""" Test the Security featrure """
import logging
from unittest.mock import patch, call, AsyncMock, MagicMock, PropertyMock
# from datetime import timedelta, datetime
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.feature_presence_manager import (
FeaturePresenceManager,
)
from .commons import *
logging.getLogger().setLevel(logging.DEBUG)
@pytest.mark.parametrize(
"temp, absence, state, nb_call, presence_state, changed",
[
(19, False, STATE_ON, 1, STATE_ON, True),
(17, True, STATE_OFF, 1, STATE_OFF, True),
(19, False, STATE_HOME, 1, STATE_ON, True),
(17, True, STATE_NOT_HOME, 1, STATE_OFF, True),
(17, False, STATE_UNAVAILABLE, 0, STATE_UNKNOWN, False),
(17, False, STATE_UNKNOWN, 0, STATE_UNKNOWN, False),
(17, False, "wrong state", 0, STATE_UNKNOWN, False),
],
)
async def test_presence_feature_manager(
hass: HomeAssistant, temp, absence, state, nb_call, presence_state, changed
):
"""Test the FeaturePresenceManager 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
presence_manager = FeaturePresenceManager(fake_vtherm, hass)
assert presence_manager is not None
assert presence_manager.is_configured is False
assert presence_manager.is_absence_detected is False
assert presence_manager.presence_state == STATE_UNAVAILABLE
assert presence_manager.name == "the name"
assert len(presence_manager._active_listener) == 0
custom_attributes = {}
presence_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["presence_sensor_entity_id"] is None
assert custom_attributes["presence_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_presence_configured"] is False
# 2. post_init
presence_manager.post_init(
{
CONF_PRESENCE_SENSOR: "sensor.the_presence_sensor",
CONF_USE_PRESENCE_FEATURE: True,
}
)
assert presence_manager.is_configured is True
assert presence_manager.presence_state == STATE_UNKNOWN
assert presence_manager.is_absence_detected is False
custom_attributes = {}
presence_manager.add_custom_attributes(custom_attributes)
assert (
custom_attributes["presence_sensor_entity_id"] == "sensor.the_presence_sensor"
)
assert custom_attributes["presence_state"] == STATE_UNKNOWN
assert custom_attributes["is_presence_configured"] is True
# 3. start listening
presence_manager.start_listening()
assert presence_manager.is_configured is True
assert presence_manager.presence_state == STATE_UNKNOWN
assert presence_manager.is_absence_detected is False
assert len(presence_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_presence_sensor", state)) as mock_get_state:
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.find_preset_temp.return_value = temp
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.async_control_heating = AsyncMock()
ret = await presence_manager.refresh_state()
assert ret == changed
assert presence_manager.is_configured is True
assert presence_manager.presence_state == presence_state
assert presence_manager.is_absence_detected is absence
assert mock_get_state.call_count == 1
assert fake_vtherm.find_preset_temp.call_count == nb_call
if nb_call == 1:
fake_vtherm.find_preset_temp.assert_has_calls(
[
call.find_preset_temp(PRESET_COMFORT),
]
)
assert fake_vtherm.change_target_temperature.call_count == nb_call
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.find_preset_temp(temp),
]
)
assert fake_vtherm.async_control_heating.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
presence_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["presence_sensor_entity_id"] == "sensor.the_presence_sensor"
assert custom_attributes["presence_state"] == presence_state
assert custom_attributes["is_presence_configured"] is True
# 6. test _presence_sensor_changed with the parametrized
fake_vtherm.find_preset_temp.return_value = temp
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.async_control_heating = AsyncMock()
await presence_manager._presence_sensor_changed(
event=Event(
event_type=EVENT_STATE_CHANGED,
data={
"entity_id": "sensor.the_presence_sensor",
"new_state": State("sensor.the_presence_sensor", state),
"old_state": State("sensor.the_presence_sensor", STATE_UNAVAILABLE),
}))
assert ret == changed
assert presence_manager.is_configured is True
assert presence_manager.presence_state == presence_state
assert presence_manager.is_absence_detected is absence
assert fake_vtherm.find_preset_temp.call_count == nb_call
if nb_call == 1:
fake_vtherm.find_preset_temp.assert_has_calls(
[
call.find_preset_temp(PRESET_COMFORT),
]
)
assert fake_vtherm.change_target_temperature.call_count == nb_call
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.find_preset_temp(temp),
]
)
assert fake_vtherm.async_control_heating.call_count == 1
fake_vtherm.async_control_heating.assert_has_calls([
call.async_control_heating(force=True)
])
fake_vtherm.reset_mock()
# 7. Check custom_attributes
custom_attributes = {}
presence_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["presence_sensor_entity_id"] == "sensor.the_presence_sensor"
assert custom_attributes["presence_state"] == presence_state
assert custom_attributes["is_presence_configured"] is True
presence_manager.stop_listening()
await hass.async_block_till_done()
+197 -34
View File
@@ -1,7 +1,7 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
""" Test the Security featrure """
from unittest.mock import patch, call
from unittest.mock import patch, call, PropertyMock, MagicMock
from datetime import timedelta, datetime
import logging
@@ -11,12 +11,103 @@ from custom_components.versatile_thermostat.thermostat_climate import (
from custom_components.versatile_thermostat.thermostat_switch import (
ThermostatOverSwitch,
)
from custom_components.versatile_thermostat.feature_safety_manager import (
FeatureSafetyManager,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
async def test_safety_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
safety_manager = FeatureSafetyManager(fake_vtherm, hass)
assert safety_manager is not None
assert safety_manager.is_configured is False
assert safety_manager.is_safety_detected is False
assert safety_manager.safety_state is STATE_UNAVAILABLE
assert safety_manager.name == "the name"
assert len(safety_manager._active_listener) == 0
custom_attributes = {}
safety_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["is_safety_configured"] is False
assert custom_attributes["safety_state"] is STATE_UNAVAILABLE
assert custom_attributes.get("safety_delay_min", None) is None
assert custom_attributes.get("safety_min_on_percent", None) is None
assert custom_attributes.get("safety_default_on_percent", None) is None
@pytest.mark.parametrize(
"safety_delay_min, safety_min_on_percent, safety_default_on_percent, is_configured, state",
[
# fmt: off
( 10, 11, 12, True, STATE_UNKNOWN),
( None, 11, 12, False, STATE_UNAVAILABLE),
( 10, None, 12, True, STATE_UNKNOWN),
( 10, 11, None, True, STATE_UNKNOWN),
( 10, None, None, True, STATE_UNKNOWN),
( None, None, None, False, STATE_UNAVAILABLE),
# fmt: on
],
)
async def test_safety_feature_manager_post_init(
hass: HomeAssistant,
safety_delay_min,
safety_min_on_percent,
safety_default_on_percent,
is_configured,
state,
):
"""Test the FeatureSafetyManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
safety_manager = FeatureSafetyManager(fake_vtherm, hass)
assert safety_manager is not None
# 2. post_init
safety_manager.post_init(
{
CONF_SAFETY_DELAY_MIN: safety_delay_min,
CONF_SAFETY_MIN_ON_PERCENT: safety_min_on_percent,
CONF_SAFETY_DEFAULT_ON_PERCENT: safety_default_on_percent,
}
)
assert safety_manager.is_configured is is_configured
assert safety_manager.safety_state is state
custom_attributes = {}
safety_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["is_safety_configured"] is is_configured
assert custom_attributes["safety_state"] is state
if safety_manager.is_configured:
assert custom_attributes.get("safety_delay_min", None) == safety_delay_min
assert (
custom_attributes.get("safety_min_on_percent", None)
== safety_min_on_percent
or DEFAULT_SAFETY_MIN_ON_PERCENT
)
assert (
custom_attributes.get("safety_default_on_percent", None)
== safety_default_on_percent
or DEFAULT_SAFETY_DEFAULT_ON_PERCENT
)
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
@@ -24,17 +115,26 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
1. creates a thermostat and check that security is off
2. activate security feature when date is expired
3. change the preset to boost
4. check that security is still on
5. resolve the date issue
6. check that security is off and preset is changed to boost
"""
tz = get_tz(hass) # pylint: disable=invalid-name
temps = {
"frost": 7,
"eco": 17,
"comfort": 18,
"boost": 19,
}
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
# With migration
version=CONFIG_VERSION,
minor_version=0,
data={
"name": "TheOverSwitchMockName",
"thermostat_type": "thermostat_over_switch",
@@ -43,15 +143,11 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
"cycle_min": 5,
"temp_min": 15,
"temp_max": 30,
"frost_temp": 7,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
"use_window_feature": False,
"use_motion_feature": False,
"use_power_feature": False,
"use_presence_feature": False,
"heater_entity_id": "switch.mock_switch",
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
"proportional_function": "tpi",
"tpi_coef_int": 0.3,
"tpi_coef_ext": 0.01,
@@ -69,8 +165,11 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
)
assert entity
assert entity._security_state is False
assert entity.preset_mode is not PRESET_SECURITY
await set_all_climate_preset_temp(hass, entity, temps, "theoverswitchmockname")
assert entity.safety_manager.safety_state is not STATE_ON
assert entity.safety_manager.is_safety_detected is False
assert entity.preset_mode is not PRESET_SAFETY
assert entity.preset_modes == [
PRESET_NONE,
PRESET_FROST_PROTECTION,
@@ -105,8 +204,8 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
await send_temperature_change_event(entity, 15, event_timestamp)
assert entity.security_state is True
assert entity.preset_mode == PRESET_SECURITY
assert entity.safety_state is STATE_ON
assert entity.preset_mode == PRESET_SAFETY
assert entity._saved_preset_mode == PRESET_COMFORT
assert entity._prop_algorithm.on_percent == 0.1
assert entity._prop_algorithm.calculated_on_percent == 0.9
@@ -114,7 +213,7 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
assert mock_send_event.call_count == 3
mock_send_event.assert_has_calls(
[
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_SECURITY}),
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_SAFETY}),
call.send_event(
EventType.TEMPERATURE_EVENT,
{
@@ -151,11 +250,13 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
await entity.async_set_preset_mode(PRESET_BOOST)
# 4. check that security is still on
assert entity._security_state is True
assert entity.safety_manager.safety_state is STATE_ON
assert entity.safety_manager.is_safety_detected is True
assert entity._prop_algorithm.on_percent == 0.1
assert entity._prop_algorithm.calculated_on_percent == 0.9
assert entity._saved_preset_mode == PRESET_BOOST
assert entity.preset_mode is PRESET_SECURITY
assert entity.preset_mode is PRESET_SAFETY
# 5. resolve the datetime issue
with patch(
@@ -168,7 +269,9 @@ async def test_security_feature(hass: HomeAssistant, skip_hass_states_is_state):
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
await send_temperature_change_event(entity, 15.2, event_timestamp)
assert entity._security_state is False
assert entity.safety_manager.safety_state is not STATE_ON
assert entity.safety_manager.is_safety_detected is False
assert entity.preset_mode == PRESET_BOOST
assert entity._saved_preset_mode == PRESET_BOOST
assert entity._prop_algorithm.on_percent == 1.0
@@ -215,6 +318,12 @@ async def test_security_feature_back_on_percent(
"""
tz = get_tz(hass) # pylint: disable=invalid-name
temps = {
"eco": 17,
"comfort": 18,
"boost": 19,
"frost": 10,
}
entry = MockConfigEntry(
domain=DOMAIN,
@@ -228,21 +337,18 @@ async def test_security_feature_back_on_percent(
"cycle_min": 5,
"temp_min": 15,
"temp_max": 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
"use_window_feature": False,
"use_motion_feature": False,
"use_power_feature": False,
"use_presence_feature": False,
"heater_entity_id": "switch.mock_switch",
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
"proportional_function": "tpi",
"tpi_coef_int": 0.3,
"tpi_coef_ext": 0.01,
"minimal_activation_delay": 30,
"security_delay_min": 5, # 5 minutes
"security_min_on_percent": 0.2,
"security_default_on_percent": 0.1,
"safety_delay_min": 5, # 5 minutes
"safety_min_on_percent": 0.2,
"safety_default_on_percent": 0.1,
},
)
@@ -253,8 +359,12 @@ async def test_security_feature_back_on_percent(
)
assert entity
assert entity._security_state is False
assert entity.preset_mode is not PRESET_SECURITY
await set_all_climate_preset_temp(hass, entity, temps, "theoverswitchmockname")
assert entity.safety_manager.safety_state is not STATE_ON
assert entity.safety_manager.is_safety_detected is False
assert entity.preset_mode is not PRESET_SAFETY
assert entity._last_ext_temperature_measure is not None
assert entity._last_temperature_measure is not None
assert (entity._last_temperature_measure.astimezone(tz) - now).total_seconds() < 1
@@ -285,7 +395,7 @@ async def test_security_feature_back_on_percent(
await send_temperature_change_event(entity, 17, event_timestamp)
assert entity._prop_algorithm.calculated_on_percent == 0.6
assert entity.preset_mode == PRESET_BOOST
assert entity.security_state is False
assert entity.safety_state is not STATE_ON
assert mock_send_event.call_count == 0
# 3. Set safety mode with a preset change
@@ -302,14 +412,14 @@ async def test_security_feature_back_on_percent(
assert entity._prop_algorithm.calculated_on_percent == 0.6
assert entity.security_state is True
assert entity.preset_mode == PRESET_SECURITY
assert entity.safety_state is STATE_ON
assert entity.preset_mode == PRESET_SAFETY
assert entity._saved_preset_mode == PRESET_BOOST
assert mock_send_event.call_count == 3
mock_send_event.assert_has_calls(
[
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_SECURITY}),
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_SAFETY}),
call.send_event(
EventType.TEMPERATURE_EVENT,
{
@@ -343,8 +453,8 @@ async def test_security_feature_back_on_percent(
entity._set_now(event_timestamp) # pylint: disable=protected-access
await entity.async_set_preset_mode(PRESET_ECO)
assert entity.security_state is True
assert entity.preset_mode == PRESET_SECURITY
assert entity.safety_state is STATE_ON
assert entity.preset_mode == PRESET_SAFETY
# 5. resolve the datetime issue
with patch(
@@ -359,7 +469,9 @@ async def test_security_feature_back_on_percent(
# set temperature to 18.9 so that on_percent will be > security_min_on_percent (0.2)
await send_temperature_change_event(entity, 18.92, event_timestamp)
assert entity._security_state is False
assert entity.safety_manager.safety_state is not STATE_ON
assert entity.safety_manager.is_safety_detected is False
assert entity.preset_mode == PRESET_ECO
assert entity._saved_preset_mode == PRESET_ECO
assert entity._prop_algorithm.on_percent == 0.0
@@ -452,7 +564,8 @@ async def test_security_over_climate(
PRESET_BOOST,
]
assert entity.preset_mode is PRESET_NONE
assert entity._security_state is False
assert entity.safety_manager.safety_state is not STATE_ON
assert entity.safety_manager.is_safety_detected is False
# should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT
assert mock_send_event.call_count == 2
@@ -506,6 +619,56 @@ async def test_security_over_climate(
await send_temperature_change_event(entity, 15, event_timestamp)
# Should stay False because a climate is never in safety mode
assert entity.security_state is False
assert entity.safety_state is not STATE_ON
assert entity.preset_mode == "none"
assert entity._saved_preset_mode == "none"
async def test_migration_security_safety(
hass: HomeAssistant,
skip_hass_states_is_state,
):
"""Tests the migration of security parameters to safety in English"""
central_config_entry = MockConfigEntry(
# Current is 2.1
version=CONFIG_VERSION,
# An old minor version
minor_version=0,
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
data={
CONF_NAME: "migrationName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_UNDERLYING_LIST: ["switch.under1"],
"security_delay_min": 61,
"security_min_on_percent": 0.5,
"security_default_on_percent": 0.2,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_DEVICE_POWER: 1,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_MINIMAL_ACTIVATION_DELAY: 10,
},
)
central_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(central_config_entry.entry_id)
assert central_config_entry.state is ConfigEntryState.LOADED
entity: ThermostatOverSwitch = search_entity(
hass, "climate.migrationname", "climate"
)
assert entity is not None
assert entity.safety_manager.safety_min_on_percent == 0.5
assert entity.safety_manager.safety_default_on_percent == 0.2
assert entity.safety_manager.safety_delay_min == 61
+6 -6
View File
@@ -62,8 +62,8 @@ async def test_sensors_over_switch(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_DEVICE_POWER: 200,
},
)
@@ -222,8 +222,8 @@ async def test_sensors_over_climate(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 1.5,
@@ -360,8 +360,8 @@ async def test_sensors_over_climate_minimal(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
+13 -25
View File
@@ -53,10 +53,10 @@ async def test_over_switch_full_start(hass: HomeAssistant, skip_hass_states_is_s
PRESET_ACTIVITY,
]
assert entity.preset_mode is PRESET_NONE
assert entity._security_state is False
assert entity._window_state is None
assert entity._motion_state is None
assert entity._presence_state is None
assert entity.safety_manager.is_safety_detected is False
assert entity.window_state is STATE_UNKNOWN
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state is STATE_UNKNOWN
assert entity._prop_algorithm is not None
assert entity.have_valve_regulation is False
@@ -112,10 +112,10 @@ async def test_over_climate_full_start(hass: HomeAssistant, skip_hass_states_is_
PRESET_BOOST,
]
assert entity.preset_mode is PRESET_NONE
assert entity._security_state is False
assert entity._window_state is None
assert entity._motion_state is None
assert entity._presence_state is None
assert entity.safety_manager.is_safety_detected is False
assert entity.window_state is STATE_UNAVAILABLE
assert entity.motion_state is STATE_UNAVAILABLE
assert entity.presence_state is STATE_UNAVAILABLE
assert entity.have_valve_regulation is False
# should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT
@@ -151,18 +151,6 @@ async def test_over_4switch_full_start(hass: HomeAssistant, skip_hass_states_is_
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
entity = await create_thermostat(hass, entry, "climate.theover4switchmockname")
# entry.add_to_hass(hass)
# await hass.config_entries.async_setup(entry.entry_id)
# assert entry.state is ConfigEntryState.LOADED
#
# def find_my_entity(entity_id) -> ClimateEntity:
# """Find my new entity"""
# component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
# for entity in component.entities:
# if entity.entity_id == entity_id:
# return entity
#
# entity: BaseThermostat = find_my_entity("climate.theover4switchmockname")
assert entity
@@ -180,10 +168,10 @@ async def test_over_4switch_full_start(hass: HomeAssistant, skip_hass_states_is_
PRESET_ACTIVITY,
]
assert entity.preset_mode is PRESET_NONE
assert entity._security_state is False
assert entity._window_state is None
assert entity._motion_state is None
assert entity._presence_state is None
assert entity.safety_manager.is_safety_detected is False
assert entity.window_state is STATE_UNKNOWN
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state is STATE_UNKNOWN
assert entity._prop_algorithm is not None
assert entity.nb_underlying_entities == 4
@@ -242,7 +230,7 @@ async def test_over_switch_deactivate_preset(
CONF_HEATER_3: None,
CONF_HEATER_4: None,
CONF_HEATER_KEEP_ALIVE: 0,
CONF_SECURITY_DELAY_MIN: 10,
CONF_SAFETY_DELAY_MIN: 10,
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.6,
+6 -6
View File
@@ -89,10 +89,10 @@ async def test_over_switch_ac_full_start(
PRESET_ACTIVITY,
]
assert entity.preset_mode is PRESET_NONE
assert entity._security_state is False # pylint: disable=protected-access
assert entity._window_state is None # pylint: disable=protected-access
assert entity._motion_state is None # pylint: disable=protected-access
assert entity._presence_state is None # pylint: disable=protected-access
assert entity.safety_manager.is_safety_detected is False
assert entity.window_state is STATE_UNKNOWN
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state is STATE_UNKNOWN
assert entity._prop_algorithm is not None # pylint: disable=protected-access
# should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT
@@ -114,7 +114,7 @@ async def test_over_switch_ac_full_start(
event_timestamp = now - timedelta(minutes=4)
await send_presence_change_event(entity, True, False, event_timestamp)
assert entity._presence_state == STATE_ON # pylint: disable=protected-access
assert entity.presence_state == STATE_ON # pylint: disable=protected-access
await entity.async_set_hvac_mode(HVACMode.COOL)
assert entity.hvac_mode is HVACMode.COOL
@@ -131,7 +131,7 @@ async def test_over_switch_ac_full_start(
# Unset the presence
event_timestamp = now - timedelta(minutes=3)
await send_presence_change_event(entity, False, True, event_timestamp)
assert entity._presence_state == STATE_OFF # pylint: disable=protected-access
assert entity.presence_state == STATE_OFF # pylint: disable=protected-access
assert entity.target_temperature == 27 # eco_ac_away
# Open a window
+2 -2
View File
@@ -39,8 +39,8 @@ def config_entry() -> MockConfigEntry:
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.1,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.1,
},
)
+12 -12
View File
@@ -75,9 +75,9 @@ async def test_add_number_for_central_config(
CONF_MAX_POWER_SENSOR: "sensor.mock_central_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: False,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
}
@@ -170,9 +170,9 @@ async def test_add_number_for_central_config_without_temp(
CONF_MAX_POWER_SENSOR: "sensor.mock_central_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: False,
},
# | temps,
@@ -265,9 +265,9 @@ async def test_add_number_for_central_config_without_temp_ac_mode(
CONF_MAX_POWER_SENSOR: "sensor.mock_central_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: False,
},
# | temps,
@@ -359,9 +359,9 @@ async def test_add_number_for_central_config_without_temp_restore(
CONF_MAX_POWER_SENSOR: "sensor.mock_central_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: False,
},
# | temps,
+11 -11
View File
@@ -38,8 +38,8 @@ async def test_tpi_calculation(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
# CONF_DEVICE_POWER: 100,
},
)
@@ -58,7 +58,7 @@ async def test_tpi_calculation(
assert tpi_algo.calculated_on_percent == 1
assert tpi_algo.on_time_sec == 300
assert tpi_algo.off_time_sec == 0
assert entity.mean_cycle_power is None # no device power configured
assert entity.power_manager.mean_cycle_power is None # no device power configured
tpi_algo.calculate(15, 14, 5, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.4
@@ -66,14 +66,14 @@ async def test_tpi_calculation(
assert tpi_algo.on_time_sec == 120
assert tpi_algo.off_time_sec == 180
tpi_algo.set_security(0.1)
tpi_algo.set_safety(0.1)
tpi_algo.calculate(15, 14, 5, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.1
assert tpi_algo.calculated_on_percent == 0.4
assert tpi_algo.on_time_sec == 30 # >= minimal_activation_delay (=30)
assert tpi_algo.off_time_sec == 270
tpi_algo.unset_security()
tpi_algo.unset_safety()
tpi_algo.calculate(15, 14, 5, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.4
assert tpi_algo.calculated_on_percent == 0.4
@@ -87,30 +87,30 @@ async def test_tpi_calculation(
assert tpi_algo.on_time_sec == 0
assert tpi_algo.off_time_sec == 300
tpi_algo.set_security(0.09)
tpi_algo.set_safety(0.09)
tpi_algo.calculate(15, 14.7, 15, HVACMode.HEAT)
assert tpi_algo.on_percent == 0.09
assert tpi_algo.calculated_on_percent == 0.09
assert tpi_algo.on_time_sec == 0
assert tpi_algo.off_time_sec == 300
tpi_algo.unset_security()
tpi_algo.unset_safety()
tpi_algo.calculate(25, 30, 35, HVACMode.COOL)
assert tpi_algo.on_percent == 1
assert tpi_algo.calculated_on_percent == 1
assert tpi_algo.on_time_sec == 300
assert tpi_algo.off_time_sec == 0
assert entity.mean_cycle_power is None # no device power configured
assert entity.power_manager.mean_cycle_power is None # no device power configured
tpi_algo.set_security(0.09)
tpi_algo.set_safety(0.09)
tpi_algo.calculate(25, 30, 35, HVACMode.COOL)
assert tpi_algo.on_percent == 0.09
assert tpi_algo.calculated_on_percent == 1
assert tpi_algo.on_time_sec == 0
assert tpi_algo.off_time_sec == 300
assert entity.mean_cycle_power is None # no device power configured
assert entity.power_manager.mean_cycle_power is None # no device power configured
tpi_algo.unset_security()
tpi_algo.unset_safety()
# The calculated values for HVACMode.OFF are the same as for HVACMode.HEAT.
tpi_algo.calculate(15, 10, 7, HVACMode.OFF)
assert tpi_algo.on_percent == 1
+11 -9
View File
@@ -60,8 +60,8 @@ async def test_over_valve_full_start(
PRESET_BOOST + PRESET_AWAY_SUFFIX + PRESET_TEMP_SUFFIX: 17.3,
CONF_PRESET_POWER: 10,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_DEVICE_POWER: 100,
CONF_AC_MODE: False,
},
@@ -98,10 +98,12 @@ async def test_over_valve_full_start(
PRESET_ACTIVITY,
]
assert entity.preset_mode is PRESET_NONE
assert entity._security_state is False # pylint: disable=protected-access
assert entity._window_state is None # pylint: disable=protected-access
assert entity._motion_state is None # pylint: disable=protected-access
assert entity._presence_state is None # pylint: disable=protected-access
assert (
entity.safety_manager.is_safety_detected is False
) # pylint: disable=protected-access
assert entity.window_state is STATE_UNKNOWN
assert entity.motion_state is STATE_UNKNOWN
assert entity.presence_state is STATE_UNKNOWN
assert entity._prop_algorithm is not None # pylint: disable=protected-access
assert entity.have_valve_regulation is False
@@ -350,8 +352,8 @@ async def test_over_valve_regulation(
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 60,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DELAY_MIN: 60,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
# only send new valve open percent if dtemp is > 30%
CONF_AUTO_REGULATION_DTEMP: 5,
# only send new valve open percent last mesure was more than 5 min ago
@@ -589,7 +591,7 @@ async def test_bug_533(
CONF_VALVE: "number.mock_valve",
CONF_AUTO_REGULATION_DTEMP: 10, # This parameter makes the bug
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 60,
CONF_SAFETY_DELAY_MIN: 60,
},
# | temps,
)
+159 -114
View File
@@ -9,11 +9,11 @@ from custom_components.versatile_thermostat.base_thermostat import BaseThermosta
from custom_components.versatile_thermostat.thermostat_climate import (
ThermostatOverClimate,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_management_time_not_enough(
@@ -45,8 +45,8 @@ async def test_window_management_time_not_enough(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_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,
@@ -65,10 +65,10 @@ async def test_window_management_time_not_enough(
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
assert entity.target_temperature == 19
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
# Open the window, but condition of time is not satisfied and check the thermostat don't turns off
with patch(
@@ -134,8 +134,8 @@ async def test_window_management_time_enough(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_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,
@@ -154,10 +154,10 @@ async def test_window_management_time_enough(
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
assert entity.target_temperature == 19
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
# change temperature to force turning on the heater
with patch(
@@ -281,8 +281,8 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_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
@@ -304,11 +304,11 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
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_enabled is True
assert entity.window_state is STATE_UNKNOWN
assert entity.window_manager.is_window_auto_configured is True
# Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
@@ -337,8 +337,12 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
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.window_manager._window_auto_algo.is_window_open_detected() is False
)
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert entity.hvac_mode is HVACMode.HEAT
# send one degre down in one minute
@@ -361,8 +365,10 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
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_manager._window_auto_algo.is_window_open_detected() is True
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert entity.window_auto_state == STATE_ON
assert entity.hvac_mode is HVACMode.OFF
@@ -397,8 +403,10 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
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_manager._window_auto_algo.is_window_open_detected() is True
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert entity.window_auto_state == STATE_ON
assert entity.hvac_mode is HVACMode.OFF
@@ -438,8 +446,12 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
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_manager._window_auto_algo.is_window_open_detected() is False
)
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is True
)
assert entity.window_auto_state == STATE_OFF
assert entity.hvac_mode is HVACMode.HEAT
@@ -478,9 +490,10 @@ async def test_window_auto_fast_and_sensor(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.fake_window_sensor",
CONF_WINDOW_DELAY: 10,
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
@@ -504,8 +517,10 @@ async def test_window_auto_fast_and_sensor(
assert entity.preset_mode is PRESET_BOOST
assert entity.target_temperature == 21
assert entity.window_state is STATE_OFF
assert entity.is_window_auto_enabled is False
assert entity.window_state is STATE_UNKNOWN
assert entity.window_auto_state is STATE_UNAVAILABLE
assert entity.window_manager.is_window_auto_configured is False
assert entity.window_manager.is_configured is True
# Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
@@ -534,8 +549,12 @@ async def test_window_auto_fast_and_sensor(
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.window_manager._window_auto_algo.is_window_open_detected() is False
)
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert entity.hvac_mode is HVACMode.HEAT
# send one degre down in one minute
@@ -559,9 +578,11 @@ async def test_window_auto_fast_and_sensor(
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.window_manager._window_auto_algo.is_window_open_detected() is True
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert entity.window_auto_state == STATE_UNAVAILABLE
assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity
@@ -594,8 +615,8 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_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
@@ -617,11 +638,11 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
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_enabled is True
assert entity.window_state is STATE_UNKNOWN
assert entity.window_manager.is_window_auto_configured is True
# 1. Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
@@ -647,8 +668,12 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
# 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.window_manager._window_auto_algo.is_window_open_detected() is False
)
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert entity.hvac_mode is HVACMode.HEAT
# 3. send one degre down in one minute
@@ -665,8 +690,10 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
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 entity.window_manager._window_auto_algo.is_window_open_detected() is True
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert mock_send_event.call_count == 2
# The heater turns off
@@ -714,8 +741,12 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
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
assert (
entity.window_manager._window_auto_algo.is_window_open_detected() is False
)
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
# Clean the entity
entity.remove_thermostat()
@@ -752,11 +783,11 @@ async def test_window_auto_no_on_percent(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_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
CONF_WINDOW_AUTO_MAX_DURATION: 1, # Should be 0 for test but 0 is not possible
},
)
@@ -775,10 +806,11 @@ async def test_window_auto_no_on_percent(
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
assert entity.target_temperature == 20
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
assert entity.window_auto_state is STATE_UNKNOWN
# Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
@@ -806,8 +838,12 @@ async def test_window_auto_no_on_percent(
# 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.window_manager._window_auto_algo.is_window_open_detected() is False
)
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.proportional_algorithm.on_percent == 0.0
@@ -833,10 +869,12 @@ async def test_window_auto_no_on_percent(
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.window_manager._window_auto_algo.is_window_open_detected() is True
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
# But the entity is still on and window_auto is not detected
assert entity.window_auto_state == STATE_UNKNOWN
assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity
@@ -872,8 +910,8 @@ async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
},
@@ -891,11 +929,11 @@ async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
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_enabled is False
assert entity.window_state is STATE_UNKNOWN
assert entity.window_manager.is_window_auto_configured is False
# change temperature to force turning on the heater
with patch(
@@ -918,9 +956,7 @@ async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
# Set Window ByPass to true
await entity.service_set_window_bypass_state(True)
assert entity.window_bypass_state is True
# entity._window_bypass_state = True
assert entity.is_window_bypass is True
# Open the window, condition of time is satisfied, check the thermostat and heater turns off
with patch(
@@ -936,7 +972,10 @@ async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
new_callable=PropertyMock,
return_value=True,
):
await send_window_change_event(entity, True, False, datetime.now())
try_function = await send_window_change_event(
entity, True, False, datetime.now()
)
await try_function(None)
assert mock_send_event.call_count == 0
@@ -944,7 +983,7 @@ async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
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 mock_condition.call_count > 0
assert entity.hvac_mode is HVACMode.HEAT
assert entity.window_state == STATE_ON
@@ -1011,8 +1050,8 @@ async def test_window_auto_bypass(hass: HomeAssistant, skip_hass_states_is_state
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_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
@@ -1034,11 +1073,11 @@ async def test_window_auto_bypass(hass: HomeAssistant, skip_hass_states_is_state
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
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_enabled
assert entity.window_state is STATE_UNKNOWN
assert entity.window_manager.is_window_auto_configured
# Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
@@ -1066,13 +1105,17 @@ async def test_window_auto_bypass(hass: HomeAssistant, skip_hass_states_is_state
# 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.window_manager._window_auto_algo.is_window_open_detected() is False
)
assert (
entity.window_manager._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.window_bypass_state is True
assert entity.is_window_bypass is True
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
@@ -1094,9 +1137,11 @@ async def test_window_auto_bypass(hass: HomeAssistant, skip_hass_states_is_state
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.window_manager._window_auto_algo.is_window_open_detected() is True
assert (
entity.window_manager._window_auto_algo.is_window_close_detected() is False
)
assert entity.window_auto_state == STATE_UNKNOWN
assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity
@@ -1133,8 +1178,8 @@ async def test_window_bypass_reactivate(hass: HomeAssistant, skip_hass_states_is
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
},
@@ -1152,10 +1197,10 @@ async def test_window_bypass_reactivate(hass: HomeAssistant, skip_hass_states_is
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNAVAILABLE
assert entity.target_temperature == 19
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
# change temperature to force turning on the heater
with patch(
@@ -1262,8 +1307,8 @@ async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_s
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 1,
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
@@ -1299,7 +1344,7 @@ async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_s
assert entity
assert entity.is_over_climate is True
assert entity.window_action == CONF_WINDOW_FAN_ONLY
assert entity.window_manager.window_action == CONF_WINDOW_FAN_ONLY
await entity.async_set_hvac_mode(HVACMode.HEAT)
assert entity.hvac_mode == HVACMode.HEAT
@@ -1307,7 +1352,7 @@ async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_s
assert entity.preset_mode == PRESET_COMFORT
assert entity.target_temperature == 18
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
# 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off
with patch(
@@ -1419,8 +1464,8 @@ async def test_window_action_fan_only_ko(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 1,
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
@@ -1456,7 +1501,7 @@ async def test_window_action_fan_only_ko(
assert entity
assert entity.is_over_climate is True
assert entity.window_action == CONF_WINDOW_FAN_ONLY
assert entity.window_manager.window_action == CONF_WINDOW_FAN_ONLY
await entity.async_set_hvac_mode(HVACMode.HEAT)
assert entity.hvac_mode == HVACMode.HEAT
@@ -1464,7 +1509,7 @@ async def test_window_action_fan_only_ko(
assert entity.preset_mode == PRESET_COMFORT
assert entity.target_temperature == 18
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
# 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off
with patch(
@@ -1570,8 +1615,8 @@ async def test_window_action_eco_temp(hass: HomeAssistant, skip_hass_states_is_s
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_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
@@ -1591,11 +1636,11 @@ async def test_window_action_eco_temp(hass: HomeAssistant, skip_hass_states_is_s
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
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_enabled is True
assert entity.window_state is STATE_UNKNOWN
assert entity.window_manager.is_window_auto_configured is True
# 1. Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
@@ -1624,8 +1669,8 @@ async def test_window_action_eco_temp(hass: HomeAssistant, skip_hass_states_is_s
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
assert entity.window_state is STATE_UNKNOWN
assert entity.window_auto_state is STATE_UNKNOWN
# 3. send one degre down in one minute
with patch(
@@ -1648,7 +1693,7 @@ async def test_window_action_eco_temp(hass: HomeAssistant, skip_hass_states_is_s
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
assert entity.window_state == STATE_ON
# No change on HVACMode
assert entity.hvac_mode is HVACMode.HEAT
# No change on preset
@@ -1767,8 +1812,8 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_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
@@ -1788,11 +1833,11 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
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_enabled is True
assert entity.window_state is STATE_UNKNOWN
assert entity.window_manager.is_window_auto_configured is True
# 1. Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
@@ -1821,8 +1866,8 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
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
assert entity.window_state is STATE_UNKNOWN
assert entity.window_auto_state is STATE_UNKNOWN
# 3. send one degre down in one minute
with patch(
@@ -1845,7 +1890,7 @@ async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is
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
assert entity.window_state == STATE_ON
# No change on HVACMode
assert entity.hvac_mode is HVACMode.HEAT
# No change on preset
@@ -1971,9 +2016,9 @@ async def test_bug_66(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_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
@@ -1991,7 +2036,7 @@ async def test_bug_66(
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
assert entity.window_state is STATE_UNKNOWN
# Open the window and let the thermostat shut down
with patch(
@@ -2122,8 +2167,8 @@ async def test_window_action_frost_temp_preset_change(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
CONF_WINDOW_SENSOR: "binary_sensor.fake_window_sensor",
CONF_WINDOW_DELAY: 1,
@@ -2150,8 +2195,8 @@ async def test_window_action_frost_temp_preset_change(
assert vtherm.preset_mode is PRESET_BOOST
assert vtherm.target_temperature == 21
assert vtherm.window_state is STATE_OFF
assert vtherm.is_window_auto_enabled is False
assert vtherm.window_state is STATE_UNKNOWN
assert vtherm.window_manager.is_window_auto_configured is False
# 1. Turn on the window sensor
now = now + timedelta(minutes=1)
@@ -2232,8 +2277,8 @@ async def test_window_action_frost_temp_temp_change(
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_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
CONF_WINDOW_SENSOR: "binary_sensor.fake_window_sensor",
CONF_WINDOW_DELAY: 1,
@@ -2260,8 +2305,8 @@ async def test_window_action_frost_temp_temp_change(
assert vtherm.preset_mode is PRESET_BOOST
assert vtherm.target_temperature == 21
assert vtherm.window_state is STATE_OFF
assert vtherm.is_window_auto_enabled is False
assert vtherm.window_state is STATE_UNKNOWN
assert vtherm.window_manager.is_window_auto_configured is False
# 1. Turn on the window sensor
now = now + timedelta(minutes=1)
+706
View File
@@ -0,0 +1,706 @@
# pylint: disable=unused-argument, line-too-long, protected-access, too-many-lines
""" Test the Window management """
import logging
from datetime import datetime, timedelta
from unittest.mock import patch, call, PropertyMock, AsyncMock, MagicMock
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.feature_window_manager import (
FeatureWindowManager,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
async def test_window_feature_manager_create(
hass: HomeAssistant,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
assert window_manager is not None
assert window_manager.is_configured is False
assert window_manager.is_window_auto_configured is False
assert window_manager.is_window_detected is False
assert window_manager.window_state == STATE_UNAVAILABLE
assert window_manager.name == "the name"
assert len(window_manager._active_listener) == 0
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] is None
assert custom_attributes["window_state"] == STATE_UNAVAILABLE
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_configured"] is False
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["window_delay_sec"] == 0
assert custom_attributes["window_auto_open_threshold"] == 0
assert custom_attributes["window_auto_close_threshold"] == 0
assert custom_attributes["window_auto_max_duration"] == 0
assert custom_attributes["window_action"] is None
@pytest.mark.parametrize(
"use_window_feature, window_sensor_entity_id, window_delay_sec, window_auto_open_threshold, window_auto_close_threshold, window_auto_max_duration, window_action, is_configured, is_auto_configured, window_state, window_auto_state",
[
# fmt: off
( True, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_TURN_OFF, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
( False, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_FAN_ONLY, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
( True, "sensor.the_window_sensor", 10, None, None, None, CONF_WINDOW_FROST_TEMP, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
# delay is missing
( True, "sensor.the_window_sensor", None, None, None, None, CONF_WINDOW_ECO_TEMP, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# action is missing -> defaults to TURN_OFF
( True, "sensor.the_window_sensor", 10, None, None, None, None, True, False, STATE_UNKNOWN, STATE_UNAVAILABLE ),
# With Window auto config complete
( True, None, None, 1, 2, 3, CONF_WINDOW_FAN_ONLY, True, True, STATE_UNKNOWN, STATE_UNKNOWN ),
# With Window auto config not complete -> missing open threshold but defaults to 0
( True, None, None, None, 2, 3, CONF_WINDOW_FROST_TEMP, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# With Window auto config not complete -> missing close threshold
( True, None, None, 1, None, 3, CONF_WINDOW_ECO_TEMP, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# With Window auto config not complete -> missing max duration threshold but defaults to 0
( True, None, None, 1, 2, None, CONF_WINDOW_TURN_OFF, False, False, STATE_UNAVAILABLE, STATE_UNAVAILABLE ),
# fmt: on
],
)
async def test_window_feature_manager_post_init(
hass: HomeAssistant,
use_window_feature,
window_sensor_entity_id,
window_delay_sec,
window_auto_open_threshold,
window_auto_close_threshold,
window_auto_max_duration,
window_action,
is_configured,
is_auto_configured,
window_state,
window_auto_state,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
assert window_manager is not None
# 2. post_init
window_manager.post_init(
{
CONF_USE_WINDOW_FEATURE: use_window_feature,
CONF_WINDOW_SENSOR: window_sensor_entity_id,
CONF_WINDOW_DELAY: window_delay_sec,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: window_auto_open_threshold,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: window_auto_close_threshold,
CONF_WINDOW_AUTO_MAX_DURATION: window_auto_max_duration,
CONF_WINDOW_ACTION: window_action,
}
)
assert window_manager.is_configured is is_configured
assert window_manager.is_window_auto_configured == is_auto_configured
assert window_manager.window_sensor_entity_id == window_sensor_entity_id
assert window_manager.window_state == window_state
assert window_manager.window_auto_state == window_auto_state
assert window_manager.window_delay_sec == window_delay_sec
assert window_manager.window_auto_open_threshold == window_auto_open_threshold
assert window_manager.window_auto_close_threshold == window_auto_close_threshold
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == window_sensor_entity_id
assert custom_attributes["window_state"] == window_state
assert custom_attributes["window_auto_state"] == window_auto_state
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is is_configured
assert custom_attributes["is_window_auto_configured"] is is_auto_configured
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is window_delay_sec
assert custom_attributes["window_auto_open_threshold"] is window_auto_open_threshold
assert (
custom_attributes["window_auto_close_threshold"] is window_auto_close_threshold
)
assert custom_attributes["window_auto_max_duration"] is window_auto_max_duration
@pytest.mark.parametrize(
"current_state, new_state, nb_call, window_state, is_window_detected, changed",
[
(STATE_OFF, STATE_ON, 1, STATE_ON, True, True),
(STATE_OFF, STATE_OFF, 0, STATE_OFF, False, False),
(STATE_ON, STATE_OFF, 1, STATE_OFF, False, True),
(STATE_ON, STATE_ON, 0, STATE_ON, True, False),
],
)
async def test_window_feature_manager_refresh_sensor_action_turn_off(
hass: HomeAssistant,
current_state,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
changed,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
}
)
# 3. start listening
window_manager.start_listening()
assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE
assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)) as mock_get_state:
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.async_set_hvac_mode = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
fake_vtherm.restore_hvac_mode = AsyncMock()
# force old state for the test
window_manager._window_state = current_state
if current_state == STATE_ON:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=HVAC_OFF_REASON_WINDOW_DETECTION)
else:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=None)
ret = await window_manager.refresh_state()
assert ret == changed
assert window_manager.is_configured is True
# in the refresh there is no delay
assert window_manager.window_state == new_state
assert mock_get_state.call_count == 1
assert fake_vtherm.set_hvac_off_reason.call_count == nb_call
if nb_call == 1:
if new_state == STATE_OFF:
assert fake_vtherm.restore_hvac_mode.call_count == 1
assert fake_vtherm.async_set_hvac_mode.call_count == 0
else:
assert fake_vtherm.async_set_hvac_mode.call_count == 1
fake_vtherm.async_set_hvac_mode.assert_has_calls(
[
call.async_set_hvac_mode(HVACMode.OFF),
]
)
assert fake_vtherm.restore_hvac_mode.call_count == 0
reason = None if current_state == STATE_ON and new_state == STATE_OFF else HVAC_OFF_REASON_WINDOW_DETECTION
fake_vtherm.set_hvac_off_reason.assert_has_calls(
[
call.set_hvac_off_reason(reason),
]
)
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.async_set_hvac_mode.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, new_state, nb_call, window_state, is_window_detected, changed",
[
(STATE_OFF, STATE_ON, 1, STATE_ON, True, True),
(STATE_OFF, STATE_OFF, 0, STATE_OFF, False, False),
(STATE_ON, STATE_OFF, 1, STATE_OFF, False, True),
(STATE_ON, STATE_ON, 0, STATE_ON, True, False),
],
)
async def test_window_feature_manager_refresh_sensor_action_frost_only(
hass: HomeAssistant,
current_state,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
changed,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
type(fake_vtherm).last_central_mode = PropertyMock(return_value=None)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
}
)
# 3. start listening
window_manager.start_listening()
assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE
assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.core.StateMachine.get", return_value=State("sensor.the_motion_sensor", new_state)) as mock_get_state:
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.save_target_temp = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
fake_vtherm.restore_target_temp = AsyncMock()
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.find_preset_temp = MagicMock()
fake_vtherm.find_preset_temp.return_value = 17
# force old state for the test
window_manager._window_state = current_state
if current_state == STATE_ON:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=HVAC_OFF_REASON_WINDOW_DETECTION)
else:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=None)
ret = await window_manager.refresh_state()
assert ret == changed
assert window_manager.is_configured is True
# in the refresh there is no delay
assert window_manager.window_state == new_state
assert mock_get_state.call_count == 1
assert fake_vtherm.set_hvac_off_reason.call_count == 0
if nb_call == 1:
if new_state == STATE_OFF:
assert fake_vtherm.restore_target_temp.call_count == 1
assert fake_vtherm.save_target_temp.call_count == 0
assert fake_vtherm.change_target_temperature.call_count == 0
assert fake_vtherm.find_preset_temp.call_count == 0
else:
assert fake_vtherm.restore_target_temp.call_count == 0
assert fake_vtherm.save_target_temp.call_count == 1
assert fake_vtherm.change_target_temperature.call_count == 1
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.change_target_temperature(17),
]
)
assert fake_vtherm.find_preset_temp.call_count == 1
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.save_target_temp.call_count == 0
assert fake_vtherm.change_target_temperature.call_count == 0
assert fake_vtherm.find_preset_temp.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, long_enough, new_state, nb_call, window_state, is_window_detected",
[
(STATE_OFF, True, STATE_ON, 1, STATE_ON, True),
(STATE_OFF, True, STATE_OFF, 0, STATE_OFF, False),
(STATE_ON, True, STATE_OFF, 1, STATE_OFF, False),
(STATE_ON, True, STATE_ON, 0, STATE_ON, True),
(STATE_OFF, False, STATE_ON, 0, STATE_OFF, False),
(STATE_ON, False, STATE_OFF, 0, STATE_ON, True),
],
)
async def test_window_feature_manager_sensor_event_action_turn_off(
hass: HomeAssistant,
current_state,
long_enough,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
}
)
# 3. start listening
window_manager.start_listening()
assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.helpers.condition.state", return_value=long_enough):
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.async_set_hvac_mode = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
fake_vtherm.restore_hvac_mode = AsyncMock()
# force old state for the test
window_manager._window_state = current_state
if current_state == STATE_ON:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=HVAC_OFF_REASON_WINDOW_DETECTION)
else:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=None)
try_window_condition = await window_manager._window_sensor_changed(
event=Event(
event_type=EVENT_STATE_CHANGED,
data={
"entity_id": "sensor.the_window_sensor",
"new_state": State("sensor.the_window_sensor", new_state),
"old_state": State("sensor.the_window_sensor", current_state),
}))
assert try_window_condition is not None
await try_window_condition(None)
# There is change only if long enough
if long_enough:
assert window_manager.window_state == new_state
else:
assert window_manager.window_state == current_state
assert fake_vtherm.set_hvac_off_reason.call_count == nb_call
if nb_call == 1:
if new_state == STATE_OFF:
assert fake_vtherm.restore_hvac_mode.call_count == 1
assert fake_vtherm.async_set_hvac_mode.call_count == 0
else:
assert fake_vtherm.async_set_hvac_mode.call_count == 1
fake_vtherm.async_set_hvac_mode.assert_has_calls(
[
call.async_set_hvac_mode(HVACMode.OFF),
]
)
assert fake_vtherm.restore_hvac_mode.call_count == 0
reason = None if current_state == STATE_ON and new_state == STATE_OFF else HVAC_OFF_REASON_WINDOW_DETECTION
fake_vtherm.set_hvac_off_reason.assert_has_calls(
[
call.set_hvac_off_reason(reason),
]
)
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.async_set_hvac_mode.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state if long_enough else current_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, long_enough, new_state, nb_call, window_state, is_window_detected",
[
(STATE_OFF, True, STATE_ON, 1, STATE_ON, True),
(STATE_OFF, True, STATE_OFF, 0, STATE_OFF, False),
(STATE_ON, True, STATE_OFF, 1, STATE_OFF, False),
(STATE_ON, True, STATE_ON, 0, STATE_ON, True),
(STATE_OFF, False, STATE_ON, 0, STATE_OFF, False),
(STATE_ON, False, STATE_OFF, 0, STATE_ON, True),
],
)
async def test_window_feature_manager_event_sensor_action_frost_only(
hass: HomeAssistant,
current_state,
long_enough,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
type(fake_vtherm).last_central_mode = PropertyMock(return_value=None)
# 1. creation
window_manager = FeatureWindowManager(fake_vtherm, hass)
# 2. post_init
window_manager.post_init(
{
CONF_WINDOW_SENSOR: "sensor.the_window_sensor",
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
}
)
# 3. start listening
window_manager.start_listening()
# 4. test refresh with the parametrized
# fmt:off
with patch("homeassistant.helpers.condition.state", return_value=long_enough):
# fmt:on
# Configurer les méthodes mockées
fake_vtherm.save_target_temp = AsyncMock()
fake_vtherm.set_hvac_off_reason = MagicMock()
fake_vtherm.restore_target_temp = AsyncMock()
fake_vtherm.change_target_temperature = AsyncMock()
fake_vtherm.find_preset_temp = MagicMock()
fake_vtherm.find_preset_temp.return_value = 17
# force old state for the test
window_manager._window_state = current_state
if current_state == STATE_ON:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=HVAC_OFF_REASON_WINDOW_DETECTION)
else:
type(fake_vtherm).hvac_off_reason = PropertyMock(return_value=None)
try_window_condition = await window_manager._window_sensor_changed(
event=Event(
event_type=EVENT_STATE_CHANGED,
data={
"entity_id": "sensor.the_window_sensor",
"new_state": State("sensor.the_window_sensor", new_state),
"old_state": State("sensor.the_window_sensor", current_state),
}))
assert try_window_condition is not None
await try_window_condition(None)
if long_enough:
assert window_manager.window_state == new_state
else:
assert window_manager.window_state == current_state
assert fake_vtherm.set_hvac_off_reason.call_count == 0
if nb_call == 1:
if new_state == STATE_OFF:
assert fake_vtherm.restore_target_temp.call_count == 1
assert fake_vtherm.save_target_temp.call_count == 0
assert fake_vtherm.change_target_temperature.call_count == 0
assert fake_vtherm.find_preset_temp.call_count == 0
else:
assert fake_vtherm.restore_target_temp.call_count == 0
assert fake_vtherm.save_target_temp.call_count == 1
assert fake_vtherm.change_target_temperature.call_count == 1
fake_vtherm.change_target_temperature.assert_has_calls(
[
call.change_target_temperature(17),
]
)
assert fake_vtherm.find_preset_temp.call_count == 1
else:
assert fake_vtherm.restore_hvac_mode.call_count == 0
assert fake_vtherm.save_target_temp.call_count == 0
assert fake_vtherm.change_target_temperature.call_count == 0
assert fake_vtherm.find_preset_temp.call_count == 0
fake_vtherm.reset_mock()
# 5. Check custom_attributes
custom_attributes = {}
window_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["window_sensor_entity_id"] == "sensor.the_window_sensor"
assert custom_attributes["window_state"] == new_state if long_enough else current_state
assert custom_attributes["window_auto_state"] == STATE_UNAVAILABLE
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["is_window_configured"] is True
assert custom_attributes["is_window_auto_configured"] is False
assert custom_attributes["is_window_bypass"] is False
assert custom_attributes["window_delay_sec"] is 10
assert custom_attributes["window_auto_open_threshold"] is None
assert (
custom_attributes["window_auto_close_threshold"] is None
)
assert custom_attributes["window_auto_max_duration"] is None
window_manager.stop_listening()
await hass.async_block_till_done()
@pytest.mark.parametrize(
"current_state, in_cycle, new_temp, new_state, nb_call, window_state, is_window_detected",
[
(STATE_OFF, True, 10, STATE_ON, 1, STATE_ON, True),
(STATE_ON, True, 10, STATE_ON, 0, STATE_ON, True),
(STATE_ON, True, 20, STATE_OFF, 1, STATE_OFF, False),
(STATE_OFF, True, 20, STATE_OFF, 0, STATE_OFF, False),
],
)
async def test_window_feature_manager_window_auto(
hass: HomeAssistant,
current_state,
in_cycle,
new_temp,
new_state, # new state of motion event
nb_call,
window_state,
is_window_detected,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT)
type(fake_vtherm).hvac_mode = PropertyMock(return_value=HVACMode.HEAT)
type(fake_vtherm).last_central_mode = PropertyMock(return_value=None)
type(fake_vtherm).proportional_algorithm = PropertyMock(return_value=None)
# 1. creation / post_init / start listening
window_manager = FeatureWindowManager(fake_vtherm, hass)
window_manager.post_init(
{
CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 3,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1,
CONF_WINDOW_AUTO_MAX_DURATION: 10,
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
}
)
assert window_manager.is_window_auto_configured is True
window_manager.start_listening()
# 2. Call manage window auto
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
# Add a fake temp point for the window_auto_algo. We need at least 4 points
for i in range(0, 4):
window_manager._window_auto_algo.add_temp_measurement(
17 + (i * (new_temp - 17) / 4), now, True
)
now = now + timedelta(minutes=5)
# fmt:off
with patch("custom_components.versatile_thermostat.feature_window_manager.FeatureWindowManager.update_window_state") as mock_update_window_state:
#fmt: on
now = now + timedelta(minutes=10)
# From 17 to new_temp in 10 minutes
type(fake_vtherm).ema_temperature = PropertyMock(return_value=new_temp)
type(fake_vtherm).last_temperature_measure = PropertyMock(return_value=now)
type(fake_vtherm).now = PropertyMock(return_value=now)
fake_vtherm.send_event = MagicMock()
window_manager._window_auto_state = current_state
dearm_window_auto = await window_manager.manage_window_auto(in_cycle=in_cycle)
assert dearm_window_auto is not None
assert mock_update_window_state.call_count == nb_call
if nb_call > 0:
mock_update_window_state.assert_has_calls(
[
call.update_window_state(new_state),
]
)
if new_state == STATE_ON:
assert window_manager._window_call_cancel is not None
assert window_manager.window_auto_state == new_state
# update_window_state is mocked
# assert window_manager.window_state == new_state
window_manager.stop_listening()
await hass.async_block_till_done()