[Feature Request] - Pwoer shedding should prevent all VTherm to start at the same time (#845)
Fixes #844 Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
@@ -1528,8 +1528,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
is_window_detected = self._window_manager.is_window_detected
|
||||
if new_central_mode == CENTRAL_MODE_AUTO:
|
||||
if not is_window_detected and not first_init:
|
||||
await self.restore_hvac_mode()
|
||||
await self.restore_preset_mode()
|
||||
await self.restore_preset_mode(force=False)
|
||||
await self.restore_hvac_mode(need_control_heating=True)
|
||||
elif is_window_detected and self.hvac_mode == HVACMode.OFF:
|
||||
# do not restore but mark the reason of off with window detection
|
||||
self.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
|
||||
|
||||
@@ -47,6 +47,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
self._current_max_power: float = None
|
||||
self._power_temp: float = None
|
||||
self._cancel_calculate_shedding_call = None
|
||||
self._started_vtherm_total_power: float = None
|
||||
# Not used now
|
||||
self._last_shedding_date = None
|
||||
|
||||
@@ -71,6 +72,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
and self._power_temp
|
||||
):
|
||||
self._is_configured = True
|
||||
self._started_vtherm_total_power = 0
|
||||
else:
|
||||
_LOGGER.info("Power management is not fully configured and will be deactivated")
|
||||
|
||||
@@ -102,6 +104,8 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
"""Handle power changes."""
|
||||
_LOGGER.debug("Receive new Power event")
|
||||
_LOGGER.debug(event)
|
||||
|
||||
self._started_vtherm_total_power = 0
|
||||
await self.refresh_state()
|
||||
|
||||
@callback
|
||||
@@ -275,6 +279,12 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
vtherms.sort(key=cmp_to_key(cmp_temps))
|
||||
return vtherms
|
||||
|
||||
def add_started_vtherm_total_power(self, started_power: float):
|
||||
"""Add the power into the _started_vtherm_total_power which holds all VTherm started after
|
||||
the last power measurement"""
|
||||
self._started_vtherm_total_power += started_power
|
||||
_LOGGER.debug("%s - started_vtherm_total_power is now %s", self, self._started_vtherm_total_power)
|
||||
|
||||
@property
|
||||
def is_configured(self) -> bool:
|
||||
"""True if the FeatureManager is fully configured"""
|
||||
@@ -305,5 +315,10 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
||||
"""Return the max power sensor entity id"""
|
||||
return self._max_power_sensor_entity_id
|
||||
|
||||
@property
|
||||
def started_vtherm_total_power(self) -> float | None:
|
||||
"""Return the started_vtherm_total_power"""
|
||||
return self._started_vtherm_total_power
|
||||
|
||||
def __str__(self):
|
||||
return "CentralPowerManager"
|
||||
|
||||
@@ -104,7 +104,8 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
|
||||
async def check_power_available(self) -> bool:
|
||||
"""Check if the Vtherm can be started considering overpowering.
|
||||
Returns True if no overpowering conditions are found
|
||||
Returns True if no overpowering conditions are found.
|
||||
If True the vtherm power is written into the temporay vtherm started
|
||||
"""
|
||||
|
||||
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
|
||||
@@ -116,6 +117,7 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
|
||||
current_power = vtherm_api.central_power_manager.current_power
|
||||
current_max_power = vtherm_api.central_power_manager.current_max_power
|
||||
started_vtherm_total_power = vtherm_api.central_power_manager.started_vtherm_total_power
|
||||
if (
|
||||
current_power is None
|
||||
or current_max_power is None
|
||||
@@ -146,7 +148,7 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
self._device_power * self._vtherm.proportional_algorithm.on_percent,
|
||||
)
|
||||
|
||||
ret = (current_power + power_consumption_max) < current_max_power
|
||||
ret = (current_power + started_vtherm_total_power + power_consumption_max) < current_max_power
|
||||
if not ret:
|
||||
_LOGGER.info(
|
||||
"%s - there is not enough power available power=%.3f, max_power=%.3f heater power=%.3f",
|
||||
@@ -155,6 +157,10 @@ class FeaturePowerManager(BaseFeatureManager):
|
||||
current_max_power,
|
||||
self._device_power,
|
||||
)
|
||||
else:
|
||||
# Adds the current_power_max to the started vtherm total power
|
||||
vtherm_api.central_power_manager.add_started_vtherm_total_power(power_consumption_max)
|
||||
|
||||
return ret
|
||||
|
||||
async def set_overpowering(self, overpowering: bool, power_consumption_max=0):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pylint: disable=wildcard-import, unused-wildcard-import, unused-argument, line-too-long, protected-access
|
||||
|
||||
""" Test the normal start of a Thermostat """
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, PropertyMock
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -179,7 +179,7 @@ async def test_overpowering_binary_sensors(
|
||||
)
|
||||
# fmt:off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
# fmt: on
|
||||
await send_power_change_event(entity, 150, now)
|
||||
await send_max_power_change_event(entity, 100, now)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# pylint: disable=protected-access, unused-argument, line-too-long
|
||||
""" Test the Central Power management """
|
||||
import asyncio
|
||||
from unittest.mock import patch, AsyncMock, MagicMock, PropertyMock
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
@@ -10,6 +11,14 @@ from custom_components.versatile_thermostat.feature_power_manager import (
|
||||
from custom_components.versatile_thermostat.central_feature_power_manager import (
|
||||
CentralFeaturePowerManager,
|
||||
)
|
||||
|
||||
from custom_components.versatile_thermostat.thermostat_switch import (
|
||||
ThermostatOverSwitch,
|
||||
)
|
||||
from custom_components.versatile_thermostat.thermostat_climate import (
|
||||
ThermostatOverClimate,
|
||||
)
|
||||
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
@@ -700,3 +709,150 @@ async def test_central_power_manager_max_power_event(
|
||||
|
||||
assert central_power_manager.current_max_power == expected_power
|
||||
assert mock_calculate_shedding.call_count == nb_call
|
||||
|
||||
|
||||
async def test_central_power_manager_start_vtherm_power(hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager):
|
||||
"""Tests the central power start VTherm power. The objective is to starts VTherm until the power max is exceeded"""
|
||||
|
||||
temps = {
|
||||
"eco": 17,
|
||||
"comfort": 18,
|
||||
"boost": 19,
|
||||
}
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverSwitchMockName",
|
||||
unique_id="uniqueId",
|
||||
data={
|
||||
CONF_NAME: "TheOverSwitchMockName",
|
||||
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_TEMP_MIN: 15,
|
||||
CONF_TEMP_MAX: 30,
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: True,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||
CONF_TPI_COEF_INT: 0.3,
|
||||
CONF_TPI_COEF_EXT: 0.01,
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SAFETY_DELAY_MIN: 5,
|
||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_DEVICE_POWER: 1000,
|
||||
CONF_PRESET_POWER: 12,
|
||||
},
|
||||
)
|
||||
|
||||
entity: ThermostatOverSwitch = await create_thermostat(hass, entry, "climate.theoverswitchmockname", temps)
|
||||
assert entity
|
||||
|
||||
now: datetime = NowClass.get_now(hass)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
central_power_manager = VersatileThermostatAPI.get_vtherm_api().central_power_manager
|
||||
assert central_power_manager
|
||||
|
||||
side_effects = SideEffects(
|
||||
{
|
||||
"sensor.the_power_sensor": State("sensor.the_power_sensor", 1000),
|
||||
"sensor.the_max_power_sensor": State("sensor.the_max_power_sensor", 2100),
|
||||
},
|
||||
State("unknown.entity_id", "unknown"),
|
||||
)
|
||||
|
||||
# 1. Make the heater heats
|
||||
# fmt: off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=False):
|
||||
# fmt: on
|
||||
# make the heater heats
|
||||
await send_power_change_event(entity, 1000, now)
|
||||
await send_max_power_change_event(entity, 2100, now)
|
||||
|
||||
await send_temperature_change_event(entity, 15, now)
|
||||
await send_ext_temperature_change_event(entity, 1, now)
|
||||
|
||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||
assert entity.preset_mode is PRESET_BOOST
|
||||
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
||||
assert entity.target_temperature == 19
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# the power of Vtherm should have been added
|
||||
assert central_power_manager.started_vtherm_total_power == 1000
|
||||
|
||||
# 2. Check that another heater cannot heat
|
||||
entry2 = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="TheOverClimateMockName2",
|
||||
unique_id="uniqueId2",
|
||||
data={
|
||||
CONF_NAME: "TheOverClimateMockName2",
|
||||
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,
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: True,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_UNDERLYING_LIST: ["switch.mock_climate"],
|
||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||
CONF_SAFETY_DELAY_MIN: 5,
|
||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||
CONF_DEVICE_POWER: 150,
|
||||
CONF_PRESET_POWER: 12,
|
||||
},
|
||||
)
|
||||
|
||||
entity2: ThermostatOverClimate = await create_thermostat(hass, entry2, "climate.theoverclimatemockname2", temps)
|
||||
assert entity2
|
||||
|
||||
fake_underlying_climate = MockClimate(
|
||||
hass=hass,
|
||||
unique_id="mockUniqueId",
|
||||
name="MockClimateName",
|
||||
)
|
||||
|
||||
# fmt: off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=False), \
|
||||
patch("custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",return_value=fake_underlying_climate):
|
||||
# fmt: on
|
||||
# make the heater heats
|
||||
await entity2.async_set_preset_mode(PRESET_COMFORT)
|
||||
assert entity2.preset_mode is PRESET_COMFORT
|
||||
assert entity2.power_manager.overpowering_state is STATE_UNKNOWN
|
||||
assert entity2.target_temperature == 18
|
||||
await entity2.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity2.hvac_mode is HVACMode.HEAT
|
||||
|
||||
await hass.async_block_till_done()
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# the power of Vtherm should have not been added (cause it has not started) and the entity2 should be shedding
|
||||
assert central_power_manager.started_vtherm_total_power == 1000
|
||||
|
||||
|
||||
assert entity2.power_manager.overpowering_state is STATE_ON
|
||||
|
||||
# 3. sends a new power sensor event
|
||||
await send_max_power_change_event(entity, 2150, now)
|
||||
# No change
|
||||
assert central_power_manager.started_vtherm_total_power == 1000
|
||||
|
||||
await send_power_change_event(entity, 1010, now)
|
||||
assert central_power_manager.started_vtherm_total_power == 0
|
||||
|
||||
@@ -347,22 +347,17 @@ async def test_motion_management_time_not_enough(
|
||||
assert entity.presence_state == STATE_ON
|
||||
|
||||
# 2. starts detecting motion with time not enough
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) 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,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=False
|
||||
) as mock_condition, patch(
|
||||
"homeassistant.core.StateMachine.get",
|
||||
return_value=State(
|
||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
||||
),
|
||||
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
try_condition = await send_motion_change_event(
|
||||
@@ -387,14 +382,11 @@ async def test_motion_management_time_not_enough(
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# starts detecting motion with time enough this time
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) 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,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -415,22 +407,17 @@ async def test_motion_management_time_not_enough(
|
||||
assert entity.presence_state == STATE_ON
|
||||
|
||||
# stop detecting motion with off delay too low
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=False
|
||||
) as mock_condition, patch(
|
||||
"homeassistant.core.StateMachine.get",
|
||||
return_value=State(
|
||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
||||
),
|
||||
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=2)
|
||||
try_condition = await send_motion_change_event(
|
||||
@@ -454,14 +441,11 @@ async def test_motion_management_time_not_enough(
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# stop detecting motion with off delay enough long
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -562,14 +546,11 @@ async def test_motion_management_time_enough_and_presence(
|
||||
assert entity.presence_state == "on"
|
||||
|
||||
# starts detecting motion
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) 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,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -590,14 +571,11 @@ async def test_motion_management_time_enough_and_presence(
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# stop detecting motion with confirmation of stop
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -693,14 +671,11 @@ async def test_motion_management_time_enough_and_not_presence(
|
||||
assert entity.presence_state == STATE_OFF
|
||||
|
||||
# starts detecting motion
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) 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,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -722,14 +697,11 @@ async def test_motion_management_time_enough_and_not_presence(
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# stop detecting motion with confirmation of stop
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -826,14 +798,11 @@ async def test_motion_management_with_stop_during_condition(
|
||||
assert entity.presence_state == STATE_OFF
|
||||
|
||||
# starts detecting motion
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
@@ -959,12 +928,11 @@ async def test_motion_management_with_stop_during_condition_last_state_on(
|
||||
# 1. starts detecting motion but the sensor is off
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
||||
"homeassistant.core.StateMachine.get",
|
||||
return_value=State(
|
||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF
|
||||
),
|
||||
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_OFF),
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=5)
|
||||
try_condition1 = await send_motion_change_event(
|
||||
@@ -982,12 +950,11 @@ async def test_motion_management_with_stop_during_condition_last_state_on(
|
||||
# 2. starts detecting motion but the sensor is on
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
), patch("homeassistant.helpers.condition.state", return_value=False), patch(
|
||||
"homeassistant.core.StateMachine.get",
|
||||
return_value=State(
|
||||
entity_id="binary_sensor.mock_motion_sensor", state=STATE_ON
|
||||
),
|
||||
return_value=State(entity_id="binary_sensor.mock_motion_sensor", state=STATE_ON),
|
||||
):
|
||||
event_timestamp = now - timedelta(minutes=5)
|
||||
try_condition1 = await send_motion_change_event(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
""" Test the Multiple switch management """
|
||||
import asyncio
|
||||
from unittest.mock import patch, call, ANY
|
||||
from unittest.mock import patch, call, ANY, PropertyMock
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
@@ -84,14 +84,11 @@ async def test_one_switch_cycle(
|
||||
assert mock_is_state.call_count == 1
|
||||
|
||||
# Set temperature to a low level
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) 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,
|
||||
) as mock_device_active, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.call_later",
|
||||
@@ -107,7 +104,8 @@ async def test_one_switch_cycle(
|
||||
# assert mock_heater_on.call_count == 1
|
||||
assert mock_heater_on.call_count == 0
|
||||
# There is no check if active
|
||||
assert mock_device_active.call_count == 0
|
||||
# don't work with PropertyMock
|
||||
# assert mock_device_active.call_count == 0
|
||||
|
||||
# 4 calls dispatched along the cycle
|
||||
assert mock_call_later.call_count == 1
|
||||
@@ -119,14 +117,11 @@ async def test_one_switch_cycle(
|
||||
|
||||
# Set a temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) 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,
|
||||
) as mock_device_active:
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
@@ -141,14 +136,11 @@ async def test_one_switch_cycle(
|
||||
|
||||
# Set another temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active:
|
||||
await send_temperature_change_event(entity, 18.1, event_timestamp)
|
||||
@@ -176,14 +168,11 @@ async def test_one_switch_cycle(
|
||||
|
||||
# Simulate the end of heater on cycle
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active:
|
||||
await entity.underlying_entity(
|
||||
@@ -201,14 +190,11 @@ async def test_one_switch_cycle(
|
||||
|
||||
# Simulate the start of heater on cycle
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
) as mock_device_active:
|
||||
await entity.underlying_entity(
|
||||
@@ -306,14 +292,11 @@ async def test_multiple_switchs(
|
||||
)
|
||||
|
||||
# Set temperature to a low level
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) 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,
|
||||
) as mock_device_active, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.call_later",
|
||||
@@ -329,7 +312,8 @@ async def test_multiple_switchs(
|
||||
# assert mock_heater_on.call_count == 1
|
||||
assert mock_heater_on.call_count == 0
|
||||
# There is no check if active
|
||||
assert mock_device_active.call_count == 0
|
||||
# don't work with PropertyMock
|
||||
# assert mock_device_active.call_count == 0
|
||||
|
||||
# 4 calls dispatched along the cycle
|
||||
assert mock_call_later.call_count == 4
|
||||
@@ -344,14 +328,11 @@ async def test_multiple_switchs(
|
||||
|
||||
# Set a temperature at middle level
|
||||
event_timestamp = now - timedelta(minutes=4)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) 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,
|
||||
) as mock_device_active:
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
@@ -818,7 +799,7 @@ async def test_multiple_switch_power_management(
|
||||
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.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
#fmt: on
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
@@ -523,7 +523,7 @@ async def test_power_management_hvac_on(
|
||||
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.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
# fmt: on
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
@@ -913,12 +913,15 @@ async def test_power_management_turn_off_while_shedding(hass: HomeAssistant, ski
|
||||
|
||||
# 1. Set VTherm to overpowering
|
||||
# Send power max mesurement too low and HVACMode is on and device is active
|
||||
|
||||
#
|
||||
#
|
||||
# fmt:off
|
||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.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.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
# fmt: on
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
@@ -939,7 +942,7 @@ async def test_power_management_turn_off_while_shedding(hass: HomeAssistant, ski
|
||||
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.thermostat_switch.ThermostatOverSwitch.is_device_active", return_value="True"):
|
||||
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
|
||||
# fmt: on
|
||||
now = now + timedelta(seconds=30)
|
||||
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||
|
||||
@@ -2039,16 +2039,13 @@ async def test_bug_66(
|
||||
assert entity.window_state is STATE_UNKNOWN
|
||||
|
||||
# Open the window and let the thermostat shut down
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=True,
|
||||
):
|
||||
await send_temperature_change_event(entity, 15, now)
|
||||
@@ -2067,16 +2064,13 @@ async def test_bug_66(
|
||||
assert entity.window_state == STATE_ON
|
||||
|
||||
# Close the window but too shortly
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=False
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = now + timedelta(minutes=1)
|
||||
@@ -2090,16 +2084,13 @@ async def test_bug_66(
|
||||
assert entity.window_state == STATE_ON
|
||||
|
||||
# Reopen immediatly with sufficient time
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
try_window_condition = await send_window_change_event(
|
||||
@@ -2113,16 +2104,13 @@ async def test_bug_66(
|
||||
assert entity.hvac_mode == HVACMode.OFF
|
||||
|
||||
# Close the window but with sufficient time this time
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
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(
|
||||
) as mock_heater_on, patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off, patch(
|
||||
"homeassistant.helpers.condition.state", return_value=True
|
||||
) as mock_condition, patch(
|
||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
|
||||
new_callable=PropertyMock,
|
||||
return_value=False,
|
||||
):
|
||||
event_timestamp = now + timedelta(minutes=2)
|
||||
|
||||
Reference in New Issue
Block a user