diff --git a/custom_components/versatile_thermostat/base_thermostat.py b/custom_components/versatile_thermostat/base_thermostat.py index b8fc18e..a0193ee 100644 --- a/custom_components/versatile_thermostat/base_thermostat.py +++ b/custom_components/versatile_thermostat/base_thermostat.py @@ -1235,7 +1235,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]): ) # If AC is on maybe we have to change the temperature in force mode, but not in frost mode (there is no Frost protection possible in AC mode) - if self._hvac_mode == HVACMode.COOL and self.preset_mode != PRESET_NONE: + if self._hvac_mode in [HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL] and self.preset_mode != PRESET_NONE: if self.preset_mode != PRESET_FROST_PROTECTION: await self._async_set_preset_mode_internal(self.preset_mode, True) else: diff --git a/custom_components/versatile_thermostat/climate.py b/custom_components/versatile_thermostat/climate.py index aa548f5..86cc91b 100644 --- a/custom_components/versatile_thermostat/climate.py +++ b/custom_components/versatile_thermostat/climate.py @@ -72,6 +72,13 @@ async def async_setup_entry( entity = ThermostatOverClimate(hass, unique_id, name, entry.data) elif vt_type == CONF_THERMOSTAT_VALVE: entity = ThermostatOverValve(hass, unique_id, name, entry.data) + else: + _LOGGER.error( + "Cannot create Versatile Thermostat name=%s of type %s which is unknown", + name, + vt_type, + ) + return async_add_entities([entity], True) diff --git a/tests/commons.py b/tests/commons.py index 4b3d2c3..7071f9f 100644 --- a/tests/commons.py +++ b/tests/commons.py @@ -987,3 +987,8 @@ async def set_climate_preset_temp( ) if temp_entity: await temp_entity.async_set_native_value(temp) + else: + _LOGGER.warning( + "commons tests set_cliamte_preset_temp: cannot find number entity with entity_id '%s'", + number_entity_id, + ) diff --git a/tests/test_auto_regulation.py b/tests/test_auto_regulation.py index 76d0f1f..7126a48 100644 --- a/tests/test_auto_regulation.py +++ b/tests/test_auto_regulation.py @@ -53,18 +53,6 @@ async def test_over_climate_regulation( return_value=fake_underlying_climate, ): 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: ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname") assert entity assert isinstance(entity, ThermostatOverClimate) @@ -163,18 +151,6 @@ async def test_over_climate_regulation_ac_mode( return_value=fake_underlying_climate, ): 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: ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname") assert entity assert isinstance(entity, ThermostatOverClimate) @@ -626,9 +602,7 @@ async def test_over_climate_regulation_dtemp_null( # the regulated temperature should be greater assert entity.regulated_target_temp > entity.target_temperature - assert ( - entity.regulated_target_temp == 20 + 0.9 - ) + assert entity.regulated_target_temp == 20 + 0.9 # change temperature so that the regulated temperature should slow down event_timestamp = now - timedelta(minutes=13) @@ -641,9 +615,7 @@ async def test_over_climate_regulation_dtemp_null( # the regulated temperature should be greater assert entity.regulated_target_temp > entity.target_temperature - assert ( - entity.regulated_target_temp == 20 + 0.5 - ) + assert entity.regulated_target_temp == 20 + 0.5 old_regulated_temp = entity.regulated_target_temp # Test if a small temperature change is taken into account : change temperature so that dtemp < 0.5 and time is > period_min (+ 3min) @@ -656,4 +628,4 @@ async def test_over_climate_regulation_dtemp_null( await send_ext_temperature_change_event(entity, 10, event_timestamp) # the regulated temperature should be greater. This does not work if dtemp is not null - assert entity.regulated_target_temp > old_regulated_temp \ No newline at end of file + assert entity.regulated_target_temp > old_regulated_temp diff --git a/tests/test_bugs.py b/tests/test_bugs.py index 2401237..963c76e 100644 --- a/tests/test_bugs.py +++ b/tests/test_bugs.py @@ -12,10 +12,14 @@ from homeassistant.components.climate import ( SERVICE_SET_TEMPERATURE, ) + from custom_components.versatile_thermostat.config_flow import ( VersatileThermostatBaseConfigFlow, ) from custom_components.versatile_thermostat.thermostat_valve import ThermostatOverValve +from custom_components.versatile_thermostat.thermostat_climate import ( + ThermostatOverClimate, +) from custom_components.versatile_thermostat.thermostat_switch import ( ThermostatOverSwitch, ) @@ -1090,3 +1094,120 @@ async def test_bug_500_3(hass: HomeAssistant, init_vtherm_api) -> None: assert flow._infos[CONF_USE_POWER_FEATURE] is True assert flow._infos[CONF_USE_PRESENCE_FEATURE] is True assert flow._infos[CONF_USE_MOTION_FEATURE] is True + + +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize("expected_lingering_timers", [True]) +async def test_bug_524(hass: HomeAssistant, skip_hass_states_is_state): + """Test when switching from Cool to Heat the new temperature in Heat mode should be used""" + + vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass) + + # The temperatures to set + temps = { + "frost": 7.0, + "eco": 17.0, + "comfort": 19.0, + "boost": 21.0, + "eco_ac": 27.0, + "comfort_ac": 25.0, + "boost_ac": 23.0, + "frost_away": 7.1, + "eco_away": 17.1, + "comfort_away": 19.1, + "boost_away": 21.1, + "eco_ac_away": 27.1, + "comfort_ac_away": 25.1, + "boost_ac_away": 23.1, + } + + config_entry = MockConfigEntry( + domain=DOMAIN, + title="TheOverClimateMockName", + unique_id="overClimateUniqueId", + data={ + CONF_NAME: "overClimate", + CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", + CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, + CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor", + CONF_CYCLE_MIN: 5, + CONF_TEMP_MIN: 15, + CONF_TEMP_MAX: 30, + CONF_USE_WINDOW_FEATURE: False, + CONF_USE_MOTION_FEATURE: False, + CONF_USE_POWER_FEATURE: False, + CONF_USE_PRESENCE_FEATURE: True, + CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor", + CONF_CLIMATE: "climate.mock_climate", + CONF_MINIMAL_ACTIVATION_DELAY: 30, + CONF_SECURITY_DELAY_MIN: 5, + CONF_SECURITY_MIN_ON_PERCENT: 0.3, + CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO, + CONF_AC_MODE: True, + }, + # | temps, + ) + + fake_underlying_climate = MockClimate( + hass=hass, + unique_id="mock_climate", + name="mock_climate", + hvac_modes=[HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT, HVACMode.FAN_ONLY], + ) + + with patch( + "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", + return_value=fake_underlying_climate, + ): + vtherm: ThermostatOverClimate = await create_thermostat( + hass, config_entry, "climate.overclimate" + ) + + assert vtherm is not None + + # We search for NumberEntities + for preset_name, value in temps.items(): + + await set_climate_preset_temp(vtherm, preset_name, value) + + temp_entity: NumberEntity = search_entity( + hass, + "number.overclimate_preset_" + preset_name + PRESET_TEMP_SUFFIX, + NUMBER_DOMAIN, + ) + assert temp_entity + # Because set_value is not implemented in Number class (really don't understand why...) + assert temp_entity.state == value + + # 1. Set mode to Heat and preset to Comfort + await send_presence_change_event(vtherm, True, False, datetime.now()) + await vtherm.async_set_hvac_mode(HVACMode.HEAT) + await vtherm.async_set_preset_mode(PRESET_COMFORT) + await hass.async_block_till_done() + + assert vtherm.target_temperature == 19.0 + + # 2. Only change the HVAC_MODE (and keep preset to comfort) + await vtherm.async_set_hvac_mode(HVACMode.COOL) + await hass.async_block_till_done() + assert vtherm.target_temperature == 25.0 + + # 3. Only change the HVAC_MODE (and keep preset to comfort) + await vtherm.async_set_hvac_mode(HVACMode.HEAT) + await hass.async_block_till_done() + assert vtherm.target_temperature == 19.0 + + # 4. Change presence to off + await send_presence_change_event(vtherm, False, True, datetime.now()) + await hass.async_block_till_done() + assert vtherm.target_temperature == 19.1 + + # 5. Change hvac_mode to AC + await vtherm.async_set_hvac_mode(HVACMode.COOL) + await hass.async_block_till_done() + assert vtherm.target_temperature == 25.1 + + # 6. Change presence to on + await send_presence_change_event(vtherm, True, False, datetime.now()) + await hass.async_block_till_done() + assert vtherm.target_temperature == 25 diff --git a/tests/test_central_boiler.py b/tests/test_central_boiler.py index 381b970..8daada6 100644 --- a/tests/test_central_boiler.py +++ b/tests/test_central_boiler.py @@ -731,7 +731,7 @@ async def test_update_central_boiler_state_simple_climate( "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate", return_value=climate1, ): - entity: ThermostatOverValve = await create_thermostat( + entity: ThermostatOverClimate = await create_thermostat( hass, entry, "climate.theoverclimatemockname" ) assert entity diff --git a/tests/test_last_seen.py b/tests/test_last_seen.py index 2c2e01e..e026add 100644 --- a/tests/test_last_seen.py +++ b/tests/test_last_seen.py @@ -5,9 +5,6 @@ from unittest.mock import patch, call from datetime import timedelta, datetime import logging -from custom_components.versatile_thermostat.thermostat_climate import ( - ThermostatOverClimate, -) from custom_components.versatile_thermostat.thermostat_switch import ( ThermostatOverSwitch, )