Issue #199 - persist and don't reset the accumulation error
This commit is contained in:
@@ -693,6 +693,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
EVENT_HOMEASSISTANT_START, _async_startup_internal
|
||||
)
|
||||
|
||||
def restore_specific_previous_state(self, old_state):
|
||||
"""Should be overriden in each specific thermostat
|
||||
if a specific previous state or attribute should be
|
||||
restored
|
||||
"""
|
||||
|
||||
async def get_my_previous_state(self):
|
||||
"""Try to get my previou state"""
|
||||
# Check If we have an old state
|
||||
@@ -738,6 +744,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY)
|
||||
if old_total_energy:
|
||||
self._total_energy = old_total_energy
|
||||
|
||||
self.restore_specific_previous_state(old_state)
|
||||
else:
|
||||
# No previous state, try and restore defaults
|
||||
if self._target_temp is None:
|
||||
|
||||
@@ -40,6 +40,10 @@ class PITemperatureRegulator:
|
||||
"""Reset the accumulated error"""
|
||||
self.accumulated_error = 0
|
||||
|
||||
def set_accumulated_error(self, accumulated_error):
|
||||
"""Allow to persist and restore the accumulated_error"""
|
||||
self.accumulated_error = accumulated_error
|
||||
|
||||
def set_target_temp(self, target_temp):
|
||||
"""Set the new target_temp"""
|
||||
self.target_temp = target_temp
|
||||
@@ -85,9 +89,10 @@ class PITemperatureRegulator:
|
||||
total_offset = min(self.offset_max, max(-self.offset_max, total_offset))
|
||||
|
||||
# If temperature is near the target_temp, reset the accumulated_error
|
||||
if abs(error) < self.stabilization_threshold:
|
||||
_LOGGER.debug("Stabilisation")
|
||||
self.accumulated_error = 0
|
||||
# Issue #199 - don't reset the accumulation error
|
||||
# if abs(error) < self.stabilization_threshold:
|
||||
# _LOGGER.debug("Stabilisation")
|
||||
# self.accumulated_error = 0
|
||||
|
||||
result = round(self.target_temp + total_offset, 1)
|
||||
|
||||
|
||||
@@ -302,6 +302,18 @@ class ThermostatOverClimate(BaseThermostat):
|
||||
)
|
||||
)
|
||||
|
||||
@overrides
|
||||
def restore_specific_previous_state(self, old_state):
|
||||
"""Restore my specific attributes from previous state"""
|
||||
old_error = old_state.attributes.get("regulation_accumulated_error")
|
||||
if old_error:
|
||||
self._regulation_algo.set_accumulated_error(old_error)
|
||||
_LOGGER.debug(
|
||||
"%s - Old regulation accumulated_error have been restored to %f",
|
||||
self,
|
||||
old_error,
|
||||
)
|
||||
|
||||
@overrides
|
||||
def update_custom_attributes(self):
|
||||
"""Custom attributes"""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
|
||||
|
||||
""" Test the normal start of a Thermostat """
|
||||
from unittest.mock import patch #, call
|
||||
from unittest.mock import patch # , call
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -14,13 +14,18 @@ from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DO
|
||||
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
||||
|
||||
# from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
||||
from custom_components.versatile_thermostat.thermostat_climate import ThermostatOverClimate
|
||||
from custom_components.versatile_thermostat.thermostat_climate import (
|
||||
ThermostatOverClimate,
|
||||
)
|
||||
|
||||
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
|
||||
@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, skip_send_event):
|
||||
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(
|
||||
@@ -41,7 +46,8 @@ async def test_over_climate_regulation(hass: HomeAssistant, skip_hass_states_is_
|
||||
event_timestamp = now - timedelta(minutes=10)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
|
||||
"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,
|
||||
@@ -57,7 +63,7 @@ async def test_over_climate_regulation(hass: HomeAssistant, skip_hass_states_is_
|
||||
if entity.entity_id == entity_id:
|
||||
return entity
|
||||
|
||||
entity:ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname")
|
||||
entity: ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname")
|
||||
|
||||
assert entity
|
||||
assert isinstance(entity, ThermostatOverClimate)
|
||||
@@ -90,36 +96,45 @@ async def test_over_climate_regulation(hass: HomeAssistant, skip_hass_states_is_
|
||||
# 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
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
await entity.async_set_temperature(temperature=18)
|
||||
|
||||
fake_underlying_climate.set_hvac_action(HVACAction.HEATING) # simulate under heating
|
||||
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
|
||||
assert entity.preset_mode == PRESET_NONE # Manual mode
|
||||
|
||||
# the regulated temperature should be greater
|
||||
assert entity.regulated_target_temp > entity.target_temperature
|
||||
# In medium we could go up to +3 degre
|
||||
# normally the calcul gives 18 + 2.2 but we round the result to the nearest 0.5 which is 2.0
|
||||
assert entity.regulated_target_temp == 18+2.0
|
||||
assert entity.regulated_target_temp == 18 + 1.5
|
||||
assert entity.hvac_action == HVACAction.HEATING
|
||||
|
||||
# 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", return_value=event_timestamp
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
await send_temperature_change_event(entity, 23, 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.5 # normally 0.6 but round_to_nearest gives 0.5
|
||||
assert (
|
||||
entity.regulated_target_temp == 18 - 2
|
||||
) # normally 0.6 but round_to_nearest gives 0.5
|
||||
|
||||
|
||||
@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, skip_send_event):
|
||||
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(
|
||||
@@ -140,7 +155,8 @@ async def test_over_climate_regulation_ac_mode(hass: HomeAssistant, skip_hass_st
|
||||
event_timestamp = now - timedelta(minutes=10)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
|
||||
"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,
|
||||
@@ -156,7 +172,7 @@ async def test_over_climate_regulation_ac_mode(hass: HomeAssistant, skip_hass_st
|
||||
if entity.entity_id == entity_id:
|
||||
return entity
|
||||
|
||||
entity:ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname")
|
||||
entity: ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname")
|
||||
|
||||
assert entity
|
||||
assert isinstance(entity, ThermostatOverClimate)
|
||||
@@ -185,53 +201,66 @@ async def test_over_climate_regulation_ac_mode(hass: HomeAssistant, skip_hass_st
|
||||
await send_temperature_change_event(entity, 30, event_timestamp)
|
||||
await send_ext_temperature_change_event(entity, 35, event_timestamp)
|
||||
|
||||
|
||||
# set manual target temp
|
||||
event_timestamp = now - timedelta(minutes=7)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
|
||||
"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
|
||||
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
|
||||
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.regulated_target_temp == 25 - 2.5
|
||||
) # 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=5)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
|
||||
"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 # +2.3 without round_to_nearest
|
||||
assert (
|
||||
entity.regulated_target_temp == 25 - 1
|
||||
) # +2.3 without round_to_nearest
|
||||
|
||||
# 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
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
await send_temperature_change_event(entity, 18, 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+0.5 # +0.4 without round_to_nearest
|
||||
assert (
|
||||
entity.regulated_target_temp == 25 + 3
|
||||
) # +0.4 without round_to_nearest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_over_climate_regulation_limitations(hass: HomeAssistant, skip_hass_states_is_state, skip_send_event):
|
||||
async def test_over_climate_regulation_limitations(
|
||||
hass: HomeAssistant, skip_hass_states_is_state, skip_send_event
|
||||
):
|
||||
"""Test the limitations of the regulation of an over climate thermostat:
|
||||
1. test the period_min parameter: do not send regulation event too frequently
|
||||
2. test the dtemp parameter: do not send regulation event if offset temp is lower than dtemp
|
||||
1. test the period_min parameter: do not send regulation event too frequently
|
||||
2. test the dtemp parameter: do not send regulation event if offset temp is lower than dtemp
|
||||
"""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
@@ -252,7 +281,8 @@ async def test_over_climate_regulation_limitations(hass: HomeAssistant, skip_has
|
||||
event_timestamp = now - timedelta(minutes=20)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
|
||||
"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,
|
||||
@@ -268,7 +298,7 @@ async def test_over_climate_regulation_limitations(hass: HomeAssistant, skip_has
|
||||
if entity.entity_id == entity_id:
|
||||
return entity
|
||||
|
||||
entity:ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname")
|
||||
entity: ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname")
|
||||
|
||||
assert entity
|
||||
assert isinstance(entity, ThermostatOverClimate)
|
||||
@@ -289,30 +319,37 @@ async def test_over_climate_regulation_limitations(hass: HomeAssistant, skip_has
|
||||
# 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
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
await entity.async_set_temperature(temperature=18)
|
||||
|
||||
fake_underlying_climate.set_hvac_action(HVACAction.HEATING) # simulate under 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 == 20.
|
||||
assert entity.regulated_target_temp == 19.5
|
||||
|
||||
# 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
|
||||
"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+1 # In strong we could go up to +3 degre. 0.7 without round_to_nearest
|
||||
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)
|
||||
event_timestamp = now - timedelta(minutes=15)
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now", return_value=event_timestamp
|
||||
"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)
|
||||
@@ -323,12 +360,15 @@ async def test_over_climate_regulation_limitations(hass: HomeAssistant, skip_has
|
||||
# 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
|
||||
"custom_components.versatile_thermostat.commons.NowClass.get_now",
|
||||
return_value=event_timestamp,
|
||||
):
|
||||
await send_temperature_change_event(entity, 18, event_timestamp)
|
||||
await send_temperature_change_event(entity, 17, event_timestamp)
|
||||
await send_ext_temperature_change_event(entity, 12, 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
|
||||
assert (
|
||||
entity.regulated_target_temp == 17 + 0.5
|
||||
) # 0.7 without round_to_nearest
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# pylint: disable=unused-argument, line-too-long
|
||||
""" Test the Versatile Thermostat config flow """
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
@@ -29,7 +30,9 @@ async def test_show_form(hass: HomeAssistant) -> None:
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_user_config_flow_over_switch(hass: HomeAssistant, skip_hass_states_get): # pylint: disable=unused-argument
|
||||
async def test_user_config_flow_over_switch(
|
||||
hass: HomeAssistant, skip_hass_states_get
|
||||
): # pylint: disable=unused-argument
|
||||
"""Test the config flow with all thermostat_over_switch features"""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
@@ -128,7 +131,9 @@ async def test_user_config_flow_over_switch(hass: HomeAssistant, skip_hass_state
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_user_config_flow_over_climate(hass: HomeAssistant, skip_hass_states_get): # pylint: disable=unused-argument
|
||||
async def test_user_config_flow_over_climate(
|
||||
hass: HomeAssistant, skip_hass_states_get
|
||||
): # pylint: disable=unused-argument
|
||||
"""Test the config flow with all thermostat_over_climate features and no additional features"""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
@@ -184,7 +189,9 @@ async def test_user_config_flow_over_climate(hass: HomeAssistant, skip_hass_stat
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_user_config_flow_window_auto_ok(
|
||||
hass: HomeAssistant, skip_hass_states_get, skip_control_heating # pylint: disable=unused-argument
|
||||
hass: HomeAssistant,
|
||||
skip_hass_states_get,
|
||||
skip_control_heating, # pylint: disable=unused-argument
|
||||
):
|
||||
"""Test the config flow with only window auto feature"""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
@@ -353,7 +360,9 @@ async def test_user_config_flow_window_auto_ko(
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||
async def test_user_config_flow_over_4_switches(
|
||||
hass: HomeAssistant, skip_hass_states_get, skip_control_heating # pylint: disable=unused-argument
|
||||
hass: HomeAssistant,
|
||||
skip_hass_states_get,
|
||||
skip_control_heating, # pylint: disable=unused-argument
|
||||
):
|
||||
"""Test the config flow with 4 switchs thermostat_over_switch features"""
|
||||
|
||||
@@ -369,7 +378,7 @@ async def test_user_config_flow_over_4_switches(
|
||||
CONF_USE_WINDOW_FEATURE: False,
|
||||
CONF_USE_MOTION_FEATURE: False,
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
}
|
||||
|
||||
TYPE_CONFIG = { # pylint: disable=wildcard-import, invalid-name
|
||||
@@ -427,15 +436,11 @@ async def test_user_config_flow_over_4_switches(
|
||||
)
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert (
|
||||
result["data"]
|
||||
== SOURCE_CONFIG
|
||||
| TYPE_CONFIG
|
||||
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
|
||||
| MOCK_PRESETS_CONFIG
|
||||
| MOCK_ADVANCED_CONFIG
|
||||
| { CONF_INVERSE_SWITCH: False }
|
||||
)
|
||||
assert result[
|
||||
"data"
|
||||
] == SOURCE_CONFIG | TYPE_CONFIG | MOCK_TH_OVER_SWITCH_TPI_CONFIG | MOCK_PRESETS_CONFIG | MOCK_ADVANCED_CONFIG | {
|
||||
CONF_INVERSE_SWITCH: False
|
||||
}
|
||||
assert result["result"]
|
||||
assert result["result"].domain == DOMAIN
|
||||
assert result["result"].version == 1
|
||||
|
||||
Reference in New Issue
Block a user