All tests not ok
This commit is contained in:
@@ -38,6 +38,10 @@ class BaseFeatureManager:
|
|||||||
|
|
||||||
self._active_listener = []
|
self._active_listener = []
|
||||||
|
|
||||||
|
async def refresh_state(self):
|
||||||
|
"""Refresh the state and return True if a change have been made"""
|
||||||
|
return False
|
||||||
|
|
||||||
def add_listener(self, func: CALLBACK_TYPE) -> None:
|
def add_listener(self, func: CALLBACK_TYPE) -> None:
|
||||||
"""Add a listener to the list of active listener"""
|
"""Add a listener to the list of active listener"""
|
||||||
self._active_listener.append(func)
|
self._active_listener.append(func)
|
||||||
|
|||||||
@@ -50,11 +50,10 @@ from homeassistant.const import (
|
|||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
STATE_ON,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
from .commons import ConfigData, T, deprecated
|
||||||
|
|
||||||
from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
@@ -1611,10 +1610,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
|
|
||||||
# Check overpowering condition
|
# Check overpowering condition
|
||||||
# Not necessary for switch because each switch is checking at startup
|
# Not necessary for switch because each switch is checking at startup
|
||||||
overpowering = await self._power_manager.check_overpowering()
|
# overpowering is now centralized
|
||||||
if overpowering == STATE_ON:
|
# overpowering = await self._power_manager.check_overpowering()
|
||||||
_LOGGER.debug("%s - End of cycle (overpowering)", self)
|
# if overpowering == STATE_ON:
|
||||||
return True
|
# _LOGGER.debug("%s - End of cycle (overpowering)", self)
|
||||||
|
# return True
|
||||||
|
|
||||||
safety: bool = await self._safety_manager.refresh_state()
|
safety: bool = await self._safety_manager.refresh_state()
|
||||||
if safety and self.is_over_climate:
|
if safety and self.is_over_climate:
|
||||||
@@ -1957,14 +1957,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
return self._presets.get(preset, None) is not None
|
return self._presets.get(preset, None) is not None
|
||||||
|
|
||||||
# For testing purpose
|
# For testing purpose
|
||||||
@DeprecationWarning
|
# @deprecated
|
||||||
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)
|
||||||
|
|
||||||
|
# @deprecated
|
||||||
@property
|
@property
|
||||||
@DeprecationWarning
|
|
||||||
def now(self) -> datetime:
|
def now(self) -> datetime:
|
||||||
"""Get now. The local datetime or the overloaded _set_now date
|
"""Get now. The local datetime or the overloaded _set_now date
|
||||||
This method should be replaced by the vthermAPI equivalent"""
|
This method should be replaced by the vthermAPI equivalent"""
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from functools import cmp_to_key
|
from functools import cmp_to_key
|
||||||
|
|
||||||
from homeassistant.const import (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
)
|
|
||||||
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,
|
||||||
@@ -106,17 +102,17 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
"""Handle power changes."""
|
"""Handle power changes."""
|
||||||
_LOGGER.debug("Thermostat %s - Receive new Power event", self)
|
_LOGGER.debug("Thermostat %s - Receive new Power event", self)
|
||||||
_LOGGER.debug(event)
|
_LOGGER.debug(event)
|
||||||
self.refresh_state()
|
await self.refresh_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
async def _max_power_sensor_changed(self, event: Event[EventStateChangedData]):
|
async def _max_power_sensor_changed(self, event: Event[EventStateChangedData]):
|
||||||
"""Handle power max changes."""
|
"""Handle power max changes."""
|
||||||
_LOGGER.debug("Thermostat %s - Receive new Power Max event", self.name)
|
_LOGGER.debug("Thermostat %s - Receive new Power Max event", self.name)
|
||||||
_LOGGER.debug(event)
|
_LOGGER.debug(event)
|
||||||
self.refresh_state()
|
await self.refresh_state()
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
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
|
ret = False
|
||||||
@@ -156,7 +152,7 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
else 999
|
else 999
|
||||||
)
|
)
|
||||||
if dtimestamp >= MIN_DTEMP_SECS:
|
if dtimestamp >= MIN_DTEMP_SECS:
|
||||||
self.calculate_shedding()
|
await self.calculate_shedding()
|
||||||
self._last_shedding_date = now
|
self._last_shedding_date = now
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@@ -207,16 +203,19 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
if not vtherm.power_manager.is_overpowering_detected:
|
if not vtherm.power_manager.is_overpowering_detected:
|
||||||
# To force all others vtherms to be in overpowering
|
# To force all others vtherms to be in overpowering
|
||||||
force_overpowering = True
|
force_overpowering = True
|
||||||
await vtherm.power_manager.set_overpowering(True)
|
await vtherm.power_manager.set_overpowering(
|
||||||
|
True, power_consumption_max
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
total_affected_power += power_consumption_max
|
total_affected_power += power_consumption_max
|
||||||
if vtherm.power_manager.is_overpowering_detected:
|
# Always set to false
|
||||||
_LOGGER.debug(
|
# if vtherm.power_manager.is_overpowering_detected:
|
||||||
"%s - vtherm %s should not be in overpowering state",
|
_LOGGER.debug(
|
||||||
self,
|
"%s - vtherm %s should not be in overpowering state",
|
||||||
vtherm.name,
|
self,
|
||||||
)
|
vtherm.name,
|
||||||
await vtherm.power_manager.set_overpowering(False)
|
)
|
||||||
|
await vtherm.power_manager.set_overpowering(False)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - after vtherm %s total_affected_power=%s, available_power=%s",
|
"%s - after vtherm %s total_affected_power=%s, available_power=%s",
|
||||||
@@ -290,5 +289,15 @@ class CentralFeaturePowerManager(BaseFeatureManager):
|
|||||||
"""Return the power temperature"""
|
"""Return the power temperature"""
|
||||||
return self._power_temp
|
return self._power_temp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def power_sensor_entity_id(self) -> float | None:
|
||||||
|
"""Return the power sensor entity id"""
|
||||||
|
return self._power_sensor_entity_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_power_sensor_entity_id(self) -> float | None:
|
||||||
|
"""Return the max power sensor entity id"""
|
||||||
|
return self._max_power_sensor_entity_id
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "CentralPowerManager"
|
return "CentralPowerManager"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# pylint: disable=line-too-long
|
# pylint: disable=line-too-long
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import warnings
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
from typing import Any, TypeVar
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
@@ -132,3 +133,20 @@ def check_and_extract_service_configuration(service_config) -> dict:
|
|||||||
"check_and_extract_service_configuration(%s) gives '%s'", service_config, ret
|
"check_and_extract_service_configuration(%s) gives '%s'", service_config, ret
|
||||||
)
|
)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def deprecated(message):
|
||||||
|
"""A decorator to indicate that the method/attribut is deprecated"""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
warnings.warn(
|
||||||
|
f"{func.__name__} is deprecated: {message}",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|||||||
@@ -339,6 +339,12 @@ STEP_CENTRAL_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
STEP_NON_CENTRAL_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_PRESET_POWER, default="13"): vol.Coerce(float),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
STEP_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
STEP_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||||
{
|
{
|
||||||
vol.Required(CONF_USE_POWER_CENTRAL_CONFIG, default=True): cv.boolean,
|
vol.Required(CONF_USE_POWER_CENTRAL_CONFIG, default=True): cv.boolean,
|
||||||
|
|||||||
@@ -12,22 +12,15 @@ from homeassistant.const import (
|
|||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
callback,
|
|
||||||
Event,
|
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.event import (
|
|
||||||
async_track_state_change_event,
|
|
||||||
EventStateChangedData,
|
|
||||||
)
|
|
||||||
from homeassistant.components.climate import HVACMode
|
|
||||||
|
|
||||||
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
from .commons import ConfigData
|
from .commons import ConfigData
|
||||||
|
|
||||||
from .base_manager import BaseFeatureManager
|
from .base_manager import BaseFeatureManager
|
||||||
|
from .vtherm_api import VersatileThermostatAPI
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -50,10 +43,6 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
def __init__(self, vtherm: Any, hass: HomeAssistant):
|
def __init__(self, vtherm: Any, hass: HomeAssistant):
|
||||||
"""Init of a featureManager"""
|
"""Init of a featureManager"""
|
||||||
super().__init__(vtherm, hass)
|
super().__init__(vtherm, hass)
|
||||||
self._power_sensor_entity_id = None
|
|
||||||
self._max_power_sensor_entity_id = None
|
|
||||||
self._current_power = None
|
|
||||||
self._current_max_power = None
|
|
||||||
self._power_temp = None
|
self._power_temp = None
|
||||||
self._overpowering_state = STATE_UNAVAILABLE
|
self._overpowering_state = STATE_UNAVAILABLE
|
||||||
self._is_configured: bool = False
|
self._is_configured: bool = False
|
||||||
@@ -64,20 +53,11 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
"""Reinit of the manager"""
|
"""Reinit of the manager"""
|
||||||
|
|
||||||
# Power management
|
# Power management
|
||||||
self._power_sensor_entity_id = entry_infos.get(CONF_POWER_SENSOR)
|
|
||||||
self._max_power_sensor_entity_id = entry_infos.get(CONF_MAX_POWER_SENSOR)
|
|
||||||
self._power_temp = entry_infos.get(CONF_PRESET_POWER)
|
self._power_temp = entry_infos.get(CONF_PRESET_POWER)
|
||||||
|
|
||||||
self._device_power = entry_infos.get(CONF_DEVICE_POWER) or 0
|
self._device_power = entry_infos.get(CONF_DEVICE_POWER) or 0
|
||||||
self._is_configured = False
|
self._is_configured = False
|
||||||
self._current_power = None
|
if entry_infos.get(CONF_USE_POWER_FEATURE, False) and self._device_power:
|
||||||
self._current_max_power = None
|
|
||||||
if (
|
|
||||||
entry_infos.get(CONF_USE_POWER_FEATURE, False)
|
|
||||||
and self._max_power_sensor_entity_id
|
|
||||||
and self._power_sensor_entity_id
|
|
||||||
and self._device_power
|
|
||||||
):
|
|
||||||
self._is_configured = True
|
self._is_configured = True
|
||||||
self._overpowering_state = STATE_UNKNOWN
|
self._overpowering_state = STATE_UNKNOWN
|
||||||
else:
|
else:
|
||||||
@@ -85,159 +65,54 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def start_listening(self):
|
def start_listening(self):
|
||||||
"""Start listening the underlying entity"""
|
"""Start listening the underlying entity. There is nothing to listen"""
|
||||||
if self._is_configured:
|
|
||||||
self.stop_listening()
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.add_listener(
|
|
||||||
async_track_state_change_event(
|
|
||||||
self.hass,
|
|
||||||
[self._power_sensor_entity_id],
|
|
||||||
self._async_power_sensor_changed,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.add_listener(
|
|
||||||
async_track_state_change_event(
|
|
||||||
self.hass,
|
|
||||||
[self._max_power_sensor_entity_id],
|
|
||||||
self._async_max_power_sensor_changed,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@overrides
|
|
||||||
async def refresh_state(self) -> bool:
|
|
||||||
"""Tries to get the last state from sensor
|
|
||||||
Returns True if a change has been made"""
|
|
||||||
ret = False
|
|
||||||
if self._is_configured:
|
|
||||||
# try to acquire current power and power max
|
|
||||||
current_power_state = self.hass.states.get(self._power_sensor_entity_id)
|
|
||||||
if current_power_state and current_power_state.state not in (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
):
|
|
||||||
self._current_power = float(current_power_state.state)
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - Current power have been retrieved: %.3f",
|
|
||||||
self,
|
|
||||||
self._current_power,
|
|
||||||
)
|
|
||||||
ret = True
|
|
||||||
|
|
||||||
# Try to acquire power max
|
|
||||||
current_power_max_state = self.hass.states.get(
|
|
||||||
self._max_power_sensor_entity_id
|
|
||||||
)
|
|
||||||
if current_power_max_state and current_power_max_state.state not in (
|
|
||||||
STATE_UNAVAILABLE,
|
|
||||||
STATE_UNKNOWN,
|
|
||||||
):
|
|
||||||
self._current_max_power = float(current_power_max_state.state)
|
|
||||||
_LOGGER.debug(
|
|
||||||
"%s - Current power max have been retrieved: %.3f",
|
|
||||||
self,
|
|
||||||
self._current_max_power,
|
|
||||||
)
|
|
||||||
ret = True
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
@callback
|
|
||||||
async def _async_power_sensor_changed(self, event: Event[EventStateChangedData]):
|
|
||||||
"""Handle power changes."""
|
|
||||||
_LOGGER.debug("Thermostat %s - Receive new Power event", self)
|
|
||||||
_LOGGER.debug(event)
|
|
||||||
new_state = event.data.get("new_state")
|
|
||||||
old_state = event.data.get("old_state")
|
|
||||||
if (
|
|
||||||
new_state is None
|
|
||||||
or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN)
|
|
||||||
or (old_state is not None and new_state.state == old_state.state)
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
current_power = float(new_state.state)
|
|
||||||
if math.isnan(current_power) or math.isinf(current_power):
|
|
||||||
raise ValueError(f"Sensor has illegal state {new_state.state}")
|
|
||||||
self._current_power = current_power
|
|
||||||
|
|
||||||
if self._vtherm.preset_mode == PRESET_POWER:
|
|
||||||
await self._vtherm.async_control_heating()
|
|
||||||
|
|
||||||
except ValueError as ex:
|
|
||||||
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
async def _async_max_power_sensor_changed(
|
|
||||||
self, event: Event[EventStateChangedData]
|
|
||||||
):
|
|
||||||
"""Handle power max changes."""
|
|
||||||
_LOGGER.debug("Thermostat %s - Receive new Power Max event", self.name)
|
|
||||||
_LOGGER.debug(event)
|
|
||||||
new_state = event.data.get("new_state")
|
|
||||||
old_state = event.data.get("old_state")
|
|
||||||
if (
|
|
||||||
new_state is None
|
|
||||||
or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN)
|
|
||||||
or (old_state is not None and new_state.state == old_state.state)
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
current_power_max = float(new_state.state)
|
|
||||||
if math.isnan(current_power_max) or math.isinf(current_power_max):
|
|
||||||
raise ValueError(f"Sensor has illegal state {new_state.state}")
|
|
||||||
self._current_max_power = current_power_max
|
|
||||||
if self._vtherm.preset_mode == PRESET_POWER:
|
|
||||||
await self._vtherm.async_control_heating()
|
|
||||||
|
|
||||||
except ValueError as ex:
|
|
||||||
_LOGGER.error("Unable to update current_power from sensor: %s", ex)
|
|
||||||
|
|
||||||
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
|
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
|
||||||
"""Add some custom attributes"""
|
"""Add some custom attributes"""
|
||||||
|
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
|
||||||
extra_state_attributes.update(
|
extra_state_attributes.update(
|
||||||
{
|
{
|
||||||
"power_sensor_entity_id": self._power_sensor_entity_id,
|
"power_sensor_entity_id": vtherm_api.central_power_manager.power_sensor_entity_id,
|
||||||
"max_power_sensor_entity_id": self._max_power_sensor_entity_id,
|
"max_power_sensor_entity_id": vtherm_api.central_power_manager.max_power_sensor_entity_id,
|
||||||
"overpowering_state": self._overpowering_state,
|
"overpowering_state": self._overpowering_state,
|
||||||
"is_power_configured": self._is_configured,
|
"is_power_configured": self._is_configured,
|
||||||
"device_power": self._device_power,
|
"device_power": self._device_power,
|
||||||
"power_temp": self._power_temp,
|
"power_temp": self._power_temp,
|
||||||
"current_power": self._current_power,
|
"current_power": vtherm_api.central_power_manager.current_power,
|
||||||
"current_max_power": self._current_max_power,
|
"current_max_power": vtherm_api.central_power_manager.current_max_power,
|
||||||
"mean_cycle_power": self.mean_cycle_power,
|
"mean_cycle_power": self.mean_cycle_power,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def check_overpowering(self) -> bool:
|
async def check_power_available(self) -> bool:
|
||||||
"""Check the overpowering condition
|
"""Check if the Vtherm can be started considering overpowering.
|
||||||
Turn the preset_mode of the heater to 'power' if power conditions are exceeded
|
Returns True if no overpowering conditions are found
|
||||||
Returns True if overpowering is 'on'
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self._is_configured:
|
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
|
||||||
return False
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self._current_power is None
|
not self._is_configured
|
||||||
|
or not vtherm_api.central_power_manager.is_configured
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
current_power = vtherm_api.central_power_manager.current_power
|
||||||
|
current_max_power = vtherm_api.central_power_manager.current_max_power
|
||||||
|
if (
|
||||||
|
current_power is None
|
||||||
|
or current_max_power is None
|
||||||
or self._device_power is None
|
or self._device_power is None
|
||||||
or self._current_max_power is None
|
|
||||||
):
|
):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s - power not valued. check_overpowering not available", self
|
"%s - power not valued. check_power_available not available", self
|
||||||
)
|
)
|
||||||
return False
|
return True
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - overpowering check: power=%.3f, max_power=%.3f heater power=%.3f",
|
"%s - overpowering check: power=%.3f, max_power=%.3f heater power=%.3f",
|
||||||
self,
|
self,
|
||||||
self._current_power,
|
current_power,
|
||||||
self._current_max_power,
|
current_max_power,
|
||||||
self._device_power,
|
self._device_power,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -253,18 +128,35 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
self._device_power * self._vtherm.proportional_algorithm.on_percent,
|
self._device_power * self._vtherm.proportional_algorithm.on_percent,
|
||||||
)
|
)
|
||||||
|
|
||||||
ret = (self._current_power + power_consumption_max) >= self._current_max_power
|
ret = (current_power + power_consumption_max) < current_max_power
|
||||||
if (
|
if not ret:
|
||||||
self._overpowering_state == STATE_OFF
|
_LOGGER.info(
|
||||||
and ret
|
"%s - there is not enough power available power=%.3f, max_power=%.3f heater power=%.3f",
|
||||||
and self._vtherm.hvac_mode != HVACMode.OFF
|
self,
|
||||||
):
|
current_power,
|
||||||
|
current_max_power,
|
||||||
|
self._device_power,
|
||||||
|
)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
async def set_overpowering(self, overpowering: bool, power_consumption_max=0):
|
||||||
|
"""Force the overpowering state for the VTherm"""
|
||||||
|
|
||||||
|
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
|
||||||
|
current_power = vtherm_api.central_power_manager.current_power
|
||||||
|
current_max_power = vtherm_api.central_power_manager.current_max_power
|
||||||
|
|
||||||
|
if overpowering and not self.is_overpowering_detected:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s - overpowering is detected. Heater preset will be set to 'power'",
|
"%s - overpowering is detected. Heater preset will be set to 'power'",
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._overpowering_state = STATE_ON
|
||||||
|
|
||||||
if self._vtherm.is_over_climate:
|
if self._vtherm.is_over_climate:
|
||||||
self._vtherm.save_hvac_mode()
|
self._vtherm.save_hvac_mode()
|
||||||
|
|
||||||
self._vtherm.save_preset_mode()
|
self._vtherm.save_preset_mode()
|
||||||
await self._vtherm.async_underlying_entity_turn_off()
|
await self._vtherm.async_underlying_entity_turn_off()
|
||||||
await self._vtherm.async_set_preset_mode_internal(PRESET_POWER)
|
await self._vtherm.async_set_preset_mode_internal(PRESET_POWER)
|
||||||
@@ -272,24 +164,20 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
EventType.POWER_EVENT,
|
EventType.POWER_EVENT,
|
||||||
{
|
{
|
||||||
"type": "start",
|
"type": "start",
|
||||||
"current_power": self._current_power,
|
"current_power": current_power,
|
||||||
"device_power": self._device_power,
|
"device_power": self._device_power,
|
||||||
"current_max_power": self._current_max_power,
|
"current_max_power": current_max_power,
|
||||||
"current_power_consumption": power_consumption_max,
|
"current_power_consumption": power_consumption_max,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
elif not overpowering and self.is_overpowering_detected:
|
||||||
# Check if we need to remove the POWER preset
|
|
||||||
if (
|
|
||||||
self._overpowering_state == STATE_ON
|
|
||||||
and not ret
|
|
||||||
and self._vtherm.preset_mode == PRESET_POWER
|
|
||||||
):
|
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s - end of overpowering is detected. Heater preset will be restored to '%s'",
|
"%s - end of overpowering is detected. Heater preset will be restored to '%s'",
|
||||||
self,
|
self,
|
||||||
self._vtherm._saved_preset_mode, # pylint: disable=protected-access
|
self._vtherm._saved_preset_mode, # pylint: disable=protected-access
|
||||||
)
|
)
|
||||||
|
self._overpowering_state = STATE_OFF
|
||||||
|
|
||||||
if self._vtherm.is_over_climate:
|
if self._vtherm.is_over_climate:
|
||||||
await self._vtherm.restore_hvac_mode(False)
|
await self._vtherm.restore_hvac_mode(False)
|
||||||
await self._vtherm.restore_preset_mode()
|
await self._vtherm.restore_preset_mode()
|
||||||
@@ -297,22 +185,18 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
EventType.POWER_EVENT,
|
EventType.POWER_EVENT,
|
||||||
{
|
{
|
||||||
"type": "end",
|
"type": "end",
|
||||||
"current_power": self._current_power,
|
"current_power": current_power,
|
||||||
"device_power": self._device_power,
|
"device_power": self._device_power,
|
||||||
"current_max_power": self._current_max_power,
|
"current_max_power": current_max_power,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
elif not overpowering and self._overpowering_state != STATE_OFF:
|
||||||
new_overpowering_state = STATE_ON if ret else STATE_OFF
|
# just set to not overpowering the state which was not set
|
||||||
if self._overpowering_state != new_overpowering_state:
|
self._overpowering_state = STATE_OFF
|
||||||
self._overpowering_state = new_overpowering_state
|
else:
|
||||||
self._vtherm.update_custom_attributes()
|
# Nothing to do (already in the right state)
|
||||||
|
return
|
||||||
return self._overpowering_state == STATE_ON
|
self._vtherm.update_custom_attributes()
|
||||||
|
|
||||||
async def set_overpowering(self, overpowering: bool):
|
|
||||||
"""Force the overpowering state for the VTherm"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
@property
|
@property
|
||||||
@@ -333,16 +217,6 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
"""Return True if the Vtherm is in overpowering state"""
|
"""Return True if the Vtherm is in overpowering state"""
|
||||||
return self._overpowering_state == STATE_ON
|
return self._overpowering_state == STATE_ON
|
||||||
|
|
||||||
@property
|
|
||||||
def max_power_sensor_entity_id(self) -> bool:
|
|
||||||
"""Return the power max entity id"""
|
|
||||||
return self._max_power_sensor_entity_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def power_sensor_entity_id(self) -> bool:
|
|
||||||
"""Return the power entity id"""
|
|
||||||
return self._power_sensor_entity_id
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def power_temperature(self) -> bool:
|
def power_temperature(self) -> bool:
|
||||||
"""Return the power temperature"""
|
"""Return the power temperature"""
|
||||||
@@ -353,16 +227,6 @@ class FeaturePowerManager(BaseFeatureManager):
|
|||||||
"""Return the device power"""
|
"""Return the device power"""
|
||||||
return self._device_power
|
return self._device_power
|
||||||
|
|
||||||
@property
|
|
||||||
def current_power(self) -> bool:
|
|
||||||
"""Return the current power from sensor"""
|
|
||||||
return self._current_power
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_max_power(self) -> bool:
|
|
||||||
"""Return the current power from sensor"""
|
|
||||||
return self._current_max_power
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mean_cycle_power(self) -> float | None:
|
def mean_cycle_power(self) -> float | None:
|
||||||
"""Returns the mean power consumption during the cycle"""
|
"""Returns the mean power consumption during the cycle"""
|
||||||
|
|||||||
@@ -409,9 +409,10 @@ class UnderlyingSwitch(UnderlyingEntity):
|
|||||||
await self.turn_off()
|
await self.turn_off()
|
||||||
return
|
return
|
||||||
|
|
||||||
if await self._thermostat.power_manager.check_overpowering():
|
# if await self._thermostat.power_manager.check_overpowering():
|
||||||
_LOGGER.debug("%s - End of cycle (3)", self)
|
# _LOGGER.debug("%s - End of cycle (3)", self)
|
||||||
return
|
# return
|
||||||
|
|
||||||
# safety mode could have change the on_time percent
|
# safety mode could have change the on_time percent
|
||||||
await self._thermostat.safety_manager.refresh_state()
|
await self._thermostat.safety_manager.refresh_state()
|
||||||
time = self._on_time_sec
|
time = self._on_time_sec
|
||||||
|
|||||||
@@ -592,7 +592,10 @@ class MockNumber(NumberEntity):
|
|||||||
|
|
||||||
|
|
||||||
async def create_thermostat(
|
async def create_thermostat(
|
||||||
hass: HomeAssistant, entry: MockConfigEntry, entity_id: str
|
hass: HomeAssistant,
|
||||||
|
entry: MockConfigEntry,
|
||||||
|
entity_id: str,
|
||||||
|
temps: dict | None = None,
|
||||||
) -> BaseThermostat:
|
) -> BaseThermostat:
|
||||||
"""Creates and return a TPI Thermostat"""
|
"""Creates and return a TPI Thermostat"""
|
||||||
entry.add_to_hass(hass)
|
entry.add_to_hass(hass)
|
||||||
@@ -601,6 +604,11 @@ async def create_thermostat(
|
|||||||
|
|
||||||
entity = search_entity(hass, entity_id, CLIMATE_DOMAIN)
|
entity = search_entity(hass, entity_id, CLIMATE_DOMAIN)
|
||||||
|
|
||||||
|
if entity and temps:
|
||||||
|
await set_all_climate_preset_temp(
|
||||||
|
hass, entity, temps, entity.entity_id.replace("climate.", "")
|
||||||
|
)
|
||||||
|
|
||||||
return entity
|
return entity
|
||||||
|
|
||||||
|
|
||||||
@@ -741,9 +749,10 @@ async def send_power_change_event(entity: BaseThermostat, new_power, date, sleep
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await entity.power_manager._async_power_sensor_changed(power_event)
|
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
|
||||||
|
await vtherm_api.central_power_manager._power_sensor_changed(power_event)
|
||||||
if sleep:
|
if sleep:
|
||||||
await asyncio.sleep(0.1)
|
await entity.hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
async def send_max_power_change_event(
|
async def send_max_power_change_event(
|
||||||
@@ -767,9 +776,10 @@ async def send_max_power_change_event(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
await entity.power_manager._async_max_power_sensor_changed(power_event)
|
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
|
||||||
|
await vtherm_api.central_power_manager._max_power_sensor_changed(power_event)
|
||||||
if sleep:
|
if sleep:
|
||||||
await asyncio.sleep(0.1)
|
await entity.hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
async def send_window_change_event(
|
async def send_window_change_event(
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
# https://github.com/miketheman/pytest-socket/pull/275
|
||||||
|
from pytest_socket import socket_allow_hosts
|
||||||
|
|
||||||
from homeassistant.core import StateMachine
|
from homeassistant.core import StateMachine
|
||||||
|
|
||||||
@@ -26,6 +28,12 @@ from custom_components.versatile_thermostat.config_flow import (
|
|||||||
VersatileThermostatBaseConfigFlow,
|
VersatileThermostatBaseConfigFlow,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from custom_components.versatile_thermostat.const import (
|
||||||
|
CONF_POWER_SENSOR,
|
||||||
|
CONF_MAX_POWER_SENSOR,
|
||||||
|
CONF_USE_POWER_FEATURE,
|
||||||
|
CONF_PRESET_POWER,
|
||||||
|
)
|
||||||
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
|
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
|
||||||
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
||||||
|
|
||||||
@@ -35,12 +43,6 @@ from .commons import (
|
|||||||
FULL_CENTRAL_CONFIG_WITH_BOILER,
|
FULL_CENTRAL_CONFIG_WITH_BOILER,
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://github.com/miketheman/pytest-socket/pull/275
|
|
||||||
from pytest_socket import socket_allow_hosts
|
|
||||||
|
|
||||||
# ...
|
|
||||||
|
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
def pytest_runtest_setup():
|
def pytest_runtest_setup():
|
||||||
"""setup tests"""
|
"""setup tests"""
|
||||||
@@ -51,16 +53,6 @@ def pytest_runtest_setup():
|
|||||||
|
|
||||||
pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name
|
pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name
|
||||||
|
|
||||||
# Permet d'exclure certains test en mode d'ex
|
|
||||||
# sequential = pytest.mark.sequential
|
|
||||||
|
|
||||||
|
|
||||||
# This fixture allow to execute some tests first and not in //
|
|
||||||
# @pytest.fixture
|
|
||||||
# def order():
|
|
||||||
# return 1
|
|
||||||
#
|
|
||||||
|
|
||||||
# This fixture enables loading custom integrations in all tests.
|
# This fixture enables loading custom integrations in all tests.
|
||||||
# Remove to enable selective use of this fixture
|
# Remove to enable selective use of this fixture
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@@ -167,3 +159,24 @@ async def init_central_config_with_boiler_fixture(
|
|||||||
await create_central_config(hass, FULL_CENTRAL_CONFIG_WITH_BOILER)
|
await create_central_config(hass, FULL_CENTRAL_CONFIG_WITH_BOILER)
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="init_central_power_manager")
|
||||||
|
async def init_central_power_manager_fixture(
|
||||||
|
hass, init_central_config
|
||||||
|
): # pylint: disable=unused-argument
|
||||||
|
"""Initialize the central power_manager"""
|
||||||
|
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
|
||||||
|
|
||||||
|
# 1. creation / init
|
||||||
|
vtherm_api.central_power_manager.post_init(
|
||||||
|
{
|
||||||
|
CONF_POWER_SENSOR: "sensor.the_power_sensor",
|
||||||
|
CONF_MAX_POWER_SENSOR: "sensor.the_max_power_sensor",
|
||||||
|
CONF_USE_POWER_FEATURE: True,
|
||||||
|
CONF_PRESET_POWER: 13,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert vtherm_api.central_power_manager.is_configured
|
||||||
|
|
||||||
|
yield
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ async def test_overpowering_binary_sensors(
|
|||||||
await entity.async_set_preset_mode(PRESET_COMFORT)
|
await entity.async_set_preset_mode(PRESET_COMFORT)
|
||||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
await send_temperature_change_event(entity, 15, now)
|
await send_temperature_change_event(entity, 15, now)
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
||||||
|
|
||||||
await overpowering_binary_sensor.async_my_climate_changed()
|
await overpowering_binary_sensor.async_my_climate_changed()
|
||||||
@@ -168,7 +168,7 @@ async def test_overpowering_binary_sensors(
|
|||||||
|
|
||||||
await send_power_change_event(entity, 100, now)
|
await send_power_change_event(entity, 100, now)
|
||||||
await send_max_power_change_event(entity, 150, now)
|
await send_max_power_change_event(entity, 150, now)
|
||||||
assert await entity.power_manager.check_overpowering() is True
|
assert entity.power_manager.is_overpowering_detected is True
|
||||||
assert entity.power_manager.overpowering_state is STATE_ON
|
assert entity.power_manager.overpowering_state is STATE_ON
|
||||||
|
|
||||||
# Simulate the event reception
|
# Simulate the event reception
|
||||||
@@ -177,7 +177,7 @@ async def test_overpowering_binary_sensors(
|
|||||||
|
|
||||||
# set max power to a low value
|
# set max power to a low value
|
||||||
await send_max_power_change_event(entity, 201, now)
|
await send_max_power_change_event(entity, 201, now)
|
||||||
assert await entity.power_manager.check_overpowering() 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
|
||||||
await overpowering_binary_sensor.async_my_climate_changed()
|
await overpowering_binary_sensor.async_my_climate_changed()
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
# Send power mesurement (theheater is already in the power measurement)
|
# Send power mesurement (theheater is already in the power measurement)
|
||||||
await send_power_change_event(entity, 100, datetime.now())
|
await send_power_change_event(entity, 100, datetime.now())
|
||||||
# No overpowering yet
|
# No overpowering yet
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
# All configuration is complete and power is < power_max
|
# All configuration is complete and power is < power_max
|
||||||
assert entity.preset_mode is PRESET_COMFORT
|
assert entity.preset_mode is PRESET_COMFORT
|
||||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||||
@@ -365,7 +365,7 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
# waits that the heater starts
|
# waits that the heater starts
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
assert entity.hvac_mode is HVACMode.HEAT
|
assert entity.hvac_mode is HVACMode.HEAT
|
||||||
assert entity.preset_mode is PRESET_BOOST
|
assert entity.preset_mode is PRESET_BOOST
|
||||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||||
@@ -385,7 +385,7 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
|
|||||||
# waits that the heater starts
|
# waits that the heater starts
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
assert await entity.power_manager.check_overpowering() is True
|
assert entity.power_manager.is_overpowering_detected is True
|
||||||
assert entity.hvac_mode is HVACMode.HEAT
|
assert entity.hvac_mode is HVACMode.HEAT
|
||||||
assert entity.preset_mode is PRESET_POWER
|
assert entity.preset_mode is PRESET_POWER
|
||||||
assert entity.power_manager.overpowering_state is STATE_ON
|
assert entity.power_manager.overpowering_state is STATE_ON
|
||||||
|
|||||||
@@ -398,19 +398,15 @@ async def test_central_power_manageer_calculate_shedding(
|
|||||||
)
|
)
|
||||||
vtherm.power_manager.device_power = vtherm_config.get("device_power")
|
vtherm.power_manager.device_power = vtherm_config.get("device_power")
|
||||||
|
|
||||||
async def mock_set_overpowering(overpowering, v=vtherm):
|
async def mock_set_overpowering(
|
||||||
|
overpowering, power_consumption_max=0, v=vtherm
|
||||||
|
):
|
||||||
register_call(v, overpowering)
|
register_call(v, overpowering)
|
||||||
|
|
||||||
vtherm.power_manager.set_overpowering = mock_set_overpowering
|
vtherm.power_manager.set_overpowering = mock_set_overpowering
|
||||||
|
|
||||||
vtherms.append(vtherm)
|
vtherms.append(vtherm)
|
||||||
|
|
||||||
# type(central_power_manager).current_max_power = PropertyMock(
|
|
||||||
# return_value=current_max_power
|
|
||||||
# )
|
|
||||||
# type(central_power_manager).current_power = PropertyMock(return_value=current_power)
|
|
||||||
# type(central_power_manager).is_configured = PropertyMock(return_value=True)
|
|
||||||
|
|
||||||
# fmt:off
|
# fmt:off
|
||||||
with patch("custom_components.versatile_thermostat.central_feature_power_manager.CentralFeaturePowerManager.find_all_vtherm_with_power_management_sorted_by_dtemp", return_value=vtherms), \
|
with patch("custom_components.versatile_thermostat.central_feature_power_manager.CentralFeaturePowerManager.find_all_vtherm_with_power_management_sorted_by_dtemp", return_value=vtherms), \
|
||||||
patch("custom_components.versatile_thermostat.central_feature_power_manager.CentralFeaturePowerManager.current_max_power", new_callable=PropertyMock, return_value=current_max_power), \
|
patch("custom_components.versatile_thermostat.central_feature_power_manager.CentralFeaturePowerManager.current_max_power", new_callable=PropertyMock, return_value=current_max_power), \
|
||||||
|
|||||||
@@ -783,7 +783,7 @@ async def test_multiple_switch_power_management(
|
|||||||
await send_power_change_event(entity, 50, datetime.now())
|
await send_power_change_event(entity, 50, datetime.now())
|
||||||
# Send power max mesurement
|
# Send power max mesurement
|
||||||
await send_max_power_change_event(entity, 300, datetime.now())
|
await send_max_power_change_event(entity, 300, datetime.now())
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
# All configuration is complete and power is < power_max
|
# All configuration is complete and power is < power_max
|
||||||
assert entity.preset_mode is PRESET_BOOST
|
assert entity.preset_mode is PRESET_BOOST
|
||||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||||
@@ -798,7 +798,7 @@ async def test_multiple_switch_power_management(
|
|||||||
) as mock_heater_off:
|
) as mock_heater_off:
|
||||||
# 100 of the device / 4 -> 25, current power 50 so max is 75
|
# 100 of the device / 4 -> 25, current power 50 so max is 75
|
||||||
await send_max_power_change_event(entity, 74, datetime.now())
|
await send_max_power_change_event(entity, 74, datetime.now())
|
||||||
assert await entity.power_manager.check_overpowering() is True
|
assert entity.power_manager.is_overpowering_detected is True
|
||||||
# All configuration is complete and power is > power_max we switch to POWER preset
|
# All configuration is complete and power is > power_max we switch to POWER preset
|
||||||
assert entity.preset_mode is PRESET_POWER
|
assert entity.preset_mode is PRESET_POWER
|
||||||
assert entity.power_manager.overpowering_state is STATE_ON
|
assert entity.power_manager.overpowering_state is STATE_ON
|
||||||
@@ -843,7 +843,7 @@ async def test_multiple_switch_power_management(
|
|||||||
) as mock_heater_off:
|
) as mock_heater_off:
|
||||||
# 100 of the device / 4 -> 25, current power 50 so max is 75. With 150 no overheating
|
# 100 of the device / 4 -> 25, current power 50 so max is 75. With 150 no overheating
|
||||||
await send_max_power_change_event(entity, 150, datetime.now())
|
await send_max_power_change_event(entity, 150, datetime.now())
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
# All configuration is complete and power is > power_max we switch to POWER preset
|
# All configuration is complete and power is > power_max we switch to POWER preset
|
||||||
assert entity.preset_mode is PRESET_ECO
|
assert entity.preset_mode is PRESET_ECO
|
||||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from custom_components.versatile_thermostat.thermostat_switch import (
|
|||||||
from custom_components.versatile_thermostat.feature_power_manager import (
|
from custom_components.versatile_thermostat.feature_power_manager import (
|
||||||
FeaturePowerManager,
|
FeaturePowerManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
from custom_components.versatile_thermostat.prop_algorithm import PropAlgorithm
|
from custom_components.versatile_thermostat.prop_algorithm import PropAlgorithm
|
||||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
@@ -17,28 +18,28 @@ logging.getLogger().setLevel(logging.DEBUG)
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"is_over_climate, is_device_active, power, max_power, current_overpowering_state, overpowering_state, nb_call, changed, check_overpowering_ret",
|
"is_over_climate, is_device_active, power, max_power, check_power_available",
|
||||||
[
|
[
|
||||||
# don't switch to overpower (power is enough)
|
# don't switch to overpower (power is enough)
|
||||||
(False, False, 1000, 3000, STATE_OFF, STATE_OFF, 0, True, False),
|
(False, False, 1000, 3000, True),
|
||||||
# switch to overpower (power is not enough)
|
# switch to overpower (power is not enough)
|
||||||
(False, False, 2000, 3000, STATE_OFF, STATE_ON, 1, True, True),
|
(False, False, 2000, 3000, False),
|
||||||
# don't switch to overpower (power is not enough but device is already on)
|
# don't switch to overpower (power is not enough but device is already on)
|
||||||
(False, True, 2000, 3000, STATE_OFF, STATE_OFF, 0, True, False),
|
(False, True, 2000, 3000, True),
|
||||||
# Same with a over_climate
|
# Same with a over_climate
|
||||||
# don't switch to overpower (power is enough)
|
# don't switch to overpower (power is enough)
|
||||||
(True, False, 1000, 3000, STATE_OFF, STATE_OFF, 0, True, False),
|
(True, False, 1000, 3000, True),
|
||||||
# switch to overpower (power is not enough)
|
# switch to overpower (power is not enough)
|
||||||
(True, False, 2000, 3000, STATE_OFF, STATE_ON, 1, True, True),
|
(True, False, 2000, 3000, False),
|
||||||
# don't switch to overpower (power is not enough but device is already on)
|
# don't switch to overpower (power is not enough but device is already on)
|
||||||
(True, True, 2000, 3000, STATE_OFF, STATE_OFF, 0, True, False),
|
(True, True, 2000, 3000, True),
|
||||||
# Leave overpowering state
|
# Leave overpowering state
|
||||||
# switch to not overpower (power is enough)
|
# switch to not overpower (power is enough)
|
||||||
(False, False, 1000, 3000, STATE_ON, STATE_OFF, 1, True, False),
|
(False, False, 1000, 3000, True),
|
||||||
# don't switch to overpower (power is still not enough)
|
# don't switch to overpower (power is still not enough)
|
||||||
(False, False, 2000, 3000, STATE_ON, STATE_ON, 0, True, True),
|
(False, False, 2000, 3000, False),
|
||||||
# keep overpower (power is not enough but device is already on)
|
# keep overpower (power is not enough but device is already on)
|
||||||
(False, True, 3000, 3000, STATE_ON, STATE_ON, 0, True, True),
|
(False, True, 3000, 3000, False),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_power_feature_manager(
|
async def test_power_feature_manager(
|
||||||
@@ -47,17 +48,15 @@ async def test_power_feature_manager(
|
|||||||
is_device_active,
|
is_device_active,
|
||||||
power,
|
power,
|
||||||
max_power,
|
max_power,
|
||||||
current_overpowering_state,
|
check_power_available,
|
||||||
overpowering_state,
|
|
||||||
nb_call,
|
|
||||||
changed,
|
|
||||||
check_overpowering_ret,
|
|
||||||
):
|
):
|
||||||
"""Test the FeaturePresenceManager class direclty"""
|
"""Test the FeaturePresenceManager class direclty"""
|
||||||
|
|
||||||
fake_vtherm = MagicMock(spec=BaseThermostat)
|
fake_vtherm = MagicMock(spec=BaseThermostat)
|
||||||
type(fake_vtherm).name = PropertyMock(return_value="the name")
|
type(fake_vtherm).name = PropertyMock(return_value="the name")
|
||||||
|
|
||||||
|
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
|
||||||
|
|
||||||
# 1. creation
|
# 1. creation
|
||||||
power_manager = FeaturePowerManager(fake_vtherm, hass)
|
power_manager = FeaturePowerManager(fake_vtherm, hass)
|
||||||
|
|
||||||
@@ -80,10 +79,19 @@ async def test_power_feature_manager(
|
|||||||
assert custom_attributes["current_max_power"] is None
|
assert custom_attributes["current_max_power"] is None
|
||||||
|
|
||||||
# 2. post_init
|
# 2. post_init
|
||||||
power_manager.post_init(
|
vtherm_api.find_central_configuration = MagicMock()
|
||||||
|
vtherm_api.central_power_manager.post_init(
|
||||||
{
|
{
|
||||||
CONF_POWER_SENSOR: "sensor.the_power_sensor",
|
CONF_POWER_SENSOR: "sensor.the_power_sensor",
|
||||||
CONF_MAX_POWER_SENSOR: "sensor.the_max_power_sensor",
|
CONF_MAX_POWER_SENSOR: "sensor.the_max_power_sensor",
|
||||||
|
CONF_USE_POWER_FEATURE: True,
|
||||||
|
CONF_PRESET_POWER: 13,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert vtherm_api.central_power_manager.is_configured
|
||||||
|
|
||||||
|
power_manager.post_init(
|
||||||
|
{
|
||||||
CONF_USE_POWER_FEATURE: True,
|
CONF_USE_POWER_FEATURE: True,
|
||||||
CONF_PRESET_POWER: 10,
|
CONF_PRESET_POWER: 10,
|
||||||
CONF_DEVICE_POWER: 1234,
|
CONF_DEVICE_POWER: 1234,
|
||||||
@@ -111,21 +119,14 @@ async def test_power_feature_manager(
|
|||||||
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
|
||||||
|
|
||||||
assert len(power_manager._active_listener) == 2
|
assert len(power_manager._active_listener) == 0 # no more listening
|
||||||
|
|
||||||
# 4. test refresh and check_overpowering with the parametrized
|
# 4. test refresh and check_overpowering with the parametrized
|
||||||
side_effects = SideEffects(
|
|
||||||
{
|
|
||||||
"sensor.the_power_sensor": State("sensor.the_power_sensor", power),
|
|
||||||
"sensor.the_max_power_sensor": State(
|
|
||||||
"sensor.the_max_power_sensor", max_power
|
|
||||||
),
|
|
||||||
},
|
|
||||||
State("unknown.entity_id", "unknown"),
|
|
||||||
)
|
|
||||||
# fmt:off
|
# fmt:off
|
||||||
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()) as mock_get_state:
|
with patch("custom_components.versatile_thermostat.central_feature_power_manager.CentralFeaturePowerManager.current_max_power", new_callable=PropertyMock, return_value=max_power), \
|
||||||
|
patch("custom_components.versatile_thermostat.central_feature_power_manager.CentralFeaturePowerManager.current_power", new_callable=PropertyMock, return_value=power):
|
||||||
# fmt:on
|
# fmt:on
|
||||||
|
|
||||||
# Finish the mock configuration
|
# Finish the mock configuration
|
||||||
tpi_algo = PropAlgorithm(PROPORTIONAL_FUNCTION_TPI, 0.6, 0.01, 5, 0, "climate.vtherm")
|
tpi_algo = PropAlgorithm(PROPORTIONAL_FUNCTION_TPI, 0.6, 0.01, 5, 0, "climate.vtherm")
|
||||||
tpi_algo._on_percent = 1 # pylint: disable="protected-access"
|
tpi_algo._on_percent = 1 # pylint: disable="protected-access"
|
||||||
@@ -134,8 +135,82 @@ async def test_power_feature_manager(
|
|||||||
type(fake_vtherm).is_over_climate = PropertyMock(return_value=is_over_climate)
|
type(fake_vtherm).is_over_climate = PropertyMock(return_value=is_over_climate)
|
||||||
type(fake_vtherm).proportional_algorithm = PropertyMock(return_value=tpi_algo)
|
type(fake_vtherm).proportional_algorithm = PropertyMock(return_value=tpi_algo)
|
||||||
type(fake_vtherm).nb_underlying_entities = PropertyMock(return_value=1)
|
type(fake_vtherm).nb_underlying_entities = PropertyMock(return_value=1)
|
||||||
type(fake_vtherm).preset_mode = PropertyMock(return_value=PRESET_COMFORT if current_overpowering_state == STATE_OFF else PRESET_POWER)
|
|
||||||
type(fake_vtherm)._saved_preset_mode = PropertyMock(return_value=PRESET_ECO)
|
ret = await power_manager.check_power_available()
|
||||||
|
assert ret == check_power_available
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"is_over_climate, current_overpowering_state, is_overpowering, new_overpowering_state, msg_sent",
|
||||||
|
[
|
||||||
|
# false -> false
|
||||||
|
(False, STATE_OFF, False, STATE_OFF, False),
|
||||||
|
# false -> true
|
||||||
|
(False, STATE_OFF, True, STATE_ON, True),
|
||||||
|
# true -> true
|
||||||
|
(False, STATE_ON, True, STATE_ON, False),
|
||||||
|
# true -> False
|
||||||
|
(False, STATE_ON, False, STATE_OFF, True),
|
||||||
|
# Same with over_climate
|
||||||
|
# false -> false
|
||||||
|
(True, STATE_OFF, False, STATE_OFF, False),
|
||||||
|
# false -> true
|
||||||
|
(True, STATE_OFF, True, STATE_ON, True),
|
||||||
|
# true -> true
|
||||||
|
(True, STATE_ON, True, STATE_ON, False),
|
||||||
|
# true -> False
|
||||||
|
(True, STATE_ON, False, STATE_OFF, True),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_power_feature_manager_set_overpowering(
|
||||||
|
hass,
|
||||||
|
is_over_climate,
|
||||||
|
current_overpowering_state,
|
||||||
|
is_overpowering,
|
||||||
|
new_overpowering_state,
|
||||||
|
msg_sent,
|
||||||
|
):
|
||||||
|
"""Test the set_overpowering method of FeaturePowerManager"""
|
||||||
|
fake_vtherm = MagicMock(spec=BaseThermostat)
|
||||||
|
type(fake_vtherm).name = PropertyMock(return_value="the name")
|
||||||
|
|
||||||
|
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
|
||||||
|
|
||||||
|
# 1. creation / init
|
||||||
|
power_manager = FeaturePowerManager(fake_vtherm, hass)
|
||||||
|
vtherm_api.find_central_configuration = MagicMock()
|
||||||
|
vtherm_api.central_power_manager.post_init(
|
||||||
|
{
|
||||||
|
CONF_POWER_SENSOR: "sensor.the_power_sensor",
|
||||||
|
CONF_MAX_POWER_SENSOR: "sensor.the_max_power_sensor",
|
||||||
|
CONF_USE_POWER_FEATURE: True,
|
||||||
|
CONF_PRESET_POWER: 13,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert vtherm_api.central_power_manager.is_configured
|
||||||
|
|
||||||
|
power_manager.post_init(
|
||||||
|
{
|
||||||
|
CONF_USE_POWER_FEATURE: True,
|
||||||
|
CONF_PRESET_POWER: 10,
|
||||||
|
CONF_DEVICE_POWER: 1234,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert power_manager.is_configured is True
|
||||||
|
assert power_manager.overpowering_state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
# check overpowering
|
||||||
|
power_manager._overpowering_state = current_overpowering_state
|
||||||
|
|
||||||
|
# fmt:off
|
||||||
|
with patch("custom_components.versatile_thermostat.central_feature_power_manager.CentralFeaturePowerManager.current_max_power", new_callable=PropertyMock, return_value=2000), \
|
||||||
|
patch("custom_components.versatile_thermostat.central_feature_power_manager.CentralFeaturePowerManager.current_power", new_callable=PropertyMock, return_value=1000):
|
||||||
|
# fmt:on
|
||||||
|
# Finish mocking
|
||||||
|
fake_vtherm.is_over_climate = is_over_climate
|
||||||
|
fake_vtherm.preset_mode = MagicMock(return_value=PRESET_COMFORT if current_overpowering_state == STATE_OFF else PRESET_POWER)
|
||||||
|
fake_vtherm._saved_preset_mode = PRESET_ECO
|
||||||
|
|
||||||
fake_vtherm.save_hvac_mode = MagicMock()
|
fake_vtherm.save_hvac_mode = MagicMock()
|
||||||
fake_vtherm.restore_hvac_mode = AsyncMock()
|
fake_vtherm.restore_hvac_mode = AsyncMock()
|
||||||
@@ -147,26 +222,17 @@ async def test_power_feature_manager(
|
|||||||
fake_vtherm.update_custom_attributes = MagicMock()
|
fake_vtherm.update_custom_attributes = MagicMock()
|
||||||
|
|
||||||
|
|
||||||
ret = await power_manager.refresh_state()
|
# Call set_overpowering
|
||||||
assert ret == changed
|
await power_manager.set_overpowering(is_overpowering, 1234)
|
||||||
assert power_manager.is_configured is True
|
|
||||||
assert power_manager.overpowering_state == STATE_UNKNOWN
|
|
||||||
assert power_manager.current_power == power
|
|
||||||
assert power_manager.current_max_power == max_power
|
|
||||||
|
|
||||||
# check overpowering
|
assert power_manager.overpowering_state == new_overpowering_state
|
||||||
power_manager._overpowering_state = current_overpowering_state
|
|
||||||
ret2 = await power_manager.check_overpowering()
|
|
||||||
assert ret2 == check_overpowering_ret
|
|
||||||
assert power_manager.overpowering_state == overpowering_state
|
|
||||||
assert mock_get_state.call_count == 2
|
|
||||||
|
|
||||||
if power_manager.overpowering_state == STATE_OFF:
|
if not is_overpowering:
|
||||||
|
assert power_manager.overpowering_state == STATE_OFF
|
||||||
assert fake_vtherm.save_hvac_mode.call_count == 0
|
assert fake_vtherm.save_hvac_mode.call_count == 0
|
||||||
assert fake_vtherm.save_preset_mode.call_count == 0
|
assert fake_vtherm.save_preset_mode.call_count == 0
|
||||||
assert fake_vtherm.async_underlying_entity_turn_off.call_count == 0
|
assert fake_vtherm.async_underlying_entity_turn_off.call_count == 0
|
||||||
assert fake_vtherm.async_set_preset_mode_internal.call_count == 0
|
assert fake_vtherm.async_set_preset_mode_internal.call_count == 0
|
||||||
assert fake_vtherm.send_event.call_count == nb_call
|
|
||||||
|
|
||||||
if current_overpowering_state == STATE_ON:
|
if current_overpowering_state == STATE_ON:
|
||||||
assert fake_vtherm.update_custom_attributes.call_count == 1
|
assert fake_vtherm.update_custom_attributes.call_count == 1
|
||||||
@@ -178,18 +244,24 @@ async def test_power_feature_manager(
|
|||||||
else:
|
else:
|
||||||
assert fake_vtherm.update_custom_attributes.call_count == 0
|
assert fake_vtherm.update_custom_attributes.call_count == 0
|
||||||
|
|
||||||
if nb_call == 1:
|
if msg_sent:
|
||||||
fake_vtherm.send_event.assert_has_calls(
|
fake_vtherm.send_event.assert_has_calls(
|
||||||
[
|
[
|
||||||
call.fake_vtherm.send_event(
|
call.fake_vtherm.send_event(
|
||||||
EventType.POWER_EVENT,
|
EventType.POWER_EVENT,
|
||||||
{'type': 'end', 'current_power': power, 'device_power': 1234, 'current_max_power': max_power}),
|
{
|
||||||
|
"type": "end",
|
||||||
|
"current_power": 1000,
|
||||||
|
"device_power": 1234,
|
||||||
|
"current_max_power": 2000,
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
# is_overpowering is True
|
||||||
|
else:
|
||||||
elif power_manager.overpowering_state == STATE_ON:
|
assert power_manager.overpowering_state == STATE_ON
|
||||||
if is_over_climate:
|
if is_over_climate and current_overpowering_state == STATE_OFF:
|
||||||
assert fake_vtherm.save_hvac_mode.call_count == 1
|
assert fake_vtherm.save_hvac_mode.call_count == 1
|
||||||
else:
|
else:
|
||||||
assert fake_vtherm.save_hvac_mode.call_count == 0
|
assert fake_vtherm.save_hvac_mode.call_count == 0
|
||||||
@@ -209,30 +281,37 @@ async def test_power_feature_manager(
|
|||||||
assert fake_vtherm.restore_hvac_mode.call_count == 0
|
assert fake_vtherm.restore_hvac_mode.call_count == 0
|
||||||
assert fake_vtherm.restore_preset_mode.call_count == 0
|
assert fake_vtherm.restore_preset_mode.call_count == 0
|
||||||
|
|
||||||
if nb_call == 1:
|
if msg_sent:
|
||||||
fake_vtherm.send_event.assert_has_calls(
|
fake_vtherm.send_event.assert_has_calls(
|
||||||
[
|
[
|
||||||
call.fake_vtherm.send_event(
|
call.fake_vtherm.send_event(
|
||||||
EventType.POWER_EVENT,
|
EventType.POWER_EVENT,
|
||||||
{'type': 'start', 'current_power': power, 'device_power': 1234, 'current_max_power': max_power, 'current_power_consumption': 1234.0}),
|
{
|
||||||
|
"type": "start",
|
||||||
|
"current_power": 1000,
|
||||||
|
"device_power": 1234,
|
||||||
|
"current_max_power": 2000,
|
||||||
|
"current_power_consumption": 1234.0,
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
fake_vtherm.reset_mock()
|
fake_vtherm.reset_mock()
|
||||||
|
|
||||||
# 5. Check custom_attributes
|
# 5. Check custom_attributes
|
||||||
custom_attributes = {}
|
custom_attributes = {}
|
||||||
power_manager.add_custom_attributes(custom_attributes)
|
power_manager.add_custom_attributes(custom_attributes)
|
||||||
assert custom_attributes["power_sensor_entity_id"] == "sensor.the_power_sensor"
|
assert custom_attributes["power_sensor_entity_id"] == "sensor.the_power_sensor"
|
||||||
assert (
|
assert (
|
||||||
custom_attributes["max_power_sensor_entity_id"] == "sensor.the_max_power_sensor"
|
custom_attributes["max_power_sensor_entity_id"] == "sensor.the_max_power_sensor"
|
||||||
)
|
)
|
||||||
assert custom_attributes["overpowering_state"] == overpowering_state
|
assert custom_attributes["overpowering_state"] == new_overpowering_state
|
||||||
assert custom_attributes["is_power_configured"] is True
|
assert custom_attributes["is_power_configured"] is True
|
||||||
assert custom_attributes["device_power"] == 1234
|
assert custom_attributes["device_power"] == 1234
|
||||||
assert custom_attributes["power_temp"] == 10
|
assert custom_attributes["power_temp"] == 10
|
||||||
assert custom_attributes["current_power"] == power
|
assert custom_attributes["current_power"] == 1000
|
||||||
assert custom_attributes["current_max_power"] == max_power
|
assert custom_attributes["current_max_power"] == 2000
|
||||||
|
|
||||||
power_manager.stop_listening()
|
power_manager.stop_listening()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@@ -241,10 +320,15 @@ async def test_power_feature_manager(
|
|||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_power_management_hvac_off(
|
async def test_power_management_hvac_off(
|
||||||
hass: HomeAssistant, skip_hass_states_is_state
|
hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager
|
||||||
):
|
):
|
||||||
"""Test the Power management"""
|
"""Test the Power management"""
|
||||||
|
|
||||||
|
temps = {
|
||||||
|
"eco": 17,
|
||||||
|
"comfort": 18,
|
||||||
|
"boost": 19,
|
||||||
|
}
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="TheOverSwitchMockName",
|
title="TheOverSwitchMockName",
|
||||||
@@ -257,29 +341,24 @@ async def test_power_management_hvac_off(
|
|||||||
CONF_CYCLE_MIN: 5,
|
CONF_CYCLE_MIN: 5,
|
||||||
CONF_TEMP_MIN: 15,
|
CONF_TEMP_MIN: 15,
|
||||||
CONF_TEMP_MAX: 30,
|
CONF_TEMP_MAX: 30,
|
||||||
"eco_temp": 17,
|
|
||||||
"comfort_temp": 18,
|
|
||||||
"boost_temp": 19,
|
|
||||||
CONF_USE_WINDOW_FEATURE: False,
|
CONF_USE_WINDOW_FEATURE: False,
|
||||||
CONF_USE_MOTION_FEATURE: False,
|
CONF_USE_MOTION_FEATURE: False,
|
||||||
CONF_USE_POWER_FEATURE: True,
|
CONF_USE_POWER_FEATURE: True,
|
||||||
CONF_USE_PRESENCE_FEATURE: False,
|
CONF_USE_PRESENCE_FEATURE: False,
|
||||||
CONF_HEATER: "switch.mock_switch",
|
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
||||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||||
CONF_TPI_COEF_INT: 0.3,
|
CONF_TPI_COEF_INT: 0.3,
|
||||||
CONF_TPI_COEF_EXT: 0.01,
|
CONF_TPI_COEF_EXT: 0.01,
|
||||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||||
CONF_SAFETY_DELAY_MIN: 5,
|
CONF_SAFETY_DELAY_MIN: 5,
|
||||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||||
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
|
|
||||||
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
|
|
||||||
CONF_DEVICE_POWER: 100,
|
CONF_DEVICE_POWER: 100,
|
||||||
CONF_PRESET_POWER: 12,
|
CONF_PRESET_POWER: 12,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
entity: ThermostatOverSwitch = await create_thermostat(
|
entity: ThermostatOverSwitch = await create_thermostat(
|
||||||
hass, entry, "climate.theoverswitchmockname"
|
hass, entry, "climate.theoverswitchmockname", temps
|
||||||
)
|
)
|
||||||
assert entity
|
assert entity
|
||||||
|
|
||||||
@@ -292,34 +371,53 @@ async def test_power_management_hvac_off(
|
|||||||
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
||||||
assert entity.hvac_mode == HVACMode.OFF
|
assert entity.hvac_mode == HVACMode.OFF
|
||||||
|
|
||||||
|
now: datetime = NowClass.get_now(hass)
|
||||||
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
|
|
||||||
# Send power mesurement
|
# Send power mesurement
|
||||||
await send_power_change_event(entity, 50, datetime.now())
|
# fmt:off
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
side_effects = SideEffects(
|
||||||
|
{
|
||||||
|
"sensor.the_power_sensor": State("sensor.the_power_sensor", 50),
|
||||||
|
"sensor.the_max_power_sensor": State("sensor.the_max_power_sensor", 300),
|
||||||
|
},
|
||||||
|
State("unknown.entity_id", "unknown"),
|
||||||
|
)
|
||||||
|
# fmt:off
|
||||||
|
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()):
|
||||||
|
# fmt: on
|
||||||
|
await send_power_change_event(entity, 50, now)
|
||||||
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
|
|
||||||
# All configuration is not complete
|
# All configuration is not complete
|
||||||
assert entity.preset_mode is PRESET_BOOST
|
assert entity.preset_mode is PRESET_BOOST
|
||||||
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
assert entity.power_manager.overpowering_state is STATE_UNKNOWN # due to hvac_off
|
||||||
|
|
||||||
# Send power max mesurement
|
# Send power max mesurement
|
||||||
await send_max_power_change_event(entity, 300, datetime.now())
|
now = now + timedelta(seconds=61)
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
# All configuration is complete and power is < power_max
|
await send_max_power_change_event(entity, 300, now)
|
||||||
assert entity.preset_mode is PRESET_BOOST
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
# All configuration is complete and power is < power_max
|
||||||
|
assert entity.preset_mode is PRESET_BOOST
|
||||||
|
assert entity.power_manager.overpowering_state is STATE_UNKNOWN # # due to hvac_off
|
||||||
|
|
||||||
# Send power max mesurement too low but HVACMode is off
|
# Send power max mesurement too low but HVACMode is off
|
||||||
with patch(
|
side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 149))
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
# fmt:off
|
||||||
) as mock_send_event, patch(
|
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
||||||
) as mock_heater_on, patch(
|
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off:
|
||||||
) as mock_heater_off:
|
# fmt: on
|
||||||
|
now = now + timedelta(seconds=61)
|
||||||
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
|
|
||||||
await send_max_power_change_event(entity, 149, datetime.now())
|
await send_max_power_change_event(entity, 149, datetime.now())
|
||||||
assert await entity.power_manager.check_overpowering() is True
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
# All configuration is complete and power is > power_max but we stay in Boost cause thermostat if Off
|
# 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.preset_mode is PRESET_BOOST
|
||||||
assert entity.power_manager.overpowering_state is STATE_ON
|
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
|
||||||
|
|
||||||
assert mock_send_event.call_count == 0
|
assert mock_send_event.call_count == 0
|
||||||
assert mock_heater_on.call_count == 0
|
assert mock_heater_on.call_count == 0
|
||||||
@@ -328,9 +426,17 @@ async def test_power_management_hvac_off(
|
|||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is_state):
|
async def test_power_management_hvac_on(
|
||||||
|
hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager
|
||||||
|
):
|
||||||
"""Test the Power management"""
|
"""Test the Power management"""
|
||||||
|
|
||||||
|
temps = {
|
||||||
|
"eco": 17,
|
||||||
|
"comfort": 18,
|
||||||
|
"boost": 19,
|
||||||
|
}
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="TheOverSwitchMockName",
|
title="TheOverSwitchMockName",
|
||||||
@@ -343,32 +449,30 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
|
|||||||
CONF_CYCLE_MIN: 5,
|
CONF_CYCLE_MIN: 5,
|
||||||
CONF_TEMP_MIN: 15,
|
CONF_TEMP_MIN: 15,
|
||||||
CONF_TEMP_MAX: 30,
|
CONF_TEMP_MAX: 30,
|
||||||
"eco_temp": 17,
|
|
||||||
"comfort_temp": 18,
|
|
||||||
"boost_temp": 19,
|
|
||||||
CONF_USE_WINDOW_FEATURE: False,
|
CONF_USE_WINDOW_FEATURE: False,
|
||||||
CONF_USE_MOTION_FEATURE: False,
|
CONF_USE_MOTION_FEATURE: False,
|
||||||
CONF_USE_POWER_FEATURE: True,
|
CONF_USE_POWER_FEATURE: True,
|
||||||
CONF_USE_PRESENCE_FEATURE: False,
|
CONF_USE_PRESENCE_FEATURE: False,
|
||||||
CONF_HEATER: "switch.mock_switch",
|
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
||||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||||
CONF_TPI_COEF_INT: 0.3,
|
CONF_TPI_COEF_INT: 0.3,
|
||||||
CONF_TPI_COEF_EXT: 0.01,
|
CONF_TPI_COEF_EXT: 0.01,
|
||||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||||
CONF_SAFETY_DELAY_MIN: 5,
|
CONF_SAFETY_DELAY_MIN: 5,
|
||||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||||
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
|
|
||||||
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
|
|
||||||
CONF_DEVICE_POWER: 100,
|
CONF_DEVICE_POWER: 100,
|
||||||
CONF_PRESET_POWER: 12,
|
CONF_PRESET_POWER: 12,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
entity: ThermostatOverSwitch = await create_thermostat(
|
entity: ThermostatOverSwitch = await create_thermostat(
|
||||||
hass, entry, "climate.theoverswitchmockname"
|
hass, entry, "climate.theoverswitchmockname", temps
|
||||||
)
|
)
|
||||||
assert entity
|
assert entity
|
||||||
|
|
||||||
|
now: datetime = NowClass.get_now(hass)
|
||||||
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
|
|
||||||
tpi_algo = entity._prop_algorithm
|
tpi_algo = entity._prop_algorithm
|
||||||
assert tpi_algo
|
assert tpi_algo
|
||||||
|
|
||||||
@@ -380,24 +484,40 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
|
|||||||
assert entity.target_temperature == 19
|
assert entity.target_temperature == 19
|
||||||
|
|
||||||
# Send power mesurement
|
# Send power mesurement
|
||||||
await send_power_change_event(entity, 50, datetime.now())
|
side_effects = SideEffects(
|
||||||
# Send power max mesurement
|
{
|
||||||
await send_max_power_change_event(entity, 300, datetime.now())
|
"sensor.the_power_sensor": State("sensor.the_power_sensor", 50),
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
"sensor.the_max_power_sensor": State("sensor.the_max_power_sensor", 300),
|
||||||
# All configuration is complete and power is < power_max
|
},
|
||||||
assert entity.preset_mode is PRESET_BOOST
|
State("unknown.entity_id", "unknown"),
|
||||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
)
|
||||||
|
# fmt:off
|
||||||
|
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()):
|
||||||
|
# fmt: on
|
||||||
|
await send_power_change_event(entity, 50, datetime.now())
|
||||||
|
# Send power max mesurement
|
||||||
|
now = now + timedelta(seconds=61)
|
||||||
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
|
await send_max_power_change_event(entity, 300, datetime.now())
|
||||||
|
|
||||||
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
|
# All configuration is complete and power is < power_max
|
||||||
|
assert entity.preset_mode is PRESET_BOOST
|
||||||
|
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||||
|
|
||||||
# Send power max mesurement too low and HVACMode is on
|
# Send power max mesurement too low and HVACMode is on
|
||||||
with patch(
|
side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 149))
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
# fmt:off
|
||||||
) as mock_send_event, patch(
|
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
||||||
) as mock_heater_on, patch(
|
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off:
|
||||||
) as mock_heater_off:
|
# fmt: on
|
||||||
|
now = now + timedelta(seconds=61)
|
||||||
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
|
|
||||||
await send_max_power_change_event(entity, 149, datetime.now())
|
await send_max_power_change_event(entity, 149, datetime.now())
|
||||||
assert await entity.power_manager.check_overpowering() is True
|
assert entity.power_manager.is_overpowering_detected is True
|
||||||
# All configuration is complete and power is > power_max we switch to POWER preset
|
# All configuration is complete and power is > power_max we switch to POWER preset
|
||||||
assert entity.preset_mode is PRESET_POWER
|
assert entity.preset_mode is PRESET_POWER
|
||||||
assert entity.power_manager.overpowering_state is STATE_ON
|
assert entity.power_manager.overpowering_state is STATE_ON
|
||||||
@@ -424,15 +544,18 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
|
|||||||
assert mock_heater_off.call_count == 1
|
assert mock_heater_off.call_count == 1
|
||||||
|
|
||||||
# Send power mesurement low to unseet power preset
|
# Send power mesurement low to unseet power preset
|
||||||
with patch(
|
side_effects.add_or_update_side_effect("sensor.the_power_sensor", State("sensor.the_power_sensor", 48))
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
# fmt:off
|
||||||
) as mock_send_event, patch(
|
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
|
patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
||||||
) as mock_heater_on, patch(
|
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on") as mock_heater_on, \
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
|
patch("custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off") as mock_heater_off:
|
||||||
) as mock_heater_off:
|
# fmt: on
|
||||||
|
now = now + timedelta(seconds=61)
|
||||||
|
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
|
||||||
|
|
||||||
await send_power_change_event(entity, 48, datetime.now())
|
await send_power_change_event(entity, 48, datetime.now())
|
||||||
assert await entity.power_manager.check_overpowering() is False
|
assert entity.power_manager.is_overpowering_detected is False
|
||||||
# All configuration is complete and power is < power_max, we restore previous preset
|
# All configuration is complete and power is < power_max, we restore previous preset
|
||||||
assert entity.preset_mode is PRESET_BOOST
|
assert entity.preset_mode is PRESET_BOOST
|
||||||
assert entity.power_manager.overpowering_state is STATE_OFF
|
assert entity.power_manager.overpowering_state is STATE_OFF
|
||||||
@@ -462,10 +585,16 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
|
|||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_power_management_energy_over_switch(
|
async def test_power_management_energy_over_switch(
|
||||||
hass: HomeAssistant, skip_hass_states_is_state
|
hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager
|
||||||
):
|
):
|
||||||
"""Test the Power management energy mesurement"""
|
"""Test the Power management energy mesurement"""
|
||||||
|
|
||||||
|
temps = {
|
||||||
|
"eco": 17,
|
||||||
|
"comfort": 18,
|
||||||
|
"boost": 19,
|
||||||
|
}
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="TheOverSwitchMockName",
|
title="TheOverSwitchMockName",
|
||||||
@@ -478,30 +607,24 @@ async def test_power_management_energy_over_switch(
|
|||||||
CONF_CYCLE_MIN: 5,
|
CONF_CYCLE_MIN: 5,
|
||||||
CONF_TEMP_MIN: 15,
|
CONF_TEMP_MIN: 15,
|
||||||
CONF_TEMP_MAX: 30,
|
CONF_TEMP_MAX: 30,
|
||||||
"eco_temp": 17,
|
|
||||||
"comfort_temp": 18,
|
|
||||||
"boost_temp": 19,
|
|
||||||
CONF_USE_WINDOW_FEATURE: False,
|
CONF_USE_WINDOW_FEATURE: False,
|
||||||
CONF_USE_MOTION_FEATURE: False,
|
CONF_USE_MOTION_FEATURE: False,
|
||||||
CONF_USE_POWER_FEATURE: True,
|
CONF_USE_POWER_FEATURE: True,
|
||||||
CONF_USE_PRESENCE_FEATURE: False,
|
CONF_USE_PRESENCE_FEATURE: False,
|
||||||
CONF_HEATER: "switch.mock_switch",
|
CONF_UNDERLYING_LIST: ["switch.mock_switch", "switch.mock_switch2"],
|
||||||
CONF_HEATER_2: "switch.mock_switch2",
|
|
||||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||||
CONF_TPI_COEF_INT: 0.3,
|
CONF_TPI_COEF_INT: 0.3,
|
||||||
CONF_TPI_COEF_EXT: 0.01,
|
CONF_TPI_COEF_EXT: 0.01,
|
||||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||||
CONF_SAFETY_DELAY_MIN: 5,
|
CONF_SAFETY_DELAY_MIN: 5,
|
||||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||||
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
|
|
||||||
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
|
|
||||||
CONF_DEVICE_POWER: 100,
|
CONF_DEVICE_POWER: 100,
|
||||||
CONF_PRESET_POWER: 12,
|
CONF_PRESET_POWER: 12,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
entity: ThermostatOverSwitch = await create_thermostat(
|
entity: ThermostatOverSwitch = await create_thermostat(
|
||||||
hass, entry, "climate.theoverswitchmockname"
|
hass, entry, "climate.theoverswitchmockname", temps
|
||||||
)
|
)
|
||||||
assert entity
|
assert entity
|
||||||
|
|
||||||
@@ -523,6 +646,8 @@ async def test_power_management_energy_over_switch(
|
|||||||
await entity.async_set_preset_mode(PRESET_BOOST)
|
await entity.async_set_preset_mode(PRESET_BOOST)
|
||||||
await send_temperature_change_event(entity, 15, datetime.now())
|
await send_temperature_change_event(entity, 15, datetime.now())
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entity.hvac_mode is HVACMode.HEAT
|
assert entity.hvac_mode is HVACMode.HEAT
|
||||||
assert entity.preset_mode is PRESET_BOOST
|
assert entity.preset_mode is PRESET_BOOST
|
||||||
assert entity.target_temperature == 19
|
assert entity.target_temperature == 19
|
||||||
@@ -594,6 +719,12 @@ async def test_power_management_energy_over_climate(
|
|||||||
):
|
):
|
||||||
"""Test the Power management for a over_climate thermostat"""
|
"""Test the Power management for a over_climate thermostat"""
|
||||||
|
|
||||||
|
temps = {
|
||||||
|
"eco": 17,
|
||||||
|
"comfort": 18,
|
||||||
|
"boost": 19,
|
||||||
|
}
|
||||||
|
|
||||||
the_mock_underlying = MagicMockClimate()
|
the_mock_underlying = MagicMockClimate()
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
@@ -611,14 +742,11 @@ async def test_power_management_energy_over_climate(
|
|||||||
CONF_CYCLE_MIN: 5,
|
CONF_CYCLE_MIN: 5,
|
||||||
CONF_TEMP_MIN: 15,
|
CONF_TEMP_MIN: 15,
|
||||||
CONF_TEMP_MAX: 30,
|
CONF_TEMP_MAX: 30,
|
||||||
"eco_temp": 17,
|
|
||||||
"comfort_temp": 18,
|
|
||||||
"boost_temp": 19,
|
|
||||||
CONF_USE_WINDOW_FEATURE: False,
|
CONF_USE_WINDOW_FEATURE: False,
|
||||||
CONF_USE_MOTION_FEATURE: False,
|
CONF_USE_MOTION_FEATURE: False,
|
||||||
CONF_USE_POWER_FEATURE: True,
|
CONF_USE_POWER_FEATURE: True,
|
||||||
CONF_USE_PRESENCE_FEATURE: False,
|
CONF_USE_PRESENCE_FEATURE: False,
|
||||||
CONF_CLIMATE: "climate.mock_climate",
|
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
|
||||||
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||||
CONF_SAFETY_DELAY_MIN: 5,
|
CONF_SAFETY_DELAY_MIN: 5,
|
||||||
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
|
||||||
@@ -630,7 +758,7 @@ async def test_power_management_energy_over_climate(
|
|||||||
)
|
)
|
||||||
|
|
||||||
entity: ThermostatOverSwitch = await create_thermostat(
|
entity: ThermostatOverSwitch = await create_thermostat(
|
||||||
hass, entry, "climate.theoverclimatemockname"
|
hass, entry, "climate.theoverclimatemockname", temps
|
||||||
)
|
)
|
||||||
assert entity
|
assert entity
|
||||||
assert entity.is_over_climate
|
assert entity.is_over_climate
|
||||||
|
|||||||
Reference in New Issue
Block a user