All tests ok. Add a multi test for climate with valve regulation
This commit is contained in:
@@ -9,7 +9,6 @@ from datetime import timedelta, datetime
|
|||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
from typing import Any, TypeVar, Generic
|
from typing import Any, TypeVar, Generic
|
||||||
|
|
||||||
from homeassistant.util import dt as dt_util
|
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
callback,
|
callback,
|
||||||
@@ -80,13 +79,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
ConfigData = MappingProxyType[str, Any]
|
ConfigData = MappingProxyType[str, Any]
|
||||||
T = TypeVar("T", bound=UnderlyingEntity)
|
T = TypeVar("T", bound=UnderlyingEntity)
|
||||||
|
|
||||||
|
|
||||||
def get_tz(hass: HomeAssistant):
|
|
||||||
"""Get the current timezone"""
|
|
||||||
|
|
||||||
return dt_util.get_time_zone(hass.config.time_zone)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||||
"""Representation of a base class for all Versatile Thermostat device."""
|
"""Representation of a base class for all Versatile Thermostat device."""
|
||||||
|
|
||||||
@@ -2293,7 +2285,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
@property
|
@property
|
||||||
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"""
|
||||||
return self._now if self._now is not None else datetime.now(self._current_tz)
|
return self._now if self._now is not None else NowClass.get_now(self._hass)
|
||||||
|
|
||||||
async def check_safety(self) -> bool:
|
async def check_safety(self) -> bool:
|
||||||
"""Check if last temperature date is too long"""
|
"""Check if last temperature date is too long"""
|
||||||
|
|||||||
@@ -3,38 +3,20 @@
|
|||||||
# pylint: disable=line-too-long
|
# pylint: disable=line-too-long
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta
|
||||||
from homeassistant.core import HomeAssistant, callback, Event
|
from homeassistant.core import HomeAssistant, callback, Event
|
||||||
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
|
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
|
||||||
from homeassistant.helpers.entity_component import EntityComponent
|
from homeassistant.helpers.entity_component import EntityComponent
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
||||||
from homeassistant.helpers.event import async_track_state_change_event, async_call_later
|
from homeassistant.helpers.event import async_track_state_change_event, async_call_later
|
||||||
from homeassistant.util import dt as dt_util
|
|
||||||
|
|
||||||
from .base_thermostat import BaseThermostat
|
from .base_thermostat import BaseThermostat
|
||||||
from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError
|
from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
def get_tz(hass: HomeAssistant):
|
|
||||||
"""Get the current timezone"""
|
|
||||||
|
|
||||||
return dt_util.get_time_zone(hass.config.time_zone)
|
|
||||||
|
|
||||||
|
|
||||||
class NowClass:
|
|
||||||
"""For testing purpose only"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_now(hass: HomeAssistant) -> datetime:
|
|
||||||
"""A test function to get the now.
|
|
||||||
For testing purpose this method can be overriden to get a specific
|
|
||||||
timestamp.
|
|
||||||
"""
|
|
||||||
return datetime.now(get_tz(hass))
|
|
||||||
|
|
||||||
|
|
||||||
def round_to_nearest(n: float, x: float) -> float:
|
def round_to_nearest(n: float, x: float) -> float:
|
||||||
"""Round a number to the nearest x (which should be decimal but not null)
|
"""Round a number to the nearest x (which should be decimal but not null)
|
||||||
Example:
|
Example:
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.const import CONF_NAME, Platform
|
from homeassistant.const import CONF_NAME, Platform
|
||||||
|
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
@@ -17,6 +19,7 @@ from homeassistant.components.climate import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .prop_algorithm import (
|
from .prop_algorithm import (
|
||||||
PROPORTIONAL_FUNCTION_TPI,
|
PROPORTIONAL_FUNCTION_TPI,
|
||||||
@@ -506,6 +509,24 @@ def get_safe_float(hass, entity_id: str):
|
|||||||
return None if math.isinf(float_val) or not math.isfinite(float_val) else float_val
|
return None if math.isinf(float_val) or not math.isfinite(float_val) else float_val
|
||||||
|
|
||||||
|
|
||||||
|
def get_tz(hass: HomeAssistant):
|
||||||
|
"""Get the current timezone"""
|
||||||
|
|
||||||
|
return dt_util.get_time_zone(hass.config.time_zone)
|
||||||
|
|
||||||
|
|
||||||
|
class NowClass:
|
||||||
|
"""For testing purpose only"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_now(hass: HomeAssistant) -> datetime:
|
||||||
|
"""A test function to get the now.
|
||||||
|
For testing purpose this method can be overriden to get a specific
|
||||||
|
timestamp.
|
||||||
|
"""
|
||||||
|
return datetime.now(get_tz(hass))
|
||||||
|
|
||||||
|
|
||||||
class UnknownEntity(HomeAssistantError):
|
class UnknownEntity(HomeAssistantError):
|
||||||
"""Error to indicate there is an unknown entity_id given."""
|
"""Error to indicate there is an unknown entity_id given."""
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from homeassistant.components.climate import (
|
|||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .commons import NowClass, round_to_nearest
|
from .commons import round_to_nearest
|
||||||
from .base_thermostat import BaseThermostat, ConfigData
|
from .base_thermostat import BaseThermostat, ConfigData
|
||||||
from .pi_algorithm import PITemperatureRegulator
|
from .pi_algorithm import PITemperatureRegulator
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
# super.__init__ calls post_init at the end. So it must be called after regulation initialization
|
# super.__init__ calls post_init at the end. So it must be called after regulation initialization
|
||||||
super().__init__(hass, unique_id, name, entry_infos)
|
super().__init__(hass, unique_id, name, entry_infos)
|
||||||
self._regulated_target_temp = self.target_temperature
|
self._regulated_target_temp = self.target_temperature
|
||||||
self._last_regulation_change = NowClass.get_now(hass)
|
self._last_regulation_change = None # NowClass.get_now(hass)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def post_init(self, config_entry: ConfigData):
|
def post_init(self, config_entry: ConfigData):
|
||||||
@@ -206,16 +206,18 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
force,
|
force,
|
||||||
)
|
)
|
||||||
|
|
||||||
now: datetime = NowClass.get_now(self._hass)
|
if self._last_regulation_change is not None:
|
||||||
period = float((now - self._last_regulation_change).total_seconds()) / 60.0
|
period = (
|
||||||
if not force and period < self._auto_regulation_period_min:
|
float((self.now - self._last_regulation_change).total_seconds()) / 60.0
|
||||||
_LOGGER.info(
|
|
||||||
"%s - period (%.1f) min is < %.0f min -> forget the regulation send",
|
|
||||||
self,
|
|
||||||
period,
|
|
||||||
self._auto_regulation_period_min,
|
|
||||||
)
|
)
|
||||||
return
|
if not force and period < self._auto_regulation_period_min:
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - period (%.1f) min is < %.0f min -> forget the regulation send",
|
||||||
|
self,
|
||||||
|
period,
|
||||||
|
self._auto_regulation_period_min,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if not self._regulated_target_temp:
|
if not self._regulated_target_temp:
|
||||||
self._regulated_target_temp = self.target_temperature
|
self._regulated_target_temp = self.target_temperature
|
||||||
@@ -253,7 +255,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
new_regulated_temp,
|
new_regulated_temp,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._last_regulation_change = now
|
self._last_regulation_change = self.now
|
||||||
for under in self._underlyings:
|
for under in self._underlyings:
|
||||||
# issue 348 - use device temperature if configured as offset
|
# issue 348 - use device temperature if configured as offset
|
||||||
offset_temp = 0
|
offset_temp = 0
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ from pytest_homeassistant_custom_component.common import MockConfigEntry
|
|||||||
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
||||||
from custom_components.versatile_thermostat.const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from custom_components.versatile_thermostat.const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
from custom_components.versatile_thermostat.underlyings import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from custom_components.versatile_thermostat.underlyings import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
from custom_components.versatile_thermostat.commons import ( # pylint: disable=unused-import
|
|
||||||
get_tz,
|
|
||||||
NowClass,
|
|
||||||
)
|
|
||||||
|
|
||||||
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
|
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ async def test_over_climate_regulation(
|
|||||||
event_timestamp = now - timedelta(minutes=10)
|
event_timestamp = now - timedelta(minutes=10)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
), patch(
|
), patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
@@ -87,7 +87,7 @@ async def test_over_climate_regulation(
|
|||||||
# set manual target temp (at now - 7) -> the regulation should occurs
|
# set manual target temp (at now - 7) -> the regulation should occurs
|
||||||
event_timestamp = now - timedelta(minutes=7)
|
event_timestamp = now - timedelta(minutes=7)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await entity.async_set_temperature(temperature=18)
|
await entity.async_set_temperature(temperature=18)
|
||||||
@@ -108,7 +108,7 @@ async def test_over_climate_regulation(
|
|||||||
# change temperature so that the regulated temperature should slow down
|
# change temperature so that the regulated temperature should slow down
|
||||||
event_timestamp = now - timedelta(minutes=5)
|
event_timestamp = now - timedelta(minutes=5)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await send_temperature_change_event(entity, 23, event_timestamp)
|
await send_temperature_change_event(entity, 23, event_timestamp)
|
||||||
@@ -144,7 +144,7 @@ async def test_over_climate_regulation_ac_mode(
|
|||||||
event_timestamp = now - timedelta(minutes=10)
|
event_timestamp = now - timedelta(minutes=10)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
), patch(
|
), patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
@@ -183,7 +183,7 @@ async def test_over_climate_regulation_ac_mode(
|
|||||||
# set manual target temp
|
# set manual target temp
|
||||||
event_timestamp = now - timedelta(minutes=7)
|
event_timestamp = now - timedelta(minutes=7)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await entity.async_set_temperature(temperature=25)
|
await entity.async_set_temperature(temperature=25)
|
||||||
@@ -204,7 +204,7 @@ async def test_over_climate_regulation_ac_mode(
|
|||||||
# change temperature so that the regulated temperature should slow down
|
# change temperature so that the regulated temperature should slow down
|
||||||
event_timestamp = now - timedelta(minutes=5)
|
event_timestamp = now - timedelta(minutes=5)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await send_temperature_change_event(entity, 26, event_timestamp)
|
await send_temperature_change_event(entity, 26, event_timestamp)
|
||||||
@@ -219,7 +219,7 @@ async def test_over_climate_regulation_ac_mode(
|
|||||||
# change temperature so that the regulated temperature should slow down
|
# change temperature so that the regulated temperature should slow down
|
||||||
event_timestamp = now - timedelta(minutes=3)
|
event_timestamp = now - timedelta(minutes=3)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||||
@@ -260,7 +260,7 @@ async def test_over_climate_regulation_limitations(
|
|||||||
event_timestamp = now - timedelta(minutes=20)
|
event_timestamp = now - timedelta(minutes=20)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
), patch(
|
), patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
@@ -286,71 +286,61 @@ async def test_over_climate_regulation_limitations(
|
|||||||
assert entity.is_over_climate is True
|
assert entity.is_over_climate is True
|
||||||
assert entity.is_regulated is True
|
assert entity.is_regulated is True
|
||||||
|
|
||||||
|
entity._set_now(event_timestamp)
|
||||||
|
# Will initialize the _last_regulation_change
|
||||||
# Activate the heating by changing HVACMode and temperature
|
# Activate the heating by changing HVACMode and temperature
|
||||||
# Select a hvacmode, presence and preset
|
# Select a hvacmode, presence and preset
|
||||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
assert entity.hvac_mode is HVACMode.HEAT
|
assert entity.hvac_mode is HVACMode.HEAT
|
||||||
|
await entity.async_set_temperature(temperature=17)
|
||||||
|
|
||||||
# it is cold today
|
# it is cold today
|
||||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||||
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
||||||
|
|
||||||
# set manual target temp (at now - 19) -> the regulation should be ignored because too early
|
# 1. set manual target temp (at now - 19) -> the regulation should be ignored because too early
|
||||||
event_timestamp = now - timedelta(minutes=19)
|
event_timestamp = now - timedelta(minutes=19)
|
||||||
with patch(
|
entity._set_now(event_timestamp)
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
await entity.async_set_temperature(temperature=18)
|
||||||
return_value=event_timestamp,
|
|
||||||
):
|
|
||||||
await entity.async_set_temperature(temperature=18)
|
|
||||||
|
|
||||||
fake_underlying_climate.set_hvac_action(
|
fake_underlying_climate.set_hvac_action(
|
||||||
HVACAction.HEATING
|
HVACAction.HEATING
|
||||||
) # simulate under heating
|
) # simulate under heating
|
||||||
assert entity.hvac_action == HVACAction.HEATING
|
assert entity.hvac_action == HVACAction.HEATING
|
||||||
|
|
||||||
# the regulated temperature will change because when we set temp manually it is forced
|
# the regulated temperature will not change because when we set temp manually it is forced
|
||||||
assert entity.regulated_target_temp == 19.5
|
assert entity.regulated_target_temp == 17 # 19.5
|
||||||
|
|
||||||
# set manual target temp (at now - 18) -> the regulation should be taken into account
|
# 2. set manual target temp (at now - 18) -> the regulation should be taken into account
|
||||||
event_timestamp = now - timedelta(minutes=18)
|
event_timestamp = now - timedelta(minutes=18)
|
||||||
with patch(
|
entity._set_now(event_timestamp)
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
|
||||||
return_value=event_timestamp,
|
|
||||||
):
|
|
||||||
await entity.async_set_temperature(temperature=17)
|
|
||||||
assert entity.regulated_target_temp > entity.target_temperature
|
|
||||||
assert (
|
|
||||||
entity.regulated_target_temp == 18 + 0
|
|
||||||
) # In strong we could go up to +3 degre. 0.7 without round_to_nearest
|
|
||||||
old_regulated_temp = entity.regulated_target_temp
|
|
||||||
|
|
||||||
# change temperature so that dtemp < 0.5 and time is > period_min (+ 3min)
|
await entity.async_set_temperature(temperature=17)
|
||||||
|
assert entity.regulated_target_temp > entity.target_temperature
|
||||||
|
assert (
|
||||||
|
entity.regulated_target_temp == 18 + 0
|
||||||
|
) # In strong we could go up to +3 degre. 0.7 without round_to_nearest
|
||||||
|
old_regulated_temp = entity.regulated_target_temp
|
||||||
|
|
||||||
|
# 3. change temperature so that dtemp < 0.5 and time is > period_min (+ 3min)
|
||||||
event_timestamp = now - timedelta(minutes=15)
|
event_timestamp = now - timedelta(minutes=15)
|
||||||
with patch(
|
entity._set_now(event_timestamp)
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
await send_temperature_change_event(entity, 16, event_timestamp)
|
||||||
return_value=event_timestamp,
|
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
||||||
):
|
|
||||||
await send_temperature_change_event(entity, 16, event_timestamp)
|
|
||||||
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
|
||||||
|
|
||||||
# the regulated temperature should be under
|
# the regulated temperature should be under
|
||||||
assert entity.regulated_target_temp <= old_regulated_temp
|
assert entity.regulated_target_temp <= old_regulated_temp
|
||||||
|
|
||||||
# change temperature so that dtemp > 0.5 and time is > period_min (+ 3min)
|
# 4. change temperature so that dtemp > 0.5 and time is > period_min (+ 3min)
|
||||||
event_timestamp = now - timedelta(minutes=12)
|
event_timestamp = now - timedelta(minutes=12)
|
||||||
with patch(
|
entity._set_now(event_timestamp)
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
await send_ext_temperature_change_event(entity, 12, event_timestamp)
|
||||||
return_value=event_timestamp,
|
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||||
):
|
|
||||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
|
||||||
await send_ext_temperature_change_event(entity, 12, event_timestamp)
|
|
||||||
|
|
||||||
# the regulated should have been done
|
# the regulated should have been done
|
||||||
assert entity.regulated_target_temp != old_regulated_temp
|
assert entity.regulated_target_temp != old_regulated_temp
|
||||||
assert entity.regulated_target_temp >= entity.target_temperature
|
assert entity.regulated_target_temp >= entity.target_temperature
|
||||||
assert (
|
assert entity.regulated_target_temp == 17 + 1.5 # 0.7 without round_to_nearest
|
||||||
entity.regulated_target_temp == 17 + 1.5
|
|
||||||
) # 0.7 without round_to_nearest
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@@ -383,7 +373,7 @@ async def test_over_climate_regulation_use_device_temp(
|
|||||||
event_timestamp = now - timedelta(minutes=10)
|
event_timestamp = now - timedelta(minutes=10)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
), patch(
|
), patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
@@ -416,7 +406,7 @@ async def test_over_climate_regulation_use_device_temp(
|
|||||||
fake_underlying_climate.set_current_temperature(15)
|
fake_underlying_climate.set_current_temperature(15)
|
||||||
event_timestamp = now - timedelta(minutes=7)
|
event_timestamp = now - timedelta(minutes=7)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
await entity.async_set_temperature(temperature=16)
|
await entity.async_set_temperature(temperature=16)
|
||||||
@@ -462,7 +452,7 @@ async def test_over_climate_regulation_use_device_temp(
|
|||||||
|
|
||||||
event_timestamp = now - timedelta(minutes=5)
|
event_timestamp = now - timedelta(minutes=5)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||||
@@ -497,7 +487,7 @@ async def test_over_climate_regulation_use_device_temp(
|
|||||||
|
|
||||||
event_timestamp = now - timedelta(minutes=3)
|
event_timestamp = now - timedelta(minutes=3)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
await send_temperature_change_event(entity, 25, event_timestamp)
|
await send_temperature_change_event(entity, 25, event_timestamp)
|
||||||
@@ -545,7 +535,7 @@ async def test_over_climate_regulation_dtemp_null(
|
|||||||
event_timestamp = now - timedelta(minutes=20)
|
event_timestamp = now - timedelta(minutes=20)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
), patch(
|
), patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
@@ -573,7 +563,7 @@ async def test_over_climate_regulation_dtemp_null(
|
|||||||
# set manual target temp
|
# set manual target temp
|
||||||
event_timestamp = now - timedelta(minutes=17)
|
event_timestamp = now - timedelta(minutes=17)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await entity.async_set_temperature(temperature=20)
|
await entity.async_set_temperature(temperature=20)
|
||||||
@@ -594,7 +584,7 @@ async def test_over_climate_regulation_dtemp_null(
|
|||||||
# change temperature so that the regulated temperature should slow down
|
# change temperature so that the regulated temperature should slow down
|
||||||
event_timestamp = now - timedelta(minutes=15)
|
event_timestamp = now - timedelta(minutes=15)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await send_temperature_change_event(entity, 19, event_timestamp)
|
await send_temperature_change_event(entity, 19, event_timestamp)
|
||||||
@@ -607,7 +597,7 @@ async def test_over_climate_regulation_dtemp_null(
|
|||||||
# change temperature so that the regulated temperature should slow down
|
# change temperature so that the regulated temperature should slow down
|
||||||
event_timestamp = now - timedelta(minutes=13)
|
event_timestamp = now - timedelta(minutes=13)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await send_temperature_change_event(entity, 20, event_timestamp)
|
await send_temperature_change_event(entity, 20, event_timestamp)
|
||||||
@@ -621,7 +611,7 @@ async def test_over_climate_regulation_dtemp_null(
|
|||||||
# Test if a small temperature change is taken into account : change temperature so that dtemp < 0.5 and time is > period_min (+ 3min)
|
# Test if a small temperature change is taken into account : change temperature so that dtemp < 0.5 and time is > period_min (+ 3min)
|
||||||
event_timestamp = now - timedelta(minutes=10)
|
event_timestamp = now - timedelta(minutes=10)
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||||
return_value=event_timestamp,
|
return_value=event_timestamp,
|
||||||
):
|
):
|
||||||
await send_temperature_change_event(entity, 19.6, event_timestamp)
|
await send_temperature_change_event(entity, 19.6, event_timestamp)
|
||||||
|
|||||||
@@ -161,19 +161,6 @@ async def test_bug_272(
|
|||||||
"homeassistant.core.ServiceRegistry.async_call"
|
"homeassistant.core.ServiceRegistry.async_call"
|
||||||
) as mock_service_call:
|
) as mock_service_call:
|
||||||
entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname")
|
entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname")
|
||||||
# entry.add_to_hass(hass)
|
|
||||||
# await hass.config_entries.async_setup(entry.entry_id)
|
|
||||||
# assert entry.state is ConfigEntryState.LOADED
|
|
||||||
#
|
|
||||||
# def find_my_entity(entity_id) -> ClimateEntity:
|
|
||||||
# """Find my new entity"""
|
|
||||||
# component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
|
|
||||||
# for entity in component.entities:
|
|
||||||
# if entity.entity_id == entity_id:
|
|
||||||
# return entity
|
|
||||||
#
|
|
||||||
# entity = find_my_entity("climate.theoverclimatemockname")
|
|
||||||
|
|
||||||
assert entity
|
assert entity
|
||||||
|
|
||||||
assert entity.name == "TheOverClimateMockName"
|
assert entity.name == "TheOverClimateMockName"
|
||||||
@@ -215,16 +202,18 @@ async def test_bug_272(
|
|||||||
)
|
)
|
||||||
|
|
||||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
now: datetime = datetime.now(tz=tz)
|
event_timestamp: datetime = datetime.now(tz=tz)
|
||||||
|
entity._set_now(now)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
# Set room temperature to something very cold
|
# Set room temperature to something very cold
|
||||||
event_timestamp = now + timedelta(minutes=1)
|
await send_temperature_change_event(entity, 13, now)
|
||||||
|
await send_ext_temperature_change_event(entity, 9, now)
|
||||||
|
|
||||||
await send_temperature_change_event(entity, 13, event_timestamp)
|
event_timestamp = event_timestamp + timedelta(minutes=3)
|
||||||
await send_ext_temperature_change_event(entity, 9, event_timestamp)
|
entity._set_now(event_timestamp)
|
||||||
|
|
||||||
# Not in the accepted interval (15-19)
|
# Not in the accepted interval (15-19)
|
||||||
await entity.async_set_temperature(temperature=10)
|
await entity.async_set_temperature(temperature=10)
|
||||||
@@ -248,12 +237,15 @@ async def test_bug_272(
|
|||||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
# Set room temperature to something very cold
|
# Set room temperature to something very cold
|
||||||
event_timestamp = now + timedelta(minutes=1)
|
event_timestamp = event_timestamp + timedelta(minutes=1)
|
||||||
|
entity._set_now(event_timestamp)
|
||||||
|
|
||||||
await send_temperature_change_event(entity, 13, event_timestamp)
|
await send_temperature_change_event(entity, 13, event_timestamp)
|
||||||
await send_ext_temperature_change_event(entity, 9, event_timestamp)
|
await send_ext_temperature_change_event(entity, 9, event_timestamp)
|
||||||
|
|
||||||
# In the accepted interval
|
# In the accepted interval
|
||||||
|
event_timestamp = event_timestamp + timedelta(minutes=3)
|
||||||
|
entity._set_now(event_timestamp)
|
||||||
await entity.async_set_temperature(temperature=20.8)
|
await entity.async_set_temperature(temperature=20.8)
|
||||||
assert mock_service_call.call_count == 1
|
assert mock_service_call.call_count == 1
|
||||||
mock_service_call.assert_has_calls(
|
mock_service_call.assert_has_calls(
|
||||||
|
|||||||
@@ -517,6 +517,9 @@ async def test_bug_508(
|
|||||||
data=PARTIAL_CLIMATE_CONFIG, # 5 minutes security delay
|
data=PARTIAL_CLIMATE_CONFIG, # 5 minutes security delay
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
|
now: datetime = datetime.now(tz=tz)
|
||||||
|
|
||||||
# Min_temp is 10 and max_temp is 31 and features contains TARGET_TEMPERATURE_RANGE
|
# Min_temp is 10 and max_temp is 31 and features contains TARGET_TEMPERATURE_RANGE
|
||||||
fake_underlying_climate = MagicMockClimateWithTemperatureRange()
|
fake_underlying_climate = MagicMockClimateWithTemperatureRange()
|
||||||
|
|
||||||
@@ -545,6 +548,9 @@ async def test_bug_508(
|
|||||||
# Set the hvac_mode to HEAT
|
# Set the hvac_mode to HEAT
|
||||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
|
|
||||||
|
now = now + timedelta(minutes=3) # avoid temporal filter
|
||||||
|
entity._set_now(now)
|
||||||
|
|
||||||
# Not In the accepted interval -> should be converted into 10 (the min) and send with target_temp_high and target_temp_low
|
# Not In the accepted interval -> should be converted into 10 (the min) and send with target_temp_high and target_temp_low
|
||||||
await entity.async_set_temperature(temperature=8.5)
|
await entity.async_set_temperature(temperature=8.5)
|
||||||
|
|
||||||
@@ -568,6 +574,9 @@ async def test_bug_508(
|
|||||||
|
|
||||||
with patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
with patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||||
# Not In the accepted interval -> should be converted into 10 (the min) and send with target_temp_high and target_temp_low
|
# Not In the accepted interval -> should be converted into 10 (the min) and send with target_temp_high and target_temp_low
|
||||||
|
now = now + timedelta(minutes=3) # avoid temporal filter
|
||||||
|
entity._set_now(now)
|
||||||
|
|
||||||
await entity.async_set_temperature(temperature=32)
|
await entity.async_set_temperature(temperature=32)
|
||||||
|
|
||||||
# MagicMock climate is already HEAT by default. So there is no SET_HAVC_MODE call
|
# MagicMock climate is already HEAT by default. So there is no SET_HAVC_MODE call
|
||||||
|
|||||||
@@ -136,15 +136,15 @@ async def test_over_climate_valve_mono(hass: HomeAssistant, skip_hass_states_get
|
|||||||
assert mock_service_call.call_count == 3
|
assert mock_service_call.call_count == 3
|
||||||
mock_service_call.assert_has_calls(
|
mock_service_call.assert_has_calls(
|
||||||
[
|
[
|
||||||
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree'}),
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree'}),
|
|
||||||
# we have no current_temperature yet
|
|
||||||
# call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration'}),
|
|
||||||
call("climate","set_temperature",{
|
call("climate","set_temperature",{
|
||||||
"entity_id": "climate.mock_climate",
|
"entity_id": "climate.mock_climate",
|
||||||
"temperature": 15, # temp-min
|
"temperature": 15, # temp-min
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree'}),
|
||||||
|
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree'}),
|
||||||
|
# we have no current_temperature yet
|
||||||
|
# call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration'}),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -416,6 +416,7 @@ async def test_over_climate_valve_multi_presence(
|
|||||||
|
|
||||||
assert vtherm.target_temperature == 17.2
|
assert vtherm.target_temperature == 17.2
|
||||||
|
|
||||||
|
# 2: set presence on -> should activate the valve and change target
|
||||||
# fmt: off
|
# fmt: off
|
||||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
||||||
patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\
|
patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\
|
||||||
@@ -432,164 +433,43 @@ async def test_over_climate_valve_multi_presence(
|
|||||||
|
|
||||||
# the underlying set temperature call and the call to the valve
|
# the underlying set temperature call and the call to the valve
|
||||||
assert mock_service_call.call_count == 8
|
assert mock_service_call.call_count == 8
|
||||||
mock_service_call.assert_has_calls(
|
mock_service_call.assert_has_calls([
|
||||||
[
|
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate1', 'temperature': 19.0}),
|
||||||
call(domain='number', service='set_value', service_data={'value': 40}, target={'entity_id': 'number.mock_opening_degree1'}),
|
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate2', 'temperature': 19.0}),
|
||||||
call(domain='number', service='set_value', service_data={'value': 60}, target={'entity_id': 'number.mock_closing_degree1'}),
|
call(domain='number', service='set_value', service_data={'value': 40}, target={'entity_id': 'number.mock_opening_degree1'}),
|
||||||
call(domain='number', service='set_value', service_data={'value': 3}, target={'entity_id': 'number.mock_offset_calibration1'}),
|
call(domain='number', service='set_value', service_data={'value': 60}, target={'entity_id': 'number.mock_closing_degree1'}),
|
||||||
call(domain='number', service='set_value', service_data={'value': 40}, target={'entity_id': 'number.mock_opening_degree2'}),
|
call(domain='number', service='set_value', service_data={'value': 3.0}, target={'entity_id': 'number.mock_offset_calibration1'}),
|
||||||
call(domain='number', service='set_value', service_data={'value': 60}, target={'entity_id': 'number.mock_closing_degree2'}),
|
call(domain='number', service='set_value', service_data={'value': 40}, target={'entity_id': 'number.mock_opening_degree2'}),
|
||||||
call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration2'}),
|
call(domain='number', service='set_value', service_data={'value': 60}, target={'entity_id': 'number.mock_closing_degree2'}),
|
||||||
call("climate","set_temperature",{
|
call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration2'})
|
||||||
"entity_id": "climate.mock_climate1",
|
|
||||||
"temperature": 19,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
call("climate","set_temperature",{
|
|
||||||
"entity_id": "climate.mock_climate2",
|
|
||||||
"temperature": 19,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert mock_get_state.call_count > 5 # each temp sensor + each valve
|
# 3: set presence off -> should deactivate the valve and change target
|
||||||
|
|
||||||
|
|
||||||
# 2. Starts heating slowly (18 vs 19)
|
|
||||||
now = now + timedelta(minutes=1)
|
|
||||||
vtherm._set_now(now)
|
|
||||||
|
|
||||||
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
||||||
patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\
|
patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\
|
||||||
patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state:
|
patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state:
|
||||||
# fmt: on
|
# fmt: on
|
||||||
now = now + timedelta(minutes=2) # avoid temporal filter
|
now = now + timedelta(minutes=3)
|
||||||
vtherm._set_now(now)
|
vtherm._set_now(now)
|
||||||
|
|
||||||
await vtherm.async_set_preset_mode(PRESET_COMFORT)
|
await send_presence_change_event(vtherm, False, True, now)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
||||||
assert vtherm.preset_mode is PRESET_COMFORT
|
|
||||||
assert vtherm.target_temperature == 19
|
|
||||||
assert vtherm.current_temperature == 18
|
|
||||||
assert vtherm.valve_open_percent == 40 # 0.3*1 + 0.1*1
|
|
||||||
|
|
||||||
|
|
||||||
assert mock_service_call.call_count == 4
|
|
||||||
mock_service_call.assert_has_calls(
|
|
||||||
[
|
|
||||||
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate', 'temperature': 19.0}),
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 40}, target={'entity_id': 'number.mock_opening_degree'}),
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 60}, target={'entity_id': 'number.mock_closing_degree'}),
|
|
||||||
# 3 = 18 (room) - 15 (current of underlying) + 0 (current offset)
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 3.0}, target={'entity_id': 'number.mock_offset_calibration'})
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# set the opening to 40%
|
|
||||||
mock_get_state_side_effect.add_or_update_side_effect(
|
|
||||||
"number.mock_opening_degree",
|
|
||||||
State(
|
|
||||||
"number.mock_opening_degree", "40", {"min": 0, "max": 100}
|
|
||||||
))
|
|
||||||
|
|
||||||
assert vtherm.hvac_action is HVACAction.HEATING
|
|
||||||
assert vtherm.is_device_active is True
|
|
||||||
|
|
||||||
# 2. Starts heating very slowly (18.9 vs 19)
|
|
||||||
now = now + timedelta(minutes=2)
|
|
||||||
vtherm._set_now(now)
|
|
||||||
# fmt: off
|
|
||||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
|
||||||
patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\
|
|
||||||
patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state:
|
|
||||||
# fmt: on
|
|
||||||
# set the offset to 3
|
|
||||||
mock_get_state_side_effect.add_or_update_side_effect(
|
|
||||||
"number.mock_offset_calibration",
|
|
||||||
State(
|
|
||||||
"number.mock_offset_calibration", "3", {"min": -12, "max": 12}
|
|
||||||
))
|
|
||||||
|
|
||||||
await send_temperature_change_event(vtherm, 18.9, now, True)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
||||||
assert vtherm.preset_mode is PRESET_COMFORT
|
|
||||||
assert vtherm.target_temperature == 19
|
|
||||||
assert vtherm.current_temperature == 18.9
|
|
||||||
assert vtherm.valve_open_percent == 13 # 0.3*0.1 + 0.1*1
|
|
||||||
|
|
||||||
|
|
||||||
assert mock_service_call.call_count == 3
|
|
||||||
mock_service_call.assert_has_calls(
|
|
||||||
[
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 13}, target={'entity_id': 'number.mock_opening_degree'}),
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 87}, target={'entity_id': 'number.mock_closing_degree'}),
|
|
||||||
# 6 = 18 (room) - 15 (current of underlying) + 3 (current offset)
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 6.899999999999999}, target={'entity_id': 'number.mock_offset_calibration'})
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# set the opening to 13%
|
|
||||||
mock_get_state_side_effect.add_or_update_side_effect(
|
|
||||||
"number.mock_opening_degree",
|
|
||||||
State(
|
|
||||||
"number.mock_opening_degree", "13", {"min": 0, "max": 100}
|
|
||||||
))
|
|
||||||
|
|
||||||
assert vtherm.hvac_action is HVACAction.HEATING
|
|
||||||
assert vtherm.is_device_active is True
|
|
||||||
|
|
||||||
# 3. Stop heating 21 > 19
|
|
||||||
now = now + timedelta(minutes=2)
|
|
||||||
vtherm._set_now(now)
|
|
||||||
# fmt: off
|
|
||||||
with patch("custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event") as mock_send_event, \
|
|
||||||
patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call,\
|
|
||||||
patch("homeassistant.core.StateMachine.get", side_effect=mock_get_state_side_effect.get_side_effects()) as mock_get_state:
|
|
||||||
# fmt: on
|
|
||||||
# set the offset to 3
|
|
||||||
mock_get_state_side_effect.add_or_update_side_effect(
|
|
||||||
"number.mock_offset_calibration",
|
|
||||||
State(
|
|
||||||
"number.mock_offset_calibration", "3", {"min": -12, "max": 12}
|
|
||||||
))
|
|
||||||
|
|
||||||
await send_temperature_change_event(vtherm, 21, now, True)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert vtherm.hvac_mode is HVACMode.HEAT
|
|
||||||
assert vtherm.preset_mode is PRESET_COMFORT
|
|
||||||
assert vtherm.target_temperature == 19
|
|
||||||
assert vtherm.current_temperature == 21
|
|
||||||
assert vtherm.valve_open_percent == 0 # 0.3* (-2) + 0.1*1
|
|
||||||
|
|
||||||
|
|
||||||
assert mock_service_call.call_count == 3
|
|
||||||
mock_service_call.assert_has_calls(
|
|
||||||
[
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree'}),
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree'}),
|
|
||||||
# 6 = 18 (room) - 15 (current of underlying) + 3 (current offset)
|
|
||||||
call(domain='number', service='set_value', service_data={'value': 9.0}, target={'entity_id': 'number.mock_offset_calibration'})
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# set the opening to 13%
|
|
||||||
mock_get_state_side_effect.add_or_update_side_effect(
|
|
||||||
"number.mock_opening_degree",
|
|
||||||
State(
|
|
||||||
"number.mock_opening_degree", "0", {"min": 0, "max": 100}
|
|
||||||
))
|
|
||||||
|
|
||||||
assert vtherm.hvac_action is HVACAction.OFF
|
|
||||||
assert vtherm.is_device_active is False
|
assert vtherm.is_device_active is False
|
||||||
|
assert vtherm.valve_open_percent == 0
|
||||||
|
|
||||||
|
# the underlying set temperature call and the call to the valve
|
||||||
|
assert mock_service_call.call_count == 8
|
||||||
await hass.async_block_till_done()
|
mock_service_call.assert_has_calls([
|
||||||
|
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate1', 'temperature': 17.2}),
|
||||||
|
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate2', 'temperature': 17.2}),
|
||||||
|
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree1'}),
|
||||||
|
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree1'}),
|
||||||
|
call(domain='number', service='set_value', service_data={'value': 3.0}, target={'entity_id': 'number.mock_offset_calibration1'}),
|
||||||
|
call(domain='number', service='set_value', service_data={'value': 0}, target={'entity_id': 'number.mock_opening_degree2'}),
|
||||||
|
call(domain='number', service='set_value', service_data={'value': 100}, target={'entity_id': 'number.mock_closing_degree2'}),
|
||||||
|
call(domain='number', service='set_value', service_data={'value': 12}, target={'entity_id': 'number.mock_offset_calibration2'})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from custom_components.versatile_thermostat.base_thermostat import BaseThermosta
|
|||||||
from custom_components.versatile_thermostat.thermostat_switch import (
|
from custom_components.versatile_thermostat.thermostat_switch import (
|
||||||
ThermostatOverSwitch,
|
ThermostatOverSwitch,
|
||||||
)
|
)
|
||||||
from custom_components.versatile_thermostat.commons import NowClass
|
from custom_components.versatile_thermostat.const import NowClass
|
||||||
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
|
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
|
||||||
|
|
||||||
from .commons import *
|
from .commons import *
|
||||||
|
|||||||
Reference in New Issue
Block a user