Feature 234 add central boiler helper (#352)
* Creation of the central boiler config + binary_sensor entity * Fonctional before testu. Miss the service call * Full featured but without testu * Documentation and release. * Add events in README * FIX #341 - when window state change, open_valve_percent should be resend * Issue #343 - disable safety mode for outdoor thermometer * Issue #255 - Specify window action on window open detection * Add en and string translation * central boiler - add entites to fine tune the boiler start * With testu ok * Add testus for valve and climate * Add testus in pipelines * With pip 3 * With more pytest options * Ass coverage tests * Add coverage report in github * Release 5.3.0 --------- Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
@@ -6,6 +6,9 @@ from unittest.mock import patch, call, PropertyMock
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
||||
from custom_components.versatile_thermostat.thermostat_climate import (
|
||||
ThermostatOverClimate,
|
||||
)
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
@@ -46,6 +49,7 @@ async def test_window_management_time_not_enough(
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
||||
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
|
||||
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -134,6 +138,7 @@ async def test_window_management_time_enough(
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
||||
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
|
||||
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -242,7 +247,7 @@ async def test_window_management_time_enough(
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
"""Test the Window management"""
|
||||
"""Test the auto Window management with fast slope down"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
@@ -822,7 +827,6 @@ async def test_window_auto_no_on_percent(
|
||||
entity.remove_thermostat()
|
||||
|
||||
|
||||
# PR - Adding Window Bypass
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
@@ -1207,3 +1211,694 @@ async def test_window_bypass_reactivate(hass: HomeAssistant, skip_hass_states_is
|
||||
|
||||
# Clean the entity
|
||||
entity.remove_thermostat()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
"""Test the Window management with the fan_only option"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverClimateMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverClimateMockName",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
"comfort_temp": 18,
|
||||
"boost_temp": 19,
|
||||
CONF_USE_WINDOW_FEATURE: True,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_CLIMATE: "climate.mock_climate",
|
||||
CONF_SECURITY_DELAY_MIN: 5,
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
||||
CONF_WINDOW_DELAY: 1,
|
||||
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
|
||||
# CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
|
||||
# CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
|
||||
# CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection
|
||||
},
|
||||
)
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
|
||||
fake_underlying_climate = MockClimate(
|
||||
hass=hass,
|
||||
unique_id="mockUniqueId",
|
||||
name="MockClimateName",
|
||||
hvac_modes=[HVACMode.HEAT, HVACMode.COOL, HVACMode.FAN_ONLY],
|
||||
)
|
||||
|
||||
# 1. intialize climate entity
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||
return_value=fake_underlying_climate,
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
entity: ThermostatOverClimate = search_entity(
|
||||
hass, "climate.theoverclimatemockname", "climate"
|
||||
)
|
||||
|
||||
assert entity
|
||||
|
||||
assert entity.is_over_climate is True
|
||||
assert entity.window_action == CONF_WINDOW_FAN_ONLY
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity.hvac_mode == HVACMode.HEAT
|
||||
await entity.async_set_preset_mode(PRESET_COMFORT)
|
||||
assert entity.preset_mode == PRESET_COMFORT
|
||||
assert entity.target_temperature == 18
|
||||
|
||||
assert entity.window_state is STATE_OFF
|
||||
|
||||
# 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
), patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
||||
) as mock_underlying_set_hvac_mode:
|
||||
event_timestamp = now - timedelta(minutes=2)
|
||||
try_window_condition = await send_window_change_event(
|
||||
entity, True, False, event_timestamp
|
||||
)
|
||||
await try_window_condition(None)
|
||||
|
||||
assert mock_send_event.call_count == 1
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(
|
||||
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.FAN_ONLY}
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# The underlying should be in OFF hvac_mode
|
||||
assert mock_underlying_set_hvac_mode.call_count == 1
|
||||
mock_underlying_set_hvac_mode.assert_has_calls(
|
||||
[
|
||||
call.set_hvac_mode(HVACMode.FAN_ONLY),
|
||||
]
|
||||
)
|
||||
|
||||
assert entity.window_state == STATE_ON
|
||||
# The underlying should be in FAN_ONLY hvac_mode
|
||||
assert entity.hvac_mode is HVACMode.FAN_ONLY
|
||||
assert entity._saved_hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_COMFORT
|
||||
|
||||
# 3. Close the window
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
), patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
||||
) as mock_underlying_set_hvac_mode:
|
||||
event_timestamp = now - timedelta(minutes=1)
|
||||
try_function = await send_window_change_event(
|
||||
entity, False, True, event_timestamp, sleep=False
|
||||
)
|
||||
|
||||
await try_function(None)
|
||||
|
||||
# Wait for initial delay of heater
|
||||
await asyncio.sleep(0.3)
|
||||
|
||||
assert entity.window_state == STATE_OFF
|
||||
assert mock_send_event.call_count == 1
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(
|
||||
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
|
||||
),
|
||||
],
|
||||
any_order=False,
|
||||
)
|
||||
|
||||
# The underlying should be in OFF hvac_mode
|
||||
assert mock_underlying_set_hvac_mode.call_count == 1
|
||||
mock_underlying_set_hvac_mode.assert_has_calls(
|
||||
[
|
||||
call.set_hvac_mode(HVACMode.HEAT),
|
||||
]
|
||||
)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_COMFORT
|
||||
|
||||
# Clean the entity
|
||||
entity.remove_thermostat()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_window_action_fan_only_ko(
|
||||
hass: HomeAssistant, skip_hass_states_is_state
|
||||
):
|
||||
"""Test the Window management with the fan_only option but the underlyings doesn't have the FAN_ONLY mode
|
||||
So the VTherm switch to OFF which is the fallback mode"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverClimateMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverClimateMockName",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
"comfort_temp": 18,
|
||||
"boost_temp": 19,
|
||||
CONF_USE_WINDOW_FEATURE: True,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_CLIMATE: "climate.mock_climate",
|
||||
CONF_SECURITY_DELAY_MIN: 5,
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
|
||||
CONF_WINDOW_DELAY: 1,
|
||||
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
|
||||
# CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
|
||||
# CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
|
||||
# CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection
|
||||
},
|
||||
)
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
|
||||
fake_underlying_climate = MockClimate(
|
||||
hass=hass,
|
||||
unique_id="mockUniqueId",
|
||||
name="MockClimateName",
|
||||
hvac_modes=[HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO],
|
||||
)
|
||||
|
||||
# 1. intialize climate entity
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||
return_value=fake_underlying_climate,
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
entity: ThermostatOverClimate = search_entity(
|
||||
hass, "climate.theoverclimatemockname", "climate"
|
||||
)
|
||||
|
||||
assert entity
|
||||
|
||||
assert entity.is_over_climate is True
|
||||
assert entity.window_action == CONF_WINDOW_FAN_ONLY
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity.hvac_mode == HVACMode.HEAT
|
||||
await entity.async_set_preset_mode(PRESET_COMFORT)
|
||||
assert entity.preset_mode == PRESET_COMFORT
|
||||
assert entity.target_temperature == 18
|
||||
|
||||
assert entity.window_state is STATE_OFF
|
||||
|
||||
# 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
), patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
||||
) as mock_underlying_set_hvac_mode:
|
||||
event_timestamp = now - timedelta(minutes=2)
|
||||
try_window_condition = await send_window_change_event(
|
||||
entity, True, False, event_timestamp
|
||||
)
|
||||
await try_window_condition(None)
|
||||
|
||||
assert mock_send_event.call_count == 1
|
||||
mock_send_event.assert_has_calls(
|
||||
[call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})]
|
||||
)
|
||||
|
||||
assert entity.window_state == STATE_ON
|
||||
assert entity.hvac_mode is HVACMode.OFF
|
||||
# The underlying should be in OFF hvac_mode
|
||||
assert mock_underlying_set_hvac_mode.call_count == 1
|
||||
mock_underlying_set_hvac_mode.assert_has_calls(
|
||||
[
|
||||
call.set_hvac_mode(HVACMode.OFF),
|
||||
]
|
||||
)
|
||||
|
||||
assert entity._saved_hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_COMFORT
|
||||
|
||||
# 3. Close the window
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
), patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
||||
) as mock_underlying_set_hvac_mode:
|
||||
event_timestamp = now - timedelta(minutes=1)
|
||||
try_function = await send_window_change_event(
|
||||
entity, False, True, event_timestamp, sleep=False
|
||||
)
|
||||
|
||||
await try_function(None)
|
||||
|
||||
# Wait for initial delay of heater
|
||||
await asyncio.sleep(0.3)
|
||||
|
||||
assert entity.window_state == STATE_OFF
|
||||
assert mock_send_event.call_count == 1
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(
|
||||
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
|
||||
),
|
||||
],
|
||||
any_order=False,
|
||||
)
|
||||
|
||||
# The underlying should be in OFF hvac_mode
|
||||
assert mock_underlying_set_hvac_mode.call_count == 1
|
||||
mock_underlying_set_hvac_mode.assert_has_calls(
|
||||
[
|
||||
call.set_hvac_mode(HVACMode.HEAT),
|
||||
]
|
||||
)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_COMFORT
|
||||
|
||||
# Clean the entity
|
||||
entity.remove_thermostat()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_window_action_eco_temp(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
"""Test the Window management with the eco_temp option"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverSwitchMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverSwitchMockName",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
"comfort_temp": 18,
|
||||
"boost_temp": 21,
|
||||
CONF_USE_WINDOW_FEATURE: True,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_HEATER: "switch.mock_switch",
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.01,
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SECURITY_DELAY_MIN: 5,
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
|
||||
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
|
||||
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
|
||||
CONF_WINDOW_ACTION: CONF_WINDOW_ECO_TEMP,
|
||||
},
|
||||
)
|
||||
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theoverswitchmockname"
|
||||
)
|
||||
assert entity
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now = datetime.now(tz)
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.overpowering_state is None
|
||||
assert entity.target_temperature == 21
|
||||
|
||||
assert entity.window_state is STATE_OFF
|
||||
assert entity.is_window_auto_enabled is True
|
||||
|
||||
# 1. Initialize the slope algo with 2 measurements
|
||||
event_timestamp = now + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
|
||||
# 2. Make the temperature down
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=True,
|
||||
):
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 0
|
||||
assert entity.is_device_active is True
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.window_state is STATE_OFF
|
||||
assert entity.window_auto_state is STATE_OFF
|
||||
|
||||
# 3. send one degre down in one minute
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=True,
|
||||
):
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 1
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert entity.last_temperature_slope == -6.24
|
||||
assert entity.window_auto_state == STATE_ON
|
||||
assert entity.window_state == STATE_OFF
|
||||
# No change on HVACMode
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
# No change on preset
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
# The eco temp
|
||||
assert entity.target_temperature == 17
|
||||
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(
|
||||
EventType.WINDOW_AUTO_EVENT,
|
||||
{"type": "start", "cause": "slope alert", "curve_slope": -6.24},
|
||||
),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
|
||||
# 4. send another 0.1 degre in one minute -> no change
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 17.9, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert round(entity.last_temperature_slope, 3) == -7.49
|
||||
assert entity.window_auto_state == STATE_ON
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
# No change on preset
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
# The eco temp
|
||||
assert entity.target_temperature == 17
|
||||
|
||||
# 5. send another plus 1.1 degre in one minute -> restore state
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 1
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(
|
||||
EventType.WINDOW_AUTO_EVENT,
|
||||
{
|
||||
"type": "end",
|
||||
"cause": "end of slope alert",
|
||||
"curve_slope": 0.42,
|
||||
},
|
||||
),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert entity.last_temperature_slope == 0.42
|
||||
assert entity.window_auto_state == STATE_OFF
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
# No change on preset
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
# The eco temp
|
||||
assert entity.target_temperature == 21
|
||||
|
||||
# Clean the entity
|
||||
entity.remove_thermostat()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is_state):
|
||||
"""Test the Window management with the frost_temp option"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverSwitchMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverSwitchMockName",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
"eco_temp": 17,
|
||||
"comfort_temp": 18,
|
||||
"boost_temp": 21,
|
||||
"frost_temp": 10,
|
||||
CONF_USE_WINDOW_FEATURE: True,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_HEATER: "switch.mock_switch",
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.01,
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SECURITY_DELAY_MIN: 5,
|
||||
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
|
||||
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
|
||||
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
|
||||
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
|
||||
},
|
||||
)
|
||||
|
||||
entity: BaseThermostat = await create_thermostat(
|
||||
hass, entry, "climate.theoverswitchmockname"
|
||||
)
|
||||
assert entity
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now = datetime.now(tz)
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.overpowering_state is None
|
||||
assert entity.target_temperature == 21
|
||||
|
||||
assert entity.window_state is STATE_OFF
|
||||
assert entity.is_window_auto_enabled is True
|
||||
|
||||
# 1. Initialize the slope algo with 2 measurements
|
||||
event_timestamp = now + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
|
||||
# 2. Make the temperature down
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=True,
|
||||
):
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 0
|
||||
assert entity.is_device_active is True
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
assert entity.window_state is STATE_OFF
|
||||
assert entity.window_auto_state is STATE_OFF
|
||||
|
||||
# 3. send one degre down in one minute
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
return_value=True,
|
||||
):
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 1
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert entity.last_temperature_slope == -6.24
|
||||
assert entity.window_auto_state == STATE_ON
|
||||
assert entity.window_state == STATE_OFF
|
||||
# No change on HVACMode
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
# No change on preset
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
# The eco temp
|
||||
assert entity.target_temperature == 10
|
||||
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(
|
||||
EventType.WINDOW_AUTO_EVENT,
|
||||
{"type": "start", "cause": "slope alert", "curve_slope": -6.24},
|
||||
),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
|
||||
# 4. send another 0.1 degre in one minute -> no change
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 17.9, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 0
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert round(entity.last_temperature_slope, 3) == -7.49
|
||||
assert entity.window_auto_state == STATE_ON
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
# No change on preset
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
# The eco temp
|
||||
assert entity.target_temperature == 10
|
||||
|
||||
# 5. send another plus 1.1 degre in one minute -> restore state
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
||||
) as mock_heater_on, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
||||
) as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||
|
||||
# The heater turns on
|
||||
assert mock_send_event.call_count == 1
|
||||
mock_send_event.assert_has_calls(
|
||||
[
|
||||
call.send_event(
|
||||
EventType.WINDOW_AUTO_EVENT,
|
||||
{
|
||||
"type": "end",
|
||||
"cause": "end of slope alert",
|
||||
"curve_slope": 0.42,
|
||||
},
|
||||
),
|
||||
],
|
||||
any_order=True,
|
||||
)
|
||||
assert mock_heater_on.call_count == 0
|
||||
assert mock_heater_off.call_count == 0
|
||||
assert entity.last_temperature_slope == 0.42
|
||||
assert entity.window_auto_state == STATE_OFF
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
# No change on preset
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
# The eco temp
|
||||
assert entity.target_temperature == 21
|
||||
|
||||
# Clean the entity
|
||||
entity.remove_thermostat()
|
||||
|
||||
Reference in New Issue
Block a user