From d6ec7a86bef257d16560385153d436ef333c8613 Mon Sep 17 00:00:00 2001 From: Jean-Marc Collin Date: Sun, 29 Sep 2024 09:54:39 +0200 Subject: [PATCH] issue #506 - Add some check to verify tpi algorithm parameters are correctly set. (#512) Co-authored-by: Jean-Marc Collin --- .../versatile_thermostat/prop_algorithm.py | 28 ++++ tests/test_start.py | 3 + tests/test_temp_number.py | 12 ++ tests/test_tpi.py | 125 +++++++++++++++++- 4 files changed, 167 insertions(+), 1 deletion(-) diff --git a/custom_components/versatile_thermostat/prop_algorithm.py b/custom_components/versatile_thermostat/prop_algorithm.py index c129262..12b3b1e 100644 --- a/custom_components/versatile_thermostat/prop_algorithm.py +++ b/custom_components/versatile_thermostat/prop_algorithm.py @@ -14,6 +14,10 @@ PROPORTIONAL_MIN_DURATION_SEC = 10 FUNCTION_TYPE = [PROPORTIONAL_FUNCTION_ATAN, PROPORTIONAL_FUNCTION_LINEAR] +def is_number(value): + return isinstance(value, (int, float)) + + class PropAlgorithm: """This class aims to do all calculation of the Proportional alogorithm""" @@ -36,6 +40,30 @@ class PropAlgorithm: cycle_min, minimal_activation_delay, ) + + # Issue 506 - check parameters + if ( + vtherm_entity_id is None + or not is_number(tpi_coef_int) + or not is_number(tpi_coef_ext) + or not is_number(cycle_min) + or not is_number(minimal_activation_delay) + or function_type != PROPORTIONAL_FUNCTION_TPI + ): + _LOGGER.error( + "%s - configuration is wrong. function_type=%s, entity_id is %s, tpi_coef_int is %s, tpi_coef_ext is %s, cycle_min is %s, minimal_activation_delay is %s", + vtherm_entity_id, + function_type, + vtherm_entity_id, + tpi_coef_int, + tpi_coef_ext, + cycle_min, + minimal_activation_delay, + ) + raise TypeError( + f"TPI parameters are not set correctly. VTherm will not work as expected. Please reconfigure it correctly. See previous log for values" + ) + self._vtherm_entity_id = vtherm_entity_id self._function = function_type self._tpi_coef_int = tpi_coef_int diff --git a/tests/test_start.py b/tests/test_start.py index c435ab5..47dca6e 100644 --- a/tests/test_start.py +++ b/tests/test_start.py @@ -254,6 +254,9 @@ async def test_over_switch_deactivate_preset( CONF_HEATER_KEEP_ALIVE: 0, CONF_SECURITY_DELAY_MIN: 10, CONF_MINIMAL_ACTIVATION_DELAY: 10, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, + CONF_TPI_COEF_INT: 0.6, + CONF_TPI_COEF_EXT: 0.01, }, ) diff --git a/tests/test_temp_number.py b/tests/test_temp_number.py index 17e744c..3672458 100644 --- a/tests/test_temp_number.py +++ b/tests/test_temp_number.py @@ -79,6 +79,7 @@ async def test_add_number_for_central_config( CONF_SECURITY_MIN_ON_PERCENT: 0.5, CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2, CONF_USE_CENTRAL_BOILER_FEATURE: False, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, } | temps, ) @@ -156,6 +157,7 @@ async def test_add_number_for_central_config_without_temp( CONF_TEMP_MAX: 30, CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_WINDOW_DELAY: 15, CONF_WINDOW_AUTO_OPEN_THRESHOLD: 4, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1, @@ -250,6 +252,7 @@ async def test_add_number_for_central_config_without_temp_ac_mode( CONF_AC_MODE: True, CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_WINDOW_DELAY: 15, CONF_WINDOW_AUTO_OPEN_THRESHOLD: 4, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1, @@ -343,6 +346,7 @@ async def test_add_number_for_central_config_without_temp_restore( CONF_AC_MODE: False, CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_WINDOW_DELAY: 15, CONF_WINDOW_AUTO_OPEN_THRESHOLD: 4, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1, @@ -441,6 +445,7 @@ async def test_add_number_for_over_switch_use_central( CONF_AC_MODE: False, CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_USE_PRESENCE_CENTRAL_CONFIG: True, CONF_USE_ADVANCED_CENTRAL_CONFIG: True, CONF_USE_MAIN_CENTRAL_CONFIG: True, @@ -666,6 +671,7 @@ async def test_add_number_for_over_switch_use_central_presets_and_restore( CONF_AC_MODE: False, CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_CYCLE_MIN: 5, CONF_HEATER: "switch.mock_switch1", CONF_USE_PRESENCE_FEATURE: True, @@ -788,6 +794,7 @@ async def test_change_central_config_temperature( CONF_TEMP_MAX: 30, CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_CYCLE_MIN: 5, CONF_VALVE: "switch.mock_valve", CONF_USE_PRESENCE_FEATURE: True, @@ -823,6 +830,7 @@ async def test_change_central_config_temperature( CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, CONF_CYCLE_MIN: 5, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_VALVE: "switch.mock_valve", CONF_USE_PRESENCE_FEATURE: True, CONF_USE_PRESENCE_CENTRAL_CONFIG: False, @@ -905,6 +913,7 @@ async def test_change_vtherm_temperature( CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, CONF_CYCLE_MIN: 5, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_VALVE: "switch.mock_valve", CONF_USE_PRESENCE_FEATURE: True, CONF_USE_PRESENCE_CENTRAL_CONFIG: True, @@ -939,6 +948,7 @@ async def test_change_vtherm_temperature( CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, CONF_CYCLE_MIN: 5, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_VALVE: "switch.mock_valve", CONF_USE_PRESENCE_FEATURE: True, CONF_USE_PRESENCE_CENTRAL_CONFIG: False, @@ -1022,6 +1032,7 @@ async def test_change_vtherm_temperature_with_presence( CONF_TEMP_MAX: 30, CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_CYCLE_MIN: 5, CONF_AC_MODE: True, CONF_VALVE: "switch.mock_valve", @@ -1063,6 +1074,7 @@ async def test_change_vtherm_temperature_with_presence( CONF_TEMP_MAX: 30, CONF_TPI_COEF_INT: 0.5, CONF_TPI_COEF_EXT: 0.02, + CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_CYCLE_MIN: 5, CONF_VALVE: "switch.mock_valve", CONF_USE_PRESENCE_FEATURE: True, diff --git a/tests/test_tpi.py b/tests/test_tpi.py index 0ef4016..679cf3a 100644 --- a/tests/test_tpi.py +++ b/tests/test_tpi.py @@ -3,7 +3,10 @@ from homeassistant.components.climate import HVACMode from custom_components.versatile_thermostat.base_thermostat import BaseThermostat -from custom_components.versatile_thermostat.prop_algorithm import PropAlgorithm +from custom_components.versatile_thermostat.prop_algorithm import ( + PropAlgorithm, + PROPORTIONAL_FUNCTION_TPI, +) from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import @@ -121,3 +124,123 @@ async def test_tpi_calculation( assert tpi_algo.calculated_on_percent == 0 assert tpi_algo.on_time_sec == 0 assert tpi_algo.off_time_sec == 300 + + +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) +async def test_wrong_tpi_parameters( + hass: HomeAssistant, skip_hass_states_is_state: None +): # pylint: disable=unused-argument + """Test the wrong TPI parameters""" + + # Nominal case + try: + algo = PropAlgorithm( + PROPORTIONAL_FUNCTION_TPI, + 0.6, + 0.01, + 5, + 1, + "entity_id", + ) + # We should not be there + assert True + except TypeError as e: + # the normal case + assert False + + # Test TPI function + try: + algo = PropAlgorithm( + "WRONG", + 1, + 0, + 2, + 3, + "entity_id", + ) + # We should not be there + assert False + except TypeError as e: + # the normal case + pass + + # Test coef_int + try: + algo = PropAlgorithm( + PROPORTIONAL_FUNCTION_TPI, + None, + 0, + 2, + 3, + "entity_id", + ) + # We should not be there + assert False + except TypeError as e: + # the normal case + pass + + # Test coef_ext + try: + algo = PropAlgorithm( + PROPORTIONAL_FUNCTION_TPI, + 0.6, + None, + 2, + 3, + "entity_id", + ) + # We should not be there + assert False + except TypeError as e: + # the normal case + pass + + # Test cycle_min + try: + algo = PropAlgorithm( + PROPORTIONAL_FUNCTION_TPI, + 0.6, + 0.00001, + None, + 3, + "entity_id", + ) + # We should not be there + assert False + except TypeError as e: + # the normal case + pass + + # Test minimal_activation_delay + try: + algo = PropAlgorithm( + PROPORTIONAL_FUNCTION_TPI, + 0.6, + 0.00001, + 0, + None, + "entity_id", + ) + # We should not be there + assert False + except TypeError as e: + # the normal case + pass + + # Test vtherm_entity_id + try: + algo = PropAlgorithm( + PROPORTIONAL_FUNCTION_TPI, + 0.6, + 0.00001, + 0, + 12, + None, + ) + # We should not be there + assert False + except TypeError as e: + # the normal case + pass