Add temporal filter for calculate_shedding

Add restore overpowering state at startup
This commit is contained in:
Jean-Marc Collin
2025-01-05 10:30:34 +00:00
parent 6bdcecefac
commit 81231f977c
18 changed files with 170 additions and 151 deletions

View File

@@ -27,7 +27,7 @@ class BaseFeatureManager:
"""Initialize the attributes of the FeatureManager""" """Initialize the attributes of the FeatureManager"""
raise NotImplementedError() raise NotImplementedError()
def start_listening(self): async def start_listening(self):
"""Start listening the underlying entity""" """Start listening the underlying entity"""
raise NotImplementedError() raise NotImplementedError()

View File

@@ -53,7 +53,7 @@ from homeassistant.const import (
) )
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData, T, deprecated from .commons import ConfigData, T
from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import
@@ -478,7 +478,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
# start listening for all managers # start listening for all managers
for manager in self._managers: for manager in self._managers:
manager.start_listening() await manager.start_listening()
await self.get_my_previous_state() await self.get_my_previous_state()
@@ -1957,7 +1957,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
def _set_now(self, now: datetime): def _set_now(self, now: datetime):
"""Set the now timestamp. This is only for tests purpose """Set the now timestamp. This is only for tests purpose
This method should be replaced by the vthermAPI equivalent""" This method should be replaced by the vthermAPI equivalent"""
VersatileThermostatAPI.get_vtherm_api(self._hass)._set_now(now) VersatileThermostatAPI.get_vtherm_api(self._hass)._set_now(now) # pylint: disable=protected-access
# @deprecated # @deprecated
@property @property
@@ -1968,8 +1968,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
@property @property
def power_percent(self) -> float | None: def power_percent(self) -> float | None:
"""Get the current on_percent as a percentage value. valid only for Vtherm with a TPI algo""" """Get the current on_percent as a percentage value. valid only for Vtherm with a TPI algo
"""Get the current on_percent value""" Get the current on_percent value"""
if self._prop_algorithm and self._prop_algorithm.on_percent is not None: if self._prop_algorithm and self._prop_algorithm.on_percent is not None:
return round(self._prop_algorithm.on_percent * 100, 0) return round(self._prop_algorithm.on_percent * 100, 0)
else: else:

View File

@@ -4,11 +4,14 @@ import logging
from typing import Any from typing import Any
from functools import cmp_to_key from functools import cmp_to_key
from datetime import timedelta
from homeassistant.const import STATE_OFF from homeassistant.const import STATE_OFF
from homeassistant.core import HomeAssistant, Event, callback from homeassistant.core import HomeAssistant, Event, callback
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change_event, async_track_state_change_event,
EventStateChangedData, EventStateChangedData,
async_call_later,
) )
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components.climate import ( from homeassistant.components.climate import (
@@ -43,6 +46,8 @@ class CentralFeaturePowerManager(BaseFeatureManager):
self._current_power: float = None self._current_power: float = None
self._current_max_power: float = None self._current_max_power: float = None
self._power_temp: float = None self._power_temp: float = None
self._cancel_calculate_shedding_call = None
# Not used now
self._last_shedding_date = None self._last_shedding_date = None
def post_init(self, entry_infos: ConfigData): def post_init(self, entry_infos: ConfigData):
@@ -69,7 +74,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
else: else:
_LOGGER.info("Power management is not fully configured and will be deactivated") _LOGGER.info("Power management is not fully configured and will be deactivated")
def start_listening(self): async def start_listening(self):
"""Start listening the power sensor""" """Start listening the power sensor"""
if not self._is_configured: if not self._is_configured:
return return
@@ -110,39 +115,47 @@ class CentralFeaturePowerManager(BaseFeatureManager):
async def refresh_state(self) -> bool: async def refresh_state(self) -> bool:
"""Tries to get the last state from sensor """Tries to get the last state from sensor
Returns True if a change has been made""" Returns True if a change has been made"""
ret = False
if self._is_configured:
# try to acquire current power and power max
if (
new_state := get_safe_float(self._hass, self._power_sensor_entity_id)
) is not None:
self._current_power = new_state
_LOGGER.debug("Current power have been retrieved: %.3f", self._current_power)
ret = True
# Try to acquire power max async def _calculate_shedding_internal(_):
if ( _LOGGER.debug("Do the shedding calculation")
new_state := get_safe_float( await self.calculate_shedding()
self._hass, self._max_power_sensor_entity_id if self._cancel_calculate_shedding_call:
) self._cancel_calculate_shedding_call()
) is not None: self._cancel_calculate_shedding_call = None
self._current_max_power = new_state
_LOGGER.debug("Current power max have been retrieved: %.3f", self._current_max_power)
ret = True
# check if we need to re-calculate shedding if not self._is_configured:
if ret: return False
now = self._vtherm_api.now
dtimestamp = (
(now - self._last_shedding_date).seconds
if self._last_shedding_date
else 999
)
if dtimestamp >= MIN_DTEMP_SECS:
await self.calculate_shedding()
self._last_shedding_date = now
return ret # Retrieve current power
new_power = get_safe_float(self._hass, self._power_sensor_entity_id)
power_changed = new_power is not None and self._current_power != new_power
if power_changed:
self._current_power = new_power
_LOGGER.debug("New current power has been retrieved: %.3f", self._current_power)
# Retrieve max power
new_max_power = get_safe_float(self._hass, self._max_power_sensor_entity_id)
max_power_changed = new_max_power is not None and self._current_max_power != new_max_power
if max_power_changed:
self._current_max_power = new_max_power
_LOGGER.debug("New current max power has been retrieved: %.3f", self._current_max_power)
# Schedule shedding calculation if there's any change
if power_changed or max_power_changed:
if not self._cancel_calculate_shedding_call:
self._cancel_calculate_shedding_call = async_call_later(self.hass, timedelta(seconds=MIN_DTEMP_SECS), _calculate_shedding_internal)
return True
return False
# For testing purpose only, do an immediate shedding calculation
async def _do_immediate_shedding(self):
"""Do an immmediate shedding calculation if a timer was programmed.
Else, do nothing"""
if self._cancel_calculate_shedding_call:
self._cancel_calculate_shedding_call()
self._cancel_calculate_shedding_call = None
await self.calculate_shedding()
async def calculate_shedding(self): async def calculate_shedding(self):
"""Do the shedding calculation and set/unset VTherm into overpowering state""" """Do the shedding calculation and set/unset VTherm into overpowering state"""
@@ -197,14 +210,15 @@ class CentralFeaturePowerManager(BaseFeatureManager):
) )
_LOGGER.debug("vtherm %s power_consumption_max is %s (device_power=%s, overclimate=%s)", vtherm.name, power_consumption_max, device_power, vtherm.is_over_climate) _LOGGER.debug("vtherm %s power_consumption_max is %s (device_power=%s, overclimate=%s)", vtherm.name, power_consumption_max, device_power, vtherm.is_over_climate)
# if total_power_added + power_consumption_max < available_power or not vtherm.power_manager.is_overpowering_detected:
_LOGGER.info("vtherm %s should not be in overpowering state (power_consumption_max=%.2f)", vtherm.name, power_consumption_max)
# we count the unshedding only if the VTherm was in shedding # or not ... is for initializing the overpowering state if not already done
if vtherm.power_manager.is_overpowering_detected: if total_power_added + power_consumption_max < available_power or not vtherm.power_manager.is_overpowering_detected:
total_power_added += power_consumption_max # we count the unshedding only if the VTherm was in shedding
if vtherm.power_manager.is_overpowering_detected:
_LOGGER.info("vtherm %s should not be in overpowering state (power_consumption_max=%.2f)", vtherm.name, power_consumption_max)
total_power_added += power_consumption_max
await vtherm.power_manager.set_overpowering(False) await vtherm.power_manager.set_overpowering(False)
if total_power_added >= available_power: if total_power_added >= available_power:
_LOGGER.debug("We have found enough vtherm to set to non-overpowering") _LOGGER.debug("We have found enough vtherm to set to non-overpowering")
@@ -212,6 +226,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
_LOGGER.debug("after vtherm %s total_power_added=%s, available_power=%s", vtherm.name, total_power_added, available_power) _LOGGER.debug("after vtherm %s total_power_added=%s, available_power=%s", vtherm.name, total_power_added, available_power)
self._last_shedding_date = self._vtherm_api.now
_LOGGER.debug("-------- End of calculate_shedding") _LOGGER.debug("-------- End of calculate_shedding")
def get_climate_components_entities(self) -> list: def get_climate_components_entities(self) -> list:

View File

@@ -71,7 +71,7 @@ class FeatureAutoStartStopManager(BaseFeatureManager):
) )
@overrides @overrides
def start_listening(self): async def start_listening(self):
"""Start listening the underlying entity""" """Start listening the underlying entity"""
@overrides @overrides

View File

@@ -86,7 +86,7 @@ class FeatureMotionManager(BaseFeatureManager):
self._motion_state = STATE_UNKNOWN self._motion_state = STATE_UNKNOWN
@overrides @overrides
def start_listening(self): async def start_listening(self):
"""Start listening the underlying entity""" """Start listening the underlying entity"""
if self._is_configured: if self._is_configured:
self.stop_listening() self.stop_listening()

View File

@@ -61,19 +61,21 @@ class FeaturePowerManager(BaseFeatureManager):
self._is_configured = False self._is_configured = False
@overrides @overrides
def start_listening(self): async def start_listening(self):
"""Start listening the underlying entity. There is nothing to listen""" """Start listening the underlying entity. There is nothing to listen"""
central_power_configuration = ( central_power_configuration = (
VersatileThermostatAPI.get_vtherm_api().central_power_manager.is_configured VersatileThermostatAPI.get_vtherm_api().central_power_manager.is_configured
) )
if ( if self._use_power_feature and self._device_power and central_power_configuration:
self._use_power_feature
and self._device_power
and central_power_configuration
):
self._is_configured = True self._is_configured = True
self._overpowering_state = STATE_UNKNOWN # Try to restore _overpowering_state from previous state
old_state = await self._vtherm.async_get_last_state()
self._overpowering_state = (
old_state.attributes.get("overpowering_state", STATE_UNKNOWN)
if old_state and old_state.attributes and old_state.attributes in (STATE_OFF, STATE_ON)
else STATE_UNKNOWN
)
else: else:
if self._use_power_feature: if self._use_power_feature:
if not central_power_configuration: if not central_power_configuration:

View File

@@ -67,7 +67,7 @@ class FeaturePresenceManager(BaseFeatureManager):
self._presence_state = STATE_UNKNOWN self._presence_state = STATE_UNKNOWN
@overrides @overrides
def start_listening(self): async def start_listening(self):
"""Start listening the underlying entity""" """Start listening the underlying entity"""
if self._is_configured: if self._is_configured:
self.stop_listening() self.stop_listening()

View File

@@ -70,7 +70,7 @@ class FeatureSafetyManager(BaseFeatureManager):
self._is_configured = True self._is_configured = True
@overrides @overrides
def start_listening(self): async def start_listening(self):
"""Start listening the underlying entity""" """Start listening the underlying entity"""
@overrides @overrides

View File

@@ -124,7 +124,7 @@ class FeatureWindowManager(BaseFeatureManager):
self._window_state = STATE_UNKNOWN self._window_state = STATE_UNKNOWN
@overrides @overrides
def start_listening(self): async def start_listening(self):
"""Start listening the underlying entity""" """Start listening the underlying entity"""
if self._is_configured: if self._is_configured:
self.stop_listening() self.stop_listening()

View File

@@ -26,23 +26,21 @@ _LOGGER = logging.getLogger(__name__)
class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]): class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
"""Representation of a base class for a Versatile Thermostat over a switch.""" """Representation of a base class for a Versatile Thermostat over a switch."""
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
BaseThermostat._entity_component_unrecorded_attributes.union( frozenset(
frozenset( {
{ "is_over_switch",
"is_over_switch", "is_inversed",
"is_inversed", "underlying_entities",
"underlying_entities", "on_time_sec",
"on_time_sec", "off_time_sec",
"off_time_sec", "cycle_min",
"cycle_min", "function",
"function", "tpi_coef_int",
"tpi_coef_int", "tpi_coef_ext",
"tpi_coef_ext", "power_percent",
"power_percent", "calculated_on_percent",
"calculated_on_percent", }
}
)
) )
) )

View File

@@ -188,7 +188,7 @@ class VersatileThermostatAPI(dict):
# start listening for the central power manager if not only one vtherm reload # start listening for the central power manager if not only one vtherm reload
if not entry_id: if not entry_id:
self.central_power_manager.start_listening() await self.central_power_manager.start_listening()
async def init_vtherm_preset_with_central(self): async def init_vtherm_preset_with_central(self):
"""Init all VTherm presets when the VTherm uses central temperature""" """Init all VTherm presets when the VTherm uses central temperature"""

View File

@@ -751,6 +751,7 @@ async def send_power_change_event(entity: BaseThermostat, new_power, date, sleep
) )
vtherm_api = VersatileThermostatAPI.get_vtherm_api() vtherm_api = VersatileThermostatAPI.get_vtherm_api()
await vtherm_api.central_power_manager._power_sensor_changed(power_event) await vtherm_api.central_power_manager._power_sensor_changed(power_event)
await vtherm_api.central_power_manager._do_immediate_shedding()
if sleep: if sleep:
await entity.hass.async_block_till_done() await entity.hass.async_block_till_done()
@@ -778,6 +779,7 @@ async def send_max_power_change_event(
) )
vtherm_api = VersatileThermostatAPI.get_vtherm_api() vtherm_api = VersatileThermostatAPI.get_vtherm_api()
await vtherm_api.central_power_manager._max_power_sensor_changed(power_event) await vtherm_api.central_power_manager._max_power_sensor_changed(power_event)
await vtherm_api.central_power_manager._do_immediate_shedding()
if sleep: if sleep:
await entity.hass.async_block_till_done() await entity.hass.async_block_till_done()

View File

@@ -192,13 +192,13 @@ async def test_overpowering_binary_sensors(
assert overpowering_binary_sensor.state == STATE_ON assert overpowering_binary_sensor.state == STATE_ON
# set max power to a low value # set max power to a low value
side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 201)) side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 251))
# fmt:off # fmt:off
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()): with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()):
# fmt: on # fmt: on
now = now + timedelta(seconds=30) now = now + timedelta(seconds=30)
VersatileThermostatAPI.get_vtherm_api()._set_now(now) VersatileThermostatAPI.get_vtherm_api()._set_now(now)
await send_max_power_change_event(entity, 201, now) await send_max_power_change_event(entity, 251, now)
assert entity.power_manager.is_overpowering_detected is False assert entity.power_manager.is_overpowering_detected is False
assert entity.power_manager.overpowering_state is STATE_OFF assert entity.power_manager.overpowering_state is STATE_OFF
# Simulate the event reception # Simulate the event reception

View File

@@ -59,7 +59,7 @@ async def test_central_power_manager_init(
assert central_power_manager.power_temperature == power_temp assert central_power_manager.power_temperature == power_temp
# 3. start listening # 3. start listening
central_power_manager.start_listening() await central_power_manager.start_listening()
assert len(central_power_manager._active_listener) == (2 if is_configured else 0) assert len(central_power_manager._active_listener) == (2 if is_configured else 0)
# 4. stop listening # 4. stop listening
@@ -311,6 +311,54 @@ async def test_central_power_manageer_find_vtherms(
# init vtherm1 to False # init vtherm1 to False
{"vtherm3": False, "vtherm2": False, "vtherm1": False}, {"vtherm3": False, "vtherm2": False, "vtherm1": False},
), ),
# Un-shedding only (will be taken in reverse order)
(
1000,
2000,
[
# should be not unshedded (too much power will be added)
{
"name": "vtherm1",
"device_power": 800,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# already stay unshedded cause already unshedded
{
"name": "vtherm2",
"device_power": 100,
"is_device_active": True,
"is_over_climate": True,
"is_overpowering_detected": False,
"overpowering_state": STATE_OFF,
},
# should be unshedded
{
"name": "vtherm3",
"device_power": 200,
"is_device_active": False,
"is_over_climate": True,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# should be unshedded
{
"name": "vtherm4",
"device_power": 300,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
],
{"vtherm4": False, "vtherm3": False},
),
# Shedding # Shedding
( (
2000, 2000,
@@ -391,65 +439,6 @@ async def test_central_power_manageer_find_vtherms(
], ],
{"vtherm1": True, "vtherm2": True, "vtherm3": True, "vtherm6": True}, {"vtherm1": True, "vtherm2": True, "vtherm3": True, "vtherm6": True},
), ),
# Un-shedding only (will be taken in reverse order)
(
1000,
2000,
[
# should be not unshedded (we have enough)
{
"name": "vtherm0",
"device_power": 800,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# should be unshedded
{
"name": "vtherm1",
"device_power": 800,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# already stay unshedded cause already unshedded
{
"name": "vtherm2",
"device_power": 1100,
"is_device_active": True,
"is_over_climate": True,
"is_overpowering_detected": False,
"overpowering_state": STATE_OFF,
},
# should be unshedded
{
"name": "vtherm3",
"device_power": 200,
"is_device_active": False,
"is_over_climate": True,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
# should be unshedded
{
"name": "vtherm4",
"device_power": 300,
"is_device_active": False,
"is_over_climate": False,
"nb_underlying_entities": 1,
"on_percent": 1,
"is_overpowering_detected": True,
"overpowering_state": STATE_ON,
},
],
{"vtherm4": False, "vtherm3": False, "vtherm1": False},
),
], ],
) )
# @pytest.mark.skip # @pytest.mark.skip
@@ -553,7 +542,7 @@ async def test_central_power_manager_power_event(
assert central_power_manager.power_temperature == 13 assert central_power_manager.power_temperature == 13
# 3. start listening (not really useful but don't eat bread) # 3. start listening (not really useful but don't eat bread)
central_power_manager.start_listening() await central_power_manager.start_listening()
assert len(central_power_manager._active_listener) == 2 assert len(central_power_manager._active_listener) == 2
now: datetime = NowClass.get_now(hass) now: datetime = NowClass.get_now(hass)
@@ -582,6 +571,9 @@ async def test_central_power_manager_power_event(
"old_state": State("sensor.power_entity_id", STATE_UNAVAILABLE), "old_state": State("sensor.power_entity_id", STATE_UNAVAILABLE),
})) }))
if nb_call > 0:
await central_power_manager._do_immediate_shedding()
expected_power = power if isinstance(power, (int, float)) else -999 expected_power = power if isinstance(power, (int, float)) else -999
assert central_power_manager.current_power == expected_power assert central_power_manager.current_power == expected_power
assert mock_calculate_shedding.call_count == nb_call assert mock_calculate_shedding.call_count == nb_call
@@ -603,8 +595,11 @@ async def test_central_power_manager_power_event(
"old_state": State("sensor.power_entity_id", STATE_UNAVAILABLE), "old_state": State("sensor.power_entity_id", STATE_UNAVAILABLE),
})) }))
if nb_call > 0:
await central_power_manager._do_immediate_shedding()
assert central_power_manager.current_power == expected_power assert central_power_manager.current_power == expected_power
assert mock_calculate_shedding.call_count == (nb_call if dsecs >= 20 else 0) assert mock_calculate_shedding.call_count == nb_call
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -645,7 +640,7 @@ async def test_central_power_manager_max_power_event(
assert central_power_manager.power_temperature == 13 assert central_power_manager.power_temperature == 13
# 3. start listening (not really useful but don't eat bread) # 3. start listening (not really useful but don't eat bread)
central_power_manager.start_listening() await central_power_manager.start_listening()
assert len(central_power_manager._active_listener) == 2 assert len(central_power_manager._active_listener) == 2
now: datetime = NowClass.get_now(hass) now: datetime = NowClass.get_now(hass)
@@ -676,6 +671,9 @@ async def test_central_power_manager_max_power_event(
"old_state": State("sensor.max_power_entity_id", STATE_UNAVAILABLE), "old_state": State("sensor.max_power_entity_id", STATE_UNAVAILABLE),
})) }))
if nb_call > 0:
await central_power_manager._do_immediate_shedding()
expected_power = max_power if isinstance(max_power, (int, float)) else -999 expected_power = max_power if isinstance(max_power, (int, float)) else -999
assert central_power_manager.current_max_power == expected_power assert central_power_manager.current_max_power == expected_power
assert mock_calculate_shedding.call_count == nb_call assert mock_calculate_shedding.call_count == nb_call
@@ -697,5 +695,8 @@ async def test_central_power_manager_max_power_event(
"old_state": State("sensor.max_power_entity_id", STATE_UNAVAILABLE), "old_state": State("sensor.max_power_entity_id", STATE_UNAVAILABLE),
})) }))
if nb_call > 0:
await central_power_manager._do_immediate_shedding()
assert central_power_manager.current_max_power == expected_power assert central_power_manager.current_max_power == expected_power
assert mock_calculate_shedding.call_count == (nb_call if dsecs >= 20 else 0) assert mock_calculate_shedding.call_count == nb_call

View File

@@ -90,7 +90,7 @@ async def test_motion_feature_manager_refresh(
assert custom_attributes["motion_off_delay_sec"] == 30 assert custom_attributes["motion_off_delay_sec"] == 30
# 3. start listening # 3. start listening
motion_manager.start_listening() await motion_manager.start_listening()
assert motion_manager.is_configured is True assert motion_manager.is_configured is True
assert motion_manager.motion_state == STATE_UNKNOWN assert motion_manager.motion_state == STATE_UNKNOWN
assert motion_manager.is_motion_detected is False assert motion_manager.is_motion_detected is False
@@ -198,7 +198,7 @@ async def test_motion_feature_manager_event(
CONF_NO_MOTION_PRESET: PRESET_ECO, CONF_NO_MOTION_PRESET: PRESET_ECO,
} }
) )
motion_manager.start_listening() await motion_manager.start_listening()
# 2. test _motion_sensor_changed with the parametrized # 2. test _motion_sensor_changed with the parametrized
# fmt: off # fmt: off

View File

@@ -98,7 +98,7 @@ async def test_power_feature_manager(
} }
) )
power_manager.start_listening() await power_manager.start_listening()
assert power_manager.is_configured is True assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN assert power_manager.overpowering_state == STATE_UNKNOWN
@@ -117,7 +117,7 @@ async def test_power_feature_manager(
assert custom_attributes["current_max_power"] is None assert custom_attributes["current_max_power"] is None
# 3. start listening # 3. start listening
power_manager.start_listening() await power_manager.start_listening()
assert power_manager.is_configured is True assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN assert power_manager.overpowering_state == STATE_UNKNOWN
@@ -199,7 +199,7 @@ async def test_power_feature_manager_set_overpowering(
} }
) )
power_manager.start_listening() await power_manager.start_listening()
assert power_manager.is_configured is True assert power_manager.is_configured is True
assert power_manager.overpowering_state == STATE_UNKNOWN assert power_manager.overpowering_state == STATE_UNKNOWN
@@ -557,6 +557,7 @@ async def test_power_management_hvac_on(
# Send power mesurement low to unset power preset # Send power mesurement low to unset power preset
side_effects.add_or_update_side_effect("sensor.the_power_sensor", State("sensor.the_power_sensor", 48)) side_effects.add_or_update_side_effect("sensor.the_power_sensor", State("sensor.the_power_sensor", 48))
side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 149))
# fmt:off # fmt:off
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \ with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \ patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
@@ -583,7 +584,7 @@ async def test_power_management_hvac_on(
"type": "end", "type": "end",
"current_power": 48, "current_power": 48,
"device_power": 100, "device_power": 100,
"current_max_power": 49, "current_max_power": 149,
}, },
), ),
], ],

View File

@@ -75,7 +75,7 @@ async def test_presence_feature_manager(
assert custom_attributes["is_presence_configured"] is True assert custom_attributes["is_presence_configured"] is True
# 3. start listening # 3. start listening
presence_manager.start_listening() await presence_manager.start_listening()
assert presence_manager.is_configured is True assert presence_manager.is_configured is True
assert presence_manager.presence_state == STATE_UNKNOWN assert presence_manager.presence_state == STATE_UNKNOWN
assert presence_manager.is_absence_detected is False assert presence_manager.is_absence_detected is False

View File

@@ -170,7 +170,7 @@ async def test_window_feature_manager_refresh_sensor_action_turn_off(
) )
# 3. start listening # 3. start listening
window_manager.start_listening() await window_manager.start_listening()
assert window_manager.is_configured is True assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE assert window_manager.window_auto_state == STATE_UNAVAILABLE
@@ -288,7 +288,7 @@ async def test_window_feature_manager_refresh_sensor_action_frost_only(
) )
# 3. start listening # 3. start listening
window_manager.start_listening() await window_manager.start_listening()
assert window_manager.is_configured is True assert window_manager.is_configured is True
assert window_manager.window_state == STATE_UNKNOWN assert window_manager.window_state == STATE_UNKNOWN
assert window_manager.window_auto_state == STATE_UNAVAILABLE assert window_manager.window_auto_state == STATE_UNAVAILABLE
@@ -408,7 +408,7 @@ async def test_window_feature_manager_sensor_event_action_turn_off(
) )
# 3. start listening # 3. start listening
window_manager.start_listening() await window_manager.start_listening()
assert len(window_manager._active_listener) == 1 assert len(window_manager._active_listener) == 1
# 4. test refresh with the parametrized # 4. test refresh with the parametrized
@@ -535,7 +535,7 @@ async def test_window_feature_manager_event_sensor_action_frost_only(
) )
# 3. start listening # 3. start listening
window_manager.start_listening() await window_manager.start_listening()
# 4. test refresh with the parametrized # 4. test refresh with the parametrized
# fmt:off # fmt:off
@@ -660,7 +660,7 @@ async def test_window_feature_manager_window_auto(
} }
) )
assert window_manager.is_window_auto_configured is True assert window_manager.is_window_auto_configured is True
window_manager.start_listening() await window_manager.start_listening()
# 2. Call manage window auto # 2. Call manage window auto
tz = get_tz(hass) # pylint: disable=invalid-name tz = get_tz(hass) # pylint: disable=invalid-name