Add regulation limitations

This commit is contained in:
Jean-Marc Collin
2023-10-31 09:07:38 +00:00
parent 49377de248
commit 9d099e3169
13 changed files with 245 additions and 103 deletions

View File

@@ -1219,7 +1219,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self.async_control_heating(force=True)
async def _async_internal_set_temperature(self, temperature):
"""Set the target temperature and the target temperature of underlying climate if any"""
"""Set the target temperature and the target temperature of underlying climate if any
For testing purpose you can pass an event_timestamp.
"""
self._target_temp = temperature
return
@@ -2247,7 +2249,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self.async_control_heating()
self.update_custom_attributes()
#PR - Adding Window ByPass
async def service_set_window_bypass_state(self, window_bypass):
"""Called by a service call:
service: versatile_thermostat.set_window_bypass

View File

@@ -24,12 +24,12 @@ from .const import (
SERVICE_SET_PRESENCE,
SERVICE_SET_PRESET_TEMPERATURE,
SERVICE_SET_SECURITY,
#PR - Adding Window ByPass
SERVICE_SET_WINDOW_BYPASS,
SERVICE_SET_AUTO_REGULATION_MODE,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE,
CONF_THERMOSTAT_VALVE
)
from .thermostat_switch import ThermostatOverSwitch
@@ -99,7 +99,6 @@ async def async_setup_entry(
"service_set_security",
)
#PR - Adding Window ByPass
platform.async_register_entity_service(
SERVICE_SET_WINDOW_BYPASS,
{
@@ -108,3 +107,11 @@ async def async_setup_entry(
},
"service_set_window_bypass_state",
)
platform.async_register_entity_service(
SERVICE_SET_AUTO_REGULATION_MODE,
{
vol.Required("auto_regulation_mode"): vol.In(["None", "Light", "Medium", "Strong"]),
},
"service_set_auto_regulation_mode",
)

View File

@@ -1,18 +1,34 @@
""" Some usefull commons class """
import logging
from datetime import timedelta
from datetime import timedelta, datetime
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
_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))
class VersatileThermostatBaseEntity(Entity):
"""A base class for all entities"""
@@ -98,7 +114,7 @@ class VersatileThermostatBaseEntity(Entity):
await try_find_climate(None)
@callback
async def async_my_climate_changed(self, event: Event):
async def async_my_climate_changed(self, event: Event): # pylint: disable=unused-argument
"""Called when my climate have change
This method aims to be overriden to take the status change
"""

View File

@@ -102,6 +102,8 @@ from .const import (
CONF_AUTO_REGULATION_MODES,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
UnknownEntity,
WindowOpenDetectionMethod,
)
@@ -264,6 +266,9 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
options=CONF_AUTO_REGULATION_MODES, translation_key="auto_regulation_mode"
)
),
vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=0.5): vol.Coerce(float),
vol.Optional(CONF_AUTO_REGULATION_PERIOD_MIN, default=5): cv.positive_int
}
)

View File

@@ -88,6 +88,8 @@ CONF_AUTO_REGULATION_NONE= "auto_regulation_none"
CONF_AUTO_REGULATION_LIGHT= "auto_regulation_light"
CONF_AUTO_REGULATION_MEDIUM= "auto_regulation_medium"
CONF_AUTO_REGULATION_STRONG= "auto_regulation_strong"
CONF_AUTO_REGULATION_DTEMP="auto_regulation_dtemp"
CONF_AUTO_REGULATION_PERIOD_MIN="auto_regulation_periode_min"
CONF_PRESETS = {
p: f"{p}_temp"
@@ -189,7 +191,9 @@ ALL_CONF = (
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_AUTO_REGULATION_MODE
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN
]
+ CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES
@@ -210,8 +214,8 @@ SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
SERVICE_SET_SECURITY = "set_security"
#PR - Adding Window ByPass
SERVICE_SET_WINDOW_BYPASS = "set_window_bypass"
SERVICE_SET_AUTO_REGULATION_MODE = "set_auto_regulation_mode"
DEFAULT_SECURITY_MIN_ON_PERCENT = 0.5
DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1

View File

@@ -137,4 +137,25 @@ set_window_bypass:
advanced: false
default: true
selector:
boolean:
boolean:
set_auto_regulation_mode:
name: Set Auto Regulation mode
description: Change the mode of self-regulation (only for VTherm over climate)
target:
entity:
integration: versatile_thermostat
fields:
auto_regulation_mode:
name: Auto regulation mode
description: Possible values
required: true
advanced: false
default: true
selector:
select:
options:
- "None"
- "Light"
- "Medium"
- "Strong"

View File

@@ -39,7 +39,9 @@
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation"
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -56,7 +58,9 @@
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature"
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
}
},
"tpi": {
@@ -202,7 +206,9 @@
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation"
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -219,7 +225,9 @@
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature"
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
}
},
"tpi": {

View File

@@ -1,13 +1,14 @@
# pylint: disable=line-too-long
""" A climate over switch classe """
import logging
from datetime import timedelta
from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.event import async_track_state_change_event, async_track_time_interval
from homeassistant.components.climate import HVACAction, HVACMode
from .commons import NowClass
from .base_thermostat import BaseThermostat
from .pi_algorithm import PITemperatureRegulator
@@ -22,6 +23,8 @@ from .const import (
CONF_AUTO_REGULATION_LIGHT,
CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
RegulationParamLight,
RegulationParamMedium,
RegulationParamStrong
@@ -33,9 +36,12 @@ _LOGGER = logging.getLogger(__name__)
class ThermostatOverClimate(BaseThermostat):
"""Representation of a base class for a Versatile Thermostat over a climate"""
_regulation_mode:str = None
_auto_regulation_mode:str = None
_regulation_algo = None
_regulated_target_temp: float = None
_auto_regulation_dtemp: float = None
_auto_regulation_period_min: int = None
_last_regulation_change: datetime = None
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union(frozenset(
{
@@ -48,6 +54,7 @@ class ThermostatOverClimate(BaseThermostat):
# 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)
@property
def is_over_climate(self) -> bool:
@@ -84,17 +91,31 @@ class ThermostatOverClimate(BaseThermostat):
async def _send_regulated_temperature(self):
""" Sends the regulated temperature to all underlying """
new_regulated_temp = self._regulation_algo.calculate_regulated_temperature(self.current_temperature, self._cur_ext_temp)
if new_regulated_temp != self._regulated_target_temp:
_LOGGER.info("%s - Regulated temp have changed to %.1f. Resend it to underlyings", self, new_regulated_temp)
self._regulated_target_temp = new_regulated_temp
if not self._regulated_target_temp:
self._regulated_target_temp = self.target_temperature
for under in self._underlyings:
await under.set_temperature(
self.regulated_target_temp, self._attr_max_temp, self._attr_min_temp
)
else:
_LOGGER.debug("%s - No change on regulated temperature (%.1f)", self, self._regulated_target_temp)
new_regulated_temp = self._regulation_algo.calculate_regulated_temperature(self.current_temperature, self._cur_ext_temp)
dtemp = new_regulated_temp - self._regulated_target_temp
if abs(dtemp) < self._auto_regulation_dtemp:
_LOGGER.info("!!!!! %s - dtemp (%.1f) is < %.1f -> forget the regulation send", self, dtemp, self._auto_regulation_dtemp)
return
now:datetime = NowClass.get_now(self._hass)
period = float((now - self._last_regulation_change).total_seconds()) / 60.
if period < self._auto_regulation_period_min:
_LOGGER.debug("%s - period (%.1f) is < %.0f -> forget the regulation send", self, period, self._auto_regulation_period_min)
return
self._regulated_target_temp = new_regulated_temp
_LOGGER.info("!!!!! %s - Regulated temp have changed to %.1f. Resend it to underlyings", self, new_regulated_temp)
self._last_regulation_change = now
for under in self._underlyings:
await under.set_temperature(
self.regulated_target_temp, self._attr_max_temp, self._attr_min_temp
)
@overrides
def post_init(self, entry_infos):
@@ -116,9 +137,17 @@ class ThermostatOverClimate(BaseThermostat):
)
)
self._regulation_mode = entry_infos.get(CONF_AUTO_REGULATION_MODE) if entry_infos.get(CONF_AUTO_REGULATION_MODE) is not None else CONF_AUTO_REGULATION_NONE
self.choose_auto_regulation_mode(
entry_infos.get(CONF_AUTO_REGULATION_MODE) if entry_infos.get(CONF_AUTO_REGULATION_MODE) is not None else CONF_AUTO_REGULATION_NONE
)
if self._regulation_mode == CONF_AUTO_REGULATION_LIGHT:
self._auto_regulation_dtemp = entry_infos.get(CONF_AUTO_REGULATION_DTEMP) if entry_infos.get(CONF_AUTO_REGULATION_DTEMP) is not None else 0.5
self._auto_regulation_period_min = entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) if entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None else 5
def choose_auto_regulation_mode(self, auto_regulation_mode):
""" Choose or change the regulation mode"""
self._auto_regulation_mode = auto_regulation_mode
if self._auto_regulation_mode == CONF_AUTO_REGULATION_LIGHT:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
RegulationParamLight.kp,
@@ -127,7 +156,7 @@ class ThermostatOverClimate(BaseThermostat):
RegulationParamLight.offset_max,
RegulationParamLight.stabilization_threshold,
RegulationParamLight.accumulated_error_threshold)
elif self._regulation_mode == CONF_AUTO_REGULATION_MEDIUM:
elif self._auto_regulation_mode == CONF_AUTO_REGULATION_MEDIUM:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
RegulationParamMedium.kp,
@@ -136,7 +165,7 @@ class ThermostatOverClimate(BaseThermostat):
RegulationParamMedium.offset_max,
RegulationParamMedium.stabilization_threshold,
RegulationParamMedium.accumulated_error_threshold)
elif self._regulation_mode == CONF_AUTO_REGULATION_STRONG:
elif self._auto_regulation_mode == CONF_AUTO_REGULATION_STRONG:
self._regulation_algo = PITemperatureRegulator(
self.target_temperature,
RegulationParamStrong.kp,
@@ -430,9 +459,9 @@ class ThermostatOverClimate(BaseThermostat):
return ret
@property
def regulation_mode(self):
def auto_regulation_mode(self):
""" Get the regulation mode """
return self._regulation_mode
return self._auto_regulation_mode
@property
def regulated_target_temp(self):
@@ -442,7 +471,7 @@ class ThermostatOverClimate(BaseThermostat):
@property
def is_regulated(self):
""" Check if the ThermostatOverClimate is regulated """
return self.regulation_mode != CONF_AUTO_REGULATION_NONE
return self.auto_regulation_mode != CONF_AUTO_REGULATION_NONE
@property
def hvac_modes(self):
@@ -617,3 +646,24 @@ class ThermostatOverClimate(BaseThermostat):
await under.set_swing_mode(swing_mode)
self._swing_mode = swing_mode
self.async_write_ha_state()
async def service_set_auto_regulation_mode(self, auto_regulation_mode):
"""Called by a service call:
service: versatile_thermostat.set_auto_regulation_mode
data:
auto_regulation_mode: [None | Light | Medium | Strong]
target:
entity_id: climate.thermostat_1
"""
_LOGGER.info("%s - Calling service_set_auto_regulation_mode, auto_regulation_mode: %s", self, auto_regulation_mode)
if auto_regulation_mode == "None":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_NONE)
elif auto_regulation_mode == "Light":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_LIGHT)
elif auto_regulation_mode == "Medium":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_MEDIUM)
elif auto_regulation_mode == "Strong":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_STRONG)
await self._send_regulated_temperature()
self.update_custom_attributes()

View File

@@ -39,7 +39,9 @@
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation"
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -56,7 +58,9 @@
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature"
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
}
},
"tpi": {
@@ -202,7 +206,9 @@
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation"
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period"
},
"data_description": {
"heater_entity_id": "Mandatory heater entity id",
@@ -219,7 +225,9 @@
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature"
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send",
"auto_regulation_periode_min": "Duration in minutes between two regulation update"
}
},
"tpi": {

View File

@@ -39,7 +39,9 @@
"valve_entity2_id": "2ème valve number",
"valve_entity3_id": "3ème valve number",
"valve_entity4_id": "4ème valve number",
"auto_regulation_mode": "Auto-régulation"
"auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -56,7 +58,9 @@
"valve_entity2_id": "Entity id de la 2ème valve",
"valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la température cible"
"auto_regulation_mode": "Ajustement automatique de la température cible",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation"
}
},
"tpi": {
@@ -203,7 +207,9 @@
"valve_entity2_id": "2ème valve",
"valve_entity3_id": "3ème valve",
"valve_entity4_id": "4ème valve",
"auto_regulation_mode": "Auto-regulation"
"auto_regulation_mode": "Auto-regulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation"
},
"data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
@@ -220,7 +226,9 @@
"valve_entity2_id": "Entity id de la 2ème valve",
"valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la consigne"
"auto_regulation_mode": "Ajustement automatique de la consigne",
"auto_regulation_dtemp": "Le seuil en ° au-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation"
}
},
"tpi": {

View File

@@ -10,7 +10,6 @@ from homeassistant.core import HomeAssistant, Event, EVENT_STATE_CHANGED, State
from homeassistant.const import UnitOfTemperature, STATE_ON, STATE_OFF, ATTR_TEMPERATURE
from homeassistant.config_entries import ConfigEntryState
from homeassistant.util import dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.components.climate import (
ClimateEntity,
@@ -25,6 +24,7 @@ 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 get_tz, NowClass # pylint: disable=unused-import
from .const import ( # pylint: disable=unused-import
MOCK_TH_OVER_SWITCH_USER_CONFIG,
@@ -478,13 +478,6 @@ async def send_presence_change_event(
await asyncio.sleep(0.1)
return ret
def get_tz(hass: HomeAssistant):
"""Get the current timezone"""
return dt_util.get_time_zone(hass.config.time_zone)
async def send_climate_change_event(
entity: BaseThermostat,
new_hvac_mode: HVACMode,

View File

@@ -53,6 +53,8 @@ from custom_components.versatile_thermostat.const import (
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN
)
MOCK_TH_OVER_SWITCH_USER_CONFIG = {
CONF_NAME: "TheOverSwitchMockName",
@@ -125,7 +127,9 @@ MOCK_TH_OVER_SWITCH_TPI_CONFIG = {
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
CONF_CLIMATE: "climate.mock_climate",
CONF_AC_MODE: False,
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_MEDIUM
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_DTEMP: 0.5,
CONF_AUTO_REGULATION_PERIOD_MIN: 2
}
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG = {
@@ -137,7 +141,9 @@ MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG = {
MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG = {
CONF_CLIMATE: "climate.mock_climate",
CONF_AC_MODE: True,
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_MEDIUM
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_DTEMP: 0.5,
CONF_AUTO_REGULATION_PERIOD_MIN: 1
}
MOCK_PRESETS_CONFIG = {

View File

@@ -20,7 +20,7 @@ from .commons import * # pylint: disable=wildcard-import, unused-wildcard-impor
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_climate_regulation(hass: HomeAssistant, skip_hass_states_is_state):
async def test_over_climate_regulation(hass: HomeAssistant, skip_hass_states_is_state, skip_send_event):
"""Test the regulation of an over climate thermostat"""
entry = MockConfigEntry(
@@ -37,8 +37,11 @@ async def test_over_climate_regulation(hass: HomeAssistant, skip_hass_states_is_
fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {})
# Creates the regulated VTherm over climate
# change temperature so that the heating will start
event_timestamp = now - timedelta(minutes=10)
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
return_value=fake_underlying_climate,
@@ -73,45 +76,48 @@ async def test_over_climate_regulation(hass: HomeAssistant, skip_hass_states_is_
]
assert entity.preset_mode is PRESET_NONE
# Activate the heating by changing HVACMode and temperature
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
):
# 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
assert entity.hvac_action == HVACAction.OFF
# change temperature so that the heating will start
event_timestamp = now - timedelta(minutes=10)
assert entity.regulated_target_temp is entity.min_temp
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 - 7) -> the regulation should occurs
event_timestamp = now - timedelta(minutes=7)
with patch(
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
):
await entity.async_set_temperature(temperature=18)
# set manual target temp
await entity.async_set_temperature(temperature=18)
fake_underlying_climate.set_hvac_action(HVACAction.HEATING) # simulate under heating
assert entity.hvac_action == HVACAction.HEATING
assert entity.preset_mode == PRESET_NONE # Manual mode
fake_underlying_climate.set_hvac_action(HVACAction.HEATING) # simulate under heating
assert entity.hvac_action == HVACAction.HEATING
assert entity.preset_mode == PRESET_NONE # Manual mode
# the regulated temperature should be greater
assert entity.regulated_target_temp > entity.target_temperature
assert entity.regulated_target_temp == 18+2.9 # In medium we could go up to +3 degre
assert entity.hvac_action == HVACAction.HEATING
# the regulated temperature should be greater
assert entity.regulated_target_temp > entity.target_temperature
assert entity.regulated_target_temp == 18+2.2 # In medium we could go up to +3 degre
assert entity.hvac_action == HVACAction.HEATING
# change temperature so that the regulated temperature should slow down
event_timestamp = now - timedelta(minutes=9)
await send_temperature_change_event(entity, 19, event_timestamp)
await send_ext_temperature_change_event(entity, 18, event_timestamp)
event_timestamp = now - timedelta(minutes=5)
with patch(
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
):
await send_temperature_change_event(entity, 22, event_timestamp)
await send_ext_temperature_change_event(entity, 19, event_timestamp)
# the regulated temperature should be under
assert entity.regulated_target_temp < entity.target_temperature
assert entity.regulated_target_temp == 18-0.1
# the regulated temperature should be under
assert entity.regulated_target_temp < entity.target_temperature
assert entity.regulated_target_temp == 18-0.6
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_climate_regulation_ac_mode(hass: HomeAssistant, skip_hass_states_is_state):
async def test_over_climate_regulation_ac_mode(hass: HomeAssistant, skip_hass_states_is_state, skip_send_event):
"""Test the regulation of an over climate thermostat"""
entry = MockConfigEntry(
@@ -128,8 +134,11 @@ async def test_over_climate_regulation_ac_mode(hass: HomeAssistant, skip_hass_st
fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {})
# Creates the regulated VTherm over climate
# change temperature so that the heating will start
event_timestamp = now - timedelta(minutes=10)
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
return_value=fake_underlying_climate,
@@ -164,47 +173,53 @@ async def test_over_climate_regulation_ac_mode(hass: HomeAssistant, skip_hass_st
]
assert entity.preset_mode is PRESET_NONE
# Activate the heating by changing HVACMode and temperature
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
):
# 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
assert entity.hvac_action == HVACAction.OFF
# change temperature so that the heating will start
event_timestamp = now - timedelta(minutes=10)
await send_temperature_change_event(entity, 30, event_timestamp)
await send_ext_temperature_change_event(entity, 35, event_timestamp)
# set manual target temp
await entity.async_set_temperature(temperature=25)
event_timestamp = now - timedelta(minutes=7)
with patch(
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
):
await entity.async_set_temperature(temperature=25)
fake_underlying_climate.set_hvac_action(HVACAction.COOLING) # simulate under heating
assert entity.hvac_action == HVACAction.COOLING
assert entity.preset_mode == PRESET_NONE # Manual mode
fake_underlying_climate.set_hvac_action(HVACAction.COOLING) # simulate under heating
assert entity.hvac_action == HVACAction.COOLING
assert entity.preset_mode == PRESET_NONE # Manual mode
# the regulated temperature should be lower
assert entity.regulated_target_temp < entity.target_temperature
assert entity.regulated_target_temp == 25-3 # In medium we could go up to -3 degre
assert entity.hvac_action == HVACAction.COOLING
# the regulated temperature should be lower
assert entity.regulated_target_temp < entity.target_temperature
assert entity.regulated_target_temp == 25-3 # In medium we could go up to -3 degre
assert entity.hvac_action == HVACAction.COOLING
# change temperature so that the regulated temperature should slow down
event_timestamp = now - timedelta(minutes=9)
await send_temperature_change_event(entity, 26, event_timestamp)
await send_ext_temperature_change_event(entity, 35, event_timestamp)
event_timestamp = now - timedelta(minutes=5)
with patch(
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
):
await send_temperature_change_event(entity, 26, event_timestamp)
await send_ext_temperature_change_event(entity, 35, event_timestamp)
# the regulated temperature should be under
assert entity.regulated_target_temp < entity.target_temperature
assert entity.regulated_target_temp == 25-2.7
# the regulated temperature should be under
assert entity.regulated_target_temp < entity.target_temperature
assert entity.regulated_target_temp == 25-2.3
# change temperature so that the regulated temperature should slow down
event_timestamp = now - timedelta(minutes=9)
await send_temperature_change_event(entity, 20, event_timestamp)
await send_ext_temperature_change_event(entity, 30, event_timestamp)
# 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", return_value=event_timestamp
):
await send_temperature_change_event(entity, 20, event_timestamp)
await send_ext_temperature_change_event(entity, 25, event_timestamp)
# the regulated temperature should be greater
assert entity.regulated_target_temp > entity.target_temperature
assert entity.regulated_target_temp == 25+1.8
# the regulated temperature should be greater
assert entity.regulated_target_temp > entity.target_temperature
assert entity.regulated_target_temp == 25+0.4