From 81b4f7e5f668054a300b18a331f38958499cb6c6 Mon Sep 17 00:00:00 2001 From: Jean-Marc Collin Date: Sun, 12 Feb 2023 23:52:04 +0100 Subject: [PATCH] Testus 2 --- .../versatile_thermostat/prop_algorithm.py | 11 +-- .../versatile_thermostat/tests/commons.py | 67 ++++++++++++++- .../versatile_thermostat/tests/test_start.py | 39 +-------- .../versatile_thermostat/tests/test_tpi.py | 81 +++++++++++++++++++ 4 files changed, 156 insertions(+), 42 deletions(-) create mode 100644 custom_components/versatile_thermostat/tests/test_tpi.py diff --git a/custom_components/versatile_thermostat/prop_algorithm.py b/custom_components/versatile_thermostat/prop_algorithm.py index 8b1f3d5..21f8c45 100644 --- a/custom_components/versatile_thermostat/prop_algorithm.py +++ b/custom_components/versatile_thermostat/prop_algorithm.py @@ -85,6 +85,12 @@ class PropAlgorithm: def _calculate_internal(self): """Finish the calculation to get the on_percent in seconds""" + # calculated on_time duration in seconds + if self._calculated_on_percent > 1: + self._calculated_on_percent = 1 + if self._calculated_on_percent < 0: + self._calculated_on_percent = 0 + if self._security: _LOGGER.debug( "Security is On using the default_on_percent %f", @@ -98,11 +104,6 @@ class PropAlgorithm: ) self._on_percent = self._calculated_on_percent - # calculated on_time duration in seconds - if self._on_percent > 1: - self._on_percent = 1 - if self._on_percent < 0: - self._on_percent = 0 self._on_time_sec = self._on_percent * self._cycle_min * 60 # Do not heat for less than xx sec diff --git a/custom_components/versatile_thermostat/tests/commons.py b/custom_components/versatile_thermostat/tests/commons.py index 6532061..1df6e73 100644 --- a/custom_components/versatile_thermostat/tests/commons.py +++ b/custom_components/versatile_thermostat/tests/commons.py @@ -1,16 +1,58 @@ """ Some common resources """ +from unittest.mock import patch from homeassistant.core import HomeAssistant -from homeassistant.components.climate import ClimateEntity from homeassistant.const import UnitOfTemperature +from homeassistant.config_entries import ConfigEntryState +from pytest_homeassistant_custom_component.common import MockConfigEntry +from homeassistant.helpers.entity_component import EntityComponent + +from ..climate import VersatileThermostat +from ..const import DOMAIN + from homeassistant.components.climate import ( + ClimateEntity, DOMAIN as CLIMATE_DOMAIN, ATTR_PRESET_MODE, HVACMode, HVACAction, ) +from .const import ( + MOCK_TH_OVER_SWITCH_USER_CONFIG, + MOCK_TH_OVER_CLIMATE_USER_CONFIG, + MOCK_TH_OVER_SWITCH_TYPE_CONFIG, + MOCK_TH_OVER_CLIMATE_TYPE_CONFIG, + MOCK_TH_OVER_SWITCH_TPI_CONFIG, + MOCK_PRESETS_CONFIG, + MOCK_WINDOW_CONFIG, + MOCK_MOTION_CONFIG, + MOCK_POWER_CONFIG, + MOCK_PRESENCE_CONFIG, + MOCK_ADVANCED_CONFIG, + # MOCK_DEFAULT_FEATURE_CONFIG, +) + +FULL_SWITCH_CONFIG = ( + MOCK_TH_OVER_SWITCH_USER_CONFIG + | MOCK_TH_OVER_SWITCH_TYPE_CONFIG + | MOCK_TH_OVER_SWITCH_TPI_CONFIG + | MOCK_PRESETS_CONFIG + | MOCK_WINDOW_CONFIG + | MOCK_MOTION_CONFIG + | MOCK_POWER_CONFIG + | MOCK_PRESENCE_CONFIG + | MOCK_ADVANCED_CONFIG +) + +PARTIAL_CLIMATE_CONFIG = ( + MOCK_TH_OVER_CLIMATE_USER_CONFIG + | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG + | MOCK_PRESETS_CONFIG + | MOCK_ADVANCED_CONFIG +) + class MockClimate(ClimateEntity): """A Mock Climate class used for Underlying climate mode""" @@ -28,3 +70,26 @@ class MockClimate(ClimateEntity): self._attr_hvac_mode = HVACMode.OFF self._attr_hvac_modes = [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT] self._attr_temperature_unit = UnitOfTemperature.CELSIUS + + +async def create_thermostat( + hass: HomeAssistant, entry: MockConfigEntry, entity_id: str +) -> VersatileThermostat: + """Creates and return a TPI Thermostat""" + with patch( + "custom_components.versatile_thermostat.climate.VersatileThermostat.send_event" + ): + 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(entity_id) + + return entity diff --git a/custom_components/versatile_thermostat/tests/test_start.py b/custom_components/versatile_thermostat/tests/test_start.py index d193c30..5eeaafe 100644 --- a/custom_components/versatile_thermostat/tests/test_start.py +++ b/custom_components/versatile_thermostat/tests/test_start.py @@ -15,41 +15,7 @@ from ..climate import VersatileThermostat from ..const import DOMAIN, EventType -from .const import ( - MOCK_TH_OVER_SWITCH_USER_CONFIG, - MOCK_TH_OVER_CLIMATE_USER_CONFIG, - MOCK_TH_OVER_SWITCH_TYPE_CONFIG, - MOCK_TH_OVER_CLIMATE_TYPE_CONFIG, - MOCK_TH_OVER_SWITCH_TPI_CONFIG, - MOCK_PRESETS_CONFIG, - MOCK_WINDOW_CONFIG, - MOCK_MOTION_CONFIG, - MOCK_POWER_CONFIG, - MOCK_PRESENCE_CONFIG, - MOCK_ADVANCED_CONFIG, - # MOCK_DEFAULT_FEATURE_CONFIG, -) - -from .commons import MockClimate - -FULL_SWITCH_CONFIG = ( - MOCK_TH_OVER_SWITCH_USER_CONFIG - | MOCK_TH_OVER_SWITCH_TYPE_CONFIG - | MOCK_TH_OVER_SWITCH_TPI_CONFIG - | MOCK_PRESETS_CONFIG - | MOCK_WINDOW_CONFIG - | MOCK_MOTION_CONFIG - | MOCK_POWER_CONFIG - | MOCK_PRESENCE_CONFIG - | MOCK_ADVANCED_CONFIG -) - -PARTIAL_CLIMATE_CONFIG = ( - MOCK_TH_OVER_CLIMATE_USER_CONFIG - | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG - | MOCK_PRESETS_CONFIG - | MOCK_ADVANCED_CONFIG -) +from .commons import MockClimate, FULL_SWITCH_CONFIG, PARTIAL_CLIMATE_CONFIG async def test_over_switch_full_start(hass: HomeAssistant, skip_hass_states_is_state): @@ -76,7 +42,7 @@ async def test_over_switch_full_start(hass: HomeAssistant, skip_hass_states_is_s if entity.entity_id == entity_id: return entity - entity = find_my_entity("climate.theoverswitchmockname") + entity: VersatileThermostat = find_my_entity("climate.theoverswitchmockname") assert entity @@ -90,6 +56,7 @@ async def test_over_switch_full_start(hass: HomeAssistant, skip_hass_states_is_s assert entity._window_state is None assert entity._motion_state is None assert entity._presence_state is None + assert entity._prop_algorithm is not None # should have been called with EventType.PRESET_EVENT and EventType.HVAC_MODE_EVENT assert mock_send_event.call_count == 2 diff --git a/custom_components/versatile_thermostat/tests/test_tpi.py b/custom_components/versatile_thermostat/tests/test_tpi.py new file mode 100644 index 0000000..93dfc02 --- /dev/null +++ b/custom_components/versatile_thermostat/tests/test_tpi.py @@ -0,0 +1,81 @@ +""" Test the TPI algorithm """ + +from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import + + +async def test_tpi_calculation(hass: HomeAssistant, skip_hass_states_is_state): + """Test the TPI calculation""" + + entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverSwitchMockName", + unique_id="uniqueId", + data={ + "name": "TheOverSwitchMockName", + "thermostat_type": "thermostat_over_switch", + "temperature_sensor_entity_id": "sensor.mock_temp_sensor", + "external_temperature_sensor_entity_id": "sensor.mock_ext_temp_sensor", + "cycle_min": 5, + "temp_min": 15, + "temp_max": 30, + "use_window_feature": False, + "use_motion_feature": False, + "use_power_feature": False, + "use_presence_feature": False, + "heater_entity_id": "switch.mock_switch", + "proportional_function": "tpi", + "tpi_coef_int": 0.3, + "tpi_coef_ext": 0.01, + "minimal_activation_delay": 30, + "security_delay_min": 5, + "security_default_on_percent": 0.3, + }, + ) + + entity: VersatileThermostat = await create_thermostat( + hass, entry, "climate.theoverswitchmockname" + ) + assert entity + + tpi_algo = entity._prop_algorithm + assert tpi_algo + + tpi_algo.calculate(15, 10, 7) + assert tpi_algo.on_percent == 1 + assert tpi_algo.calculated_on_percent == 1 + assert tpi_algo.on_time_sec == 300 + assert tpi_algo.off_time_sec == 0 + + tpi_algo.calculate(15, 14, 5) + assert tpi_algo.on_percent == 0.4 + assert tpi_algo.calculated_on_percent == 0.4 + assert tpi_algo.on_time_sec == 120 + assert tpi_algo.off_time_sec == 180 + + tpi_algo.set_security(0.1) + tpi_algo.calculate(15, 14, 5) + assert tpi_algo.on_percent == 0.1 + assert tpi_algo.calculated_on_percent == 0.4 + assert tpi_algo.on_time_sec == 30 # >= minimal_activation_delay (=30) + assert tpi_algo.off_time_sec == 270 + + tpi_algo.unset_security() + tpi_algo.calculate(15, 14, 5) + assert tpi_algo.on_percent == 0.4 + assert tpi_algo.calculated_on_percent == 0.4 + assert tpi_algo.on_time_sec == 120 + assert tpi_algo.off_time_sec == 180 + + # Test minimal activation delay + tpi_algo.calculate(15, 14.7, 15) + assert tpi_algo.on_percent == 0.09 + assert tpi_algo.calculated_on_percent == 0.09 + assert tpi_algo.on_time_sec == 0 + assert tpi_algo.off_time_sec == 300 + + tpi_algo.set_security(0.09) + tpi_algo.calculate(15, 14.7, 15) + assert tpi_algo.on_percent == 0.09 + assert tpi_algo.calculated_on_percent == 0.09 + assert tpi_algo.on_time_sec == 0 + assert tpi_algo.off_time_sec == 300