* Replace tests at the right place. Add missing config files. Run tests * FIX merge from #108 have lost some changes * FIX entity_not found error * Change port * Add tests for over switch AC mode * Issue #133 - Force temperature after off to on transition of over climate * Build release 3.6.0 --------- Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
456 lines
17 KiB
Python
456 lines
17 KiB
Python
""" Test the Power management """
|
|
from unittest.mock import patch, call
|
|
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
|
from datetime import datetime, timedelta
|
|
|
|
from homeassistant.const import UnitOfTemperature
|
|
|
|
import logging
|
|
|
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_power_management_hvac_off(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Power management"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: False,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: True,
|
|
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_POWER_SENSOR: "sensor.mock_power_sensor",
|
|
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
|
|
CONF_DEVICE_POWER: 100,
|
|
CONF_PRESET_POWER: 12,
|
|
},
|
|
)
|
|
|
|
entity: VersatileThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
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.hvac_mode == HVACMode.OFF
|
|
|
|
# Send power mesurement
|
|
await send_power_change_event(entity, 50, datetime.now())
|
|
assert await entity.check_overpowering() is False
|
|
|
|
# All configuration is not complete
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.overpowering_state is None
|
|
|
|
# Send power max mesurement
|
|
await send_max_power_change_event(entity, 300, datetime.now())
|
|
assert await entity.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
|
|
|
|
# Send power max mesurement too low but HVACMode is off
|
|
with patch(
|
|
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
|
await send_max_power_change_event(entity, 149, datetime.now())
|
|
assert await entity.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 mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is_state):
|
|
"""Test the Power management"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: False,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: True,
|
|
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_POWER_SENSOR: "sensor.mock_power_sensor",
|
|
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
|
|
CONF_DEVICE_POWER: 100,
|
|
CONF_PRESET_POWER: 12,
|
|
},
|
|
)
|
|
|
|
entity: VersatileThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.overpowering_state is None
|
|
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
|
|
# All configuration is complete and power is < power_max
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.overpowering_state is False
|
|
|
|
# Send power max mesurement too low and HVACMode is on
|
|
with patch(
|
|
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
|
await send_max_power_change_event(entity, 149, datetime.now())
|
|
assert await entity.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.target_temperature == 12
|
|
|
|
assert mock_send_event.call_count == 2
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_POWER}),
|
|
call.send_event(
|
|
EventType.POWER_EVENT,
|
|
{
|
|
"type": "start",
|
|
"current_power": 50,
|
|
"device_power": 100,
|
|
"current_power_max": 149,
|
|
},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 1
|
|
|
|
# Send power mesurement low to unseet power preset
|
|
with patch(
|
|
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
|
await send_power_change_event(entity, 48, datetime.now())
|
|
assert await entity.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.target_temperature == 19
|
|
|
|
assert mock_send_event.call_count == 2
|
|
mock_send_event.assert_has_calls(
|
|
[
|
|
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_BOOST}),
|
|
call.send_event(
|
|
EventType.POWER_EVENT,
|
|
{
|
|
"type": "end",
|
|
"current_power": 48,
|
|
"device_power": 100,
|
|
"current_power_max": 149,
|
|
},
|
|
),
|
|
],
|
|
any_order=True,
|
|
)
|
|
# No current temperature is set so the heater wont be turned on
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_power_management_energy_over_switch(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Power management energy mesurement"""
|
|
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
title="TheOverSwitchMockName",
|
|
unique_id="uniqueId",
|
|
data={
|
|
CONF_NAME: "TheOverSwitchMockName",
|
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
|
CONF_CYCLE_MIN: 5,
|
|
CONF_TEMP_MIN: 15,
|
|
CONF_TEMP_MAX: 30,
|
|
"eco_temp": 17,
|
|
"comfort_temp": 18,
|
|
"boost_temp": 19,
|
|
CONF_USE_WINDOW_FEATURE: False,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: True,
|
|
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_POWER_SENSOR: "sensor.mock_power_sensor",
|
|
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
|
|
CONF_DEVICE_POWER: 100,
|
|
CONF_PRESET_POWER: 12,
|
|
},
|
|
)
|
|
|
|
entity: VersatileThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverswitchmockname"
|
|
)
|
|
assert entity
|
|
|
|
tpi_algo = entity._prop_algorithm
|
|
assert tpi_algo
|
|
|
|
assert entity.total_energy == 0
|
|
|
|
# set temperature to 15 so that on_percent will be set
|
|
with patch(
|
|
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
|
await send_temperature_change_event(entity, 15, datetime.now())
|
|
|
|
assert entity.hvac_mode is HVACMode.HEAT
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.target_temperature == 19
|
|
assert entity.current_temperature == 15
|
|
assert tpi_algo.on_percent == 1
|
|
|
|
assert entity.mean_cycle_power == 100.0
|
|
|
|
assert mock_send_event.call_count == 2
|
|
assert mock_heater_on.call_count == 1
|
|
assert mock_heater_off.call_count == 0
|
|
|
|
entity.incremente_energy()
|
|
assert entity.total_energy == 100 * 5 / 60.0
|
|
entity.incremente_energy()
|
|
assert entity.total_energy == 2 * 100 * 5 / 60.0
|
|
|
|
# change temperature to a higher value
|
|
with patch(
|
|
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
|
await send_temperature_change_event(entity, 18, datetime.now())
|
|
assert tpi_algo.on_percent == 0.3
|
|
assert entity.mean_cycle_power == 30.0
|
|
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
|
|
entity.incremente_energy()
|
|
assert round(entity.total_energy, 2) == round((2.0 + 0.3) * 100 * 5 / 60.0, 2)
|
|
|
|
entity.incremente_energy()
|
|
assert round(entity.total_energy, 2) == round((2.0 + 0.6) * 100 * 5 / 60.0, 2)
|
|
|
|
# change temperature to a much higher value so that heater will be shut down
|
|
with patch(
|
|
"custom_components.versatile_thermostat.climate.VersatileThermostat.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:
|
|
await send_temperature_change_event(entity, 20, datetime.now())
|
|
assert tpi_algo.on_percent == 0.0
|
|
assert entity.mean_cycle_power == 0.0
|
|
|
|
assert mock_send_event.call_count == 0
|
|
assert mock_heater_on.call_count == 0
|
|
assert mock_heater_off.call_count == 0
|
|
|
|
entity.incremente_energy()
|
|
# No change on energy
|
|
assert round(entity.total_energy, 2) == round((2.0 + 0.6) * 100 * 5 / 60.0, 2)
|
|
|
|
# Still no change
|
|
entity.incremente_energy()
|
|
assert round(entity.total_energy, 2) == round((2.0 + 0.6) * 100 * 5 / 60.0, 2)
|
|
|
|
|
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
|
async def test_power_management_energy_over_climate(
|
|
hass: HomeAssistant, skip_hass_states_is_state
|
|
):
|
|
"""Test the Power management for a over_climate thermostat"""
|
|
|
|
the_mock_underlying = MagicMockClimate()
|
|
with patch(
|
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
|
return_value=the_mock_underlying,
|
|
):
|
|
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: False,
|
|
CONF_USE_MOTION_FEATURE: False,
|
|
CONF_USE_POWER_FEATURE: 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_POWER_SENSOR: "sensor.mock_power_sensor",
|
|
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
|
|
CONF_DEVICE_POWER: 100,
|
|
CONF_PRESET_POWER: 12,
|
|
},
|
|
)
|
|
|
|
entity: VersatileThermostat = await create_thermostat(
|
|
hass, entry, "climate.theoverclimatemockname"
|
|
)
|
|
assert entity
|
|
assert entity.is_over_climate
|
|
|
|
now = datetime.now(tz=get_tz(hass))
|
|
await send_temperature_change_event(entity, 15, now)
|
|
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.hvac_action is HVACAction.IDLE
|
|
assert entity.preset_mode is PRESET_BOOST
|
|
assert entity.target_temperature == 19
|
|
assert entity.current_temperature == 15
|
|
|
|
# Not initialised yet
|
|
assert entity.mean_cycle_power is None
|
|
assert entity._underlying_climate_start_hvac_action_date is None
|
|
|
|
# Send a climate_change event with HVACAction=HEATING
|
|
event_timestamp = now - timedelta(minutes=3)
|
|
await send_climate_change_event(
|
|
entity,
|
|
new_hvac_mode=HVACMode.HEAT,
|
|
old_hvac_mode=HVACMode.HEAT,
|
|
new_hvac_action=HVACAction.HEATING,
|
|
old_hvac_action=HVACAction.OFF,
|
|
date=event_timestamp,
|
|
)
|
|
# We have the start event and not the end event
|
|
assert (entity._underlying_climate_start_hvac_action_date - now).total_seconds() < 1
|
|
|
|
entity.incremente_energy()
|
|
assert entity.total_energy == 0
|
|
|
|
# Send a climate_change event with HVACAction=IDLE (end of heating)
|
|
await send_climate_change_event(
|
|
entity,
|
|
new_hvac_mode=HVACMode.HEAT,
|
|
old_hvac_mode=HVACMode.HEAT,
|
|
new_hvac_action=HVACAction.IDLE,
|
|
old_hvac_action=HVACAction.HEATING,
|
|
date=now,
|
|
)
|
|
# We have the end event -> we should have some power and on_percent
|
|
assert entity._underlying_climate_start_hvac_action_date is None
|
|
|
|
# 3 minutes at 100 W
|
|
assert entity.total_energy == 100 * 3.0 / 60
|
|
|
|
# Test the re-increment
|
|
entity.incremente_energy()
|
|
assert entity.total_energy == 2 * 100 * 3.0 / 60
|