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 typing import Any, TypeVar, Generic
|
||||
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
callback,
|
||||
@@ -80,13 +79,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
ConfigData = MappingProxyType[str, Any]
|
||||
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]):
|
||||
"""Representation of a base class for all Versatile Thermostat device."""
|
||||
|
||||
@@ -2293,7 +2285,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
||||
@property
|
||||
def now(self) -> datetime:
|
||||
"""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:
|
||||
"""Check if last temperature date is too long"""
|
||||
|
||||
@@ -3,38 +3,20 @@
|
||||
# pylint: disable=line-too-long
|
||||
|
||||
import logging
|
||||
from datetime import timedelta, datetime
|
||||
from datetime import timedelta
|
||||
from homeassistant.core import HomeAssistant, callback, Event
|
||||
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
||||
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 .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError
|
||||
|
||||
_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:
|
||||
"""Round a number to the nearest x (which should be decimal but not null)
|
||||
Example:
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
import logging
|
||||
import math
|
||||
from typing import Literal
|
||||
from datetime import datetime
|
||||
|
||||
from enum import Enum
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import CONF_NAME, Platform
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
@@ -17,6 +19,7 @@ from homeassistant.components.climate import (
|
||||
)
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .prop_algorithm import (
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""Error to indicate there is an unknown entity_id given."""
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.components.climate import (
|
||||
ClimateEntityFeature,
|
||||
)
|
||||
|
||||
from .commons import NowClass, round_to_nearest
|
||||
from .commons import round_to_nearest
|
||||
from .base_thermostat import BaseThermostat, ConfigData
|
||||
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__(hass, unique_id, name, entry_infos)
|
||||
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
|
||||
def post_init(self, config_entry: ConfigData):
|
||||
@@ -206,16 +206,18 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
force,
|
||||
)
|
||||
|
||||
now: datetime = NowClass.get_now(self._hass)
|
||||
period = float((now - self._last_regulation_change).total_seconds()) / 60.0
|
||||
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,
|
||||
if self._last_regulation_change is not None:
|
||||
period = (
|
||||
float((self.now - self._last_regulation_change).total_seconds()) / 60.0
|
||||
)
|
||||
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:
|
||||
self._regulated_target_temp = self.target_temperature
|
||||
@@ -253,7 +255,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||
new_regulated_temp,
|
||||
)
|
||||
|
||||
self._last_regulation_change = now
|
||||
self._last_regulation_change = self.now
|
||||
for under in self._underlyings:
|
||||
# issue 348 - use device temperature if configured as offset
|
||||
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.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.commons import ( # pylint: disable=unused-import
|
||||
get_tz,
|
||||
NowClass,
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
), patch(
|
||||
"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
|
||||
event_timestamp = now - timedelta(minutes=7)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
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
|
||||
event_timestamp = now - timedelta(minutes=5)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=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)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
), patch(
|
||||
"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
|
||||
event_timestamp = now - timedelta(minutes=7)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
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
|
||||
event_timestamp = now - timedelta(minutes=5)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=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
|
||||
event_timestamp = now - timedelta(minutes=3)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=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)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
), patch(
|
||||
"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_regulated is True
|
||||
|
||||
entity._set_now(event_timestamp)
|
||||
# Will initialize the _last_regulation_change
|
||||
# Activate the heating by changing HVACMode and temperature
|
||||
# Select a hvacmode, presence and preset
|
||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||
assert entity.hvac_mode is HVACMode.HEAT
|
||||
await entity.async_set_temperature(temperature=17)
|
||||
|
||||
# it is cold today
|
||||
await send_temperature_change_event(entity, 15, 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)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
await entity.async_set_temperature(temperature=18)
|
||||
entity._set_now(event_timestamp)
|
||||
await entity.async_set_temperature(temperature=18)
|
||||
|
||||
fake_underlying_climate.set_hvac_action(
|
||||
HVACAction.HEATING
|
||||
) # simulate under heating
|
||||
assert entity.hvac_action == HVACAction.HEATING
|
||||
fake_underlying_climate.set_hvac_action(
|
||||
HVACAction.HEATING
|
||||
) # simulate under heating
|
||||
assert entity.hvac_action == HVACAction.HEATING
|
||||
|
||||
# the regulated temperature will change because when we set temp manually it is forced
|
||||
assert entity.regulated_target_temp == 19.5
|
||||
# the regulated temperature will not change because when we set temp manually it is forced
|
||||
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)
|
||||
with patch(
|
||||
"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
|
||||
entity._set_now(event_timestamp)
|
||||
|
||||
# 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)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
await send_temperature_change_event(entity, 16, event_timestamp)
|
||||
await send_ext_temperature_change_event(entity, 10, event_timestamp)
|
||||
entity._set_now(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
|
||||
assert entity.regulated_target_temp <= old_regulated_temp
|
||||
# the regulated temperature should be under
|
||||
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)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||
await send_ext_temperature_change_event(entity, 12, event_timestamp)
|
||||
entity._set_now(event_timestamp)
|
||||
await send_ext_temperature_change_event(entity, 12, event_timestamp)
|
||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||
|
||||
# the regulated should have been done
|
||||
assert entity.regulated_target_temp != old_regulated_temp
|
||||
assert entity.regulated_target_temp >= entity.target_temperature
|
||||
assert (
|
||||
entity.regulated_target_temp == 17 + 1.5
|
||||
) # 0.7 without round_to_nearest
|
||||
# the regulated should have been done
|
||||
assert entity.regulated_target_temp != old_regulated_temp
|
||||
assert entity.regulated_target_temp >= entity.target_temperature
|
||||
assert entity.regulated_target_temp == 17 + 1.5 # 0.7 without round_to_nearest
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
), patch(
|
||||
"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)
|
||||
event_timestamp = now - timedelta(minutes=7)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||
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)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||
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)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||
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)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
), patch(
|
||||
"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
|
||||
event_timestamp = now - timedelta(minutes=17)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
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
|
||||
event_timestamp = now - timedelta(minutes=15)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=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
|
||||
event_timestamp = now - timedelta(minutes=13)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=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)
|
||||
event_timestamp = now - timedelta(minutes=10)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
"custom_components.versatile_thermostat.const.NowClass.get_now",
|
||||
return_value=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"
|
||||
) as mock_service_call:
|
||||
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.name == "TheOverClimateMockName"
|
||||
@@ -215,16 +202,18 @@ async def test_bug_272(
|
||||
)
|
||||
|
||||
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(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||
# 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)
|
||||
await send_ext_temperature_change_event(entity, 9, event_timestamp)
|
||||
event_timestamp = event_timestamp + timedelta(minutes=3)
|
||||
entity._set_now(event_timestamp)
|
||||
|
||||
# Not in the accepted interval (15-19)
|
||||
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"
|
||||
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
|
||||
# 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_ext_temperature_change_event(entity, 9, event_timestamp)
|
||||
|
||||
# In the accepted interval
|
||||
event_timestamp = event_timestamp + timedelta(minutes=3)
|
||||
entity._set_now(event_timestamp)
|
||||
await entity.async_set_temperature(temperature=20.8)
|
||||
assert mock_service_call.call_count == 1
|
||||
mock_service_call.assert_has_calls(
|
||||
|
||||
@@ -517,6 +517,9 @@ async def test_bug_508(
|
||||
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
|
||||
fake_underlying_climate = MagicMockClimateWithTemperatureRange()
|
||||
|
||||
@@ -545,6 +548,9 @@ async def test_bug_508(
|
||||
# Set the hvac_mode to 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
|
||||
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:
|
||||
# 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)
|
||||
|
||||
# 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
|
||||
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",{
|
||||
"entity_id": "climate.mock_climate",
|
||||
"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
|
||||
|
||||
# 2: set presence on -> should activate the valve and change target
|
||||
# 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,\
|
||||
@@ -432,164 +433,43 @@ async def test_over_climate_valve_multi_presence(
|
||||
|
||||
# the underlying set temperature call and the call to the valve
|
||||
assert mock_service_call.call_count == 8
|
||||
mock_service_call.assert_has_calls(
|
||||
[
|
||||
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': 60}, target={'entity_id': 'number.mock_closing_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': 40}, target={'entity_id': 'number.mock_opening_degree2'}),
|
||||
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': 12}, target={'entity_id': 'number.mock_offset_calibration2'}),
|
||||
call("climate","set_temperature",{
|
||||
"entity_id": "climate.mock_climate1",
|
||||
"temperature": 19,
|
||||
},
|
||||
),
|
||||
call("climate","set_temperature",{
|
||||
"entity_id": "climate.mock_climate2",
|
||||
"temperature": 19,
|
||||
},
|
||||
),
|
||||
mock_service_call.assert_has_calls([
|
||||
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate1', 'temperature': 19.0}),
|
||||
call('climate', 'set_temperature', {'entity_id': 'climate.mock_climate2', 'temperature': 19.0}),
|
||||
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': 60}, 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': 40}, target={'entity_id': 'number.mock_opening_degree2'}),
|
||||
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': 12}, target={'entity_id': 'number.mock_offset_calibration2'})
|
||||
]
|
||||
)
|
||||
|
||||
assert mock_get_state.call_count > 5 # each temp sensor + each valve
|
||||
|
||||
|
||||
# 2. Starts heating slowly (18 vs 19)
|
||||
now = now + timedelta(minutes=1)
|
||||
vtherm._set_now(now)
|
||||
|
||||
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
|
||||
# 3: set presence off -> should deactivate the valve and change target
|
||||
# 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
|
||||
now = now + timedelta(minutes=2) # avoid temporal filter
|
||||
now = now + timedelta(minutes=3)
|
||||
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()
|
||||
|
||||
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.valve_open_percent == 0
|
||||
|
||||
|
||||
|
||||
await hass.async_block_till_done()
|
||||
# the underlying set temperature call and the call to the valve
|
||||
assert mock_service_call.call_count == 8
|
||||
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 (
|
||||
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 .commons import *
|
||||
|
||||
Reference in New Issue
Block a user