From c81a319346c4dae678ec77de7243e4eac3d96d0a Mon Sep 17 00:00:00 2001 From: Brian Egge Date: Sat, 4 Sep 2021 16:17:57 -0400 Subject: [PATCH] Handle unknown preset mode in generic thermostat (#55588) Co-authored-by: Paulus Schoutsen --- .../components/generic_thermostat/climate.py | 34 +++++++++---------- .../generic_thermostat/test_climate.py | 15 ++++++++ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/generic_thermostat/climate.py b/homeassistant/components/generic_thermostat/climate.py index e83852d122f..a659d13cb7e 100644 --- a/homeassistant/components/generic_thermostat/climate.py +++ b/homeassistant/components/generic_thermostat/climate.py @@ -182,14 +182,17 @@ class GenericThermostat(ClimateEntity, RestoreEntity): self._temp_lock = asyncio.Lock() self._min_temp = min_temp self._max_temp = max_temp + self._attr_preset_mode = PRESET_NONE self._target_temp = target_temp self._unit = unit self._unique_id = unique_id self._support_flags = SUPPORT_FLAGS if away_temp: self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE + self._attr_preset_modes = [PRESET_NONE, PRESET_AWAY] + else: + self._attr_preset_modes = [PRESET_NONE] self._away_temp = away_temp - self._is_away = False async def async_added_to_hass(self): """Run when entity about to be added.""" @@ -247,8 +250,8 @@ class GenericThermostat(ClimateEntity, RestoreEntity): ) else: self._target_temp = float(old_state.attributes[ATTR_TEMPERATURE]) - if old_state.attributes.get(ATTR_PRESET_MODE) == PRESET_AWAY: - self._is_away = True + if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes: + self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE) if not self._hvac_mode and old_state.state: self._hvac_mode = old_state.state @@ -343,16 +346,6 @@ class GenericThermostat(ClimateEntity, RestoreEntity): """List of available operation modes.""" return self._hvac_list - @property - def preset_mode(self): - """Return the current preset mode, e.g., home, away, temp.""" - return PRESET_AWAY if self._is_away else PRESET_NONE - - @property - def preset_modes(self): - """Return a list of available preset modes or PRESET_NONE if _away_temp is undefined.""" - return [PRESET_NONE, PRESET_AWAY] if self._away_temp else PRESET_NONE - async def async_set_hvac_mode(self, hvac_mode): """Set hvac mode.""" if hvac_mode == HVAC_MODE_HEAT: @@ -521,13 +514,20 @@ class GenericThermostat(ClimateEntity, RestoreEntity): async def async_set_preset_mode(self, preset_mode: str): """Set new preset mode.""" - if preset_mode == PRESET_AWAY and not self._is_away: - self._is_away = True + if preset_mode not in (self._attr_preset_modes or []): + raise ValueError( + f"Got unsupported preset_mode {preset_mode}. Must be one of {self._attr_preset_modes}" + ) + if preset_mode == self._attr_preset_mode: + # I don't think we need to call async_write_ha_state if we didn't change the state + return + if preset_mode == PRESET_AWAY: + self._attr_preset_mode = PRESET_AWAY self._saved_target_temp = self._target_temp self._target_temp = self._away_temp await self._async_control_heating(force=True) - elif preset_mode == PRESET_NONE and self._is_away: - self._is_away = False + elif preset_mode == PRESET_NONE: + self._attr_preset_mode = PRESET_NONE self._target_temp = self._saved_target_temp await self._async_control_heating(force=True) diff --git a/tests/components/generic_thermostat/test_climate.py b/tests/components/generic_thermostat/test_climate.py index 4b9fbca41e2..7363ee8a32a 100644 --- a/tests/components/generic_thermostat/test_climate.py +++ b/tests/components/generic_thermostat/test_climate.py @@ -324,6 +324,21 @@ async def test_set_away_mode_twice_and_restore_prev_temp(hass, setup_comp_2): assert state.attributes.get("temperature") == 23 +async def test_set_preset_mode_invalid(hass, setup_comp_2): + """Test an invalid mode raises an error and ignore case when checking modes.""" + await common.async_set_temperature(hass, 23) + await common.async_set_preset_mode(hass, "away") + state = hass.states.get(ENTITY) + assert state.attributes.get("preset_mode") == "away" + await common.async_set_preset_mode(hass, "none") + state = hass.states.get(ENTITY) + assert state.attributes.get("preset_mode") == "none" + with pytest.raises(ValueError): + await common.async_set_preset_mode(hass, "Sleep") + state = hass.states.get(ENTITY) + assert state.attributes.get("preset_mode") == "none" + + async def test_sensor_bad_value(hass, setup_comp_2): """Test sensor that have None as state.""" state = hass.states.get(ENTITY)