diff --git a/homeassistant/components/shelly/climate.py b/homeassistant/components/shelly/climate.py index a2b81bc222c..5102ed9e4c3 100644 --- a/homeassistant/components/shelly/climate.py +++ b/homeassistant/components/shelly/climate.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections.abc import Mapping +from dataclasses import asdict, dataclass from typing import Any, cast from aioshelly.block_device import Block @@ -27,7 +28,7 @@ from homeassistant.helpers.entity_registry import ( async_entries_for_config_entry, async_get as er_async_get, ) -from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.restore_state import ExtraStoredData, RestoreEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util.unit_conversion import TemperatureConverter from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM @@ -100,6 +101,17 @@ def async_restore_climate_entities( break +@dataclass +class ShellyClimateExtraStoredData(ExtraStoredData): + """Object to hold extra stored data.""" + + last_target_temp: float | None = None + + def as_dict(self) -> dict[str, Any]: + """Return a dict representation of the text data.""" + return asdict(self) + + class BlockSleepingClimate( CoordinatorEntity[ShellyBlockCoordinator], RestoreEntity, ClimateEntity ): @@ -131,14 +143,7 @@ class BlockSleepingClimate( self.last_state: State | None = None self.last_state_attributes: Mapping[str, Any] self._preset_modes: list[str] = [] - if coordinator.hass.config.units is US_CUSTOMARY_SYSTEM: - self._last_target_temp = TemperatureConverter.convert( - SHTRV_01_TEMPERATURE_SETTINGS["default"], - UnitOfTemperature.CELSIUS, - UnitOfTemperature.FAHRENHEIT, - ) - else: - self._last_target_temp = SHTRV_01_TEMPERATURE_SETTINGS["default"] + self._last_target_temp = SHTRV_01_TEMPERATURE_SETTINGS["default"] if self.block is not None and self.device_block is not None: self._unique_id = f"{self.coordinator.mac}-{self.block.description}" @@ -154,6 +159,11 @@ class BlockSleepingClimate( self._channel = cast(int, self._unique_id.split("_")[1]) + @property + def extra_restore_state_data(self) -> ShellyClimateExtraStoredData: + """Return text specific state data to be restored.""" + return ShellyClimateExtraStoredData(self._last_target_temp) + @property def unique_id(self) -> str: """Set unique id of entity.""" @@ -308,7 +318,6 @@ class BlockSleepingClimate( LOGGER.info("Restoring entity %s", self.name) last_state = await self.async_get_last_state() - if last_state is not None: self.last_state = last_state self.last_state_attributes = self.last_state.attributes @@ -316,6 +325,10 @@ class BlockSleepingClimate( list, self.last_state.attributes.get("preset_modes") ) + last_extra_data = await self.async_get_last_extra_data() + if last_extra_data is not None: + self._last_target_temp = last_extra_data.as_dict()["last_target_temp"] + await super().async_added_to_hass() @callback diff --git a/tests/components/shelly/test_climate.py b/tests/components/shelly/test_climate.py index 0d43ae118cf..527aa44c892 100644 --- a/tests/components/shelly/test_climate.py +++ b/tests/components/shelly/test_climate.py @@ -25,7 +25,7 @@ from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from . import init_integration, register_device, register_entity -from tests.common import mock_restore_cache +from tests.common import mock_restore_cache, mock_restore_cache_with_extra_data SENSOR_BLOCK_ID = 3 DEVICE_BLOCK_ID = 4 @@ -191,19 +191,25 @@ async def test_block_restored_climate(hass, mock_block_device, device_reg, monke "sensor_0", entry, ) - mock_restore_cache(hass, [State(entity_id, HVACMode.HEAT)]) + attrs = {"current_temperature": 20.5, "temperature": 4.0} + extra_data = {"last_target_temp": 22.0} + mock_restore_cache_with_extra_data( + hass, ((State(entity_id, HVACMode.OFF, attributes=attrs), extra_data),) + ) monkeypatch.setattr(mock_block_device, "initialized", False) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert hass.states.get(entity_id).state == HVACMode.HEAT + assert hass.states.get(entity_id).state == HVACMode.OFF + assert hass.states.get(entity_id).attributes.get("temperature") == 4.0 # Partial update, should not change state mock_block_device.mock_update() await hass.async_block_till_done() - assert hass.states.get(entity_id).state == HVACMode.HEAT + assert hass.states.get(entity_id).state == HVACMode.OFF + assert hass.states.get(entity_id).attributes.get("temperature") == 4.0 # Make device online monkeypatch.setattr(mock_block_device, "initialized", True) @@ -211,6 +217,24 @@ async def test_block_restored_climate(hass, mock_block_device, device_reg, monke await hass.async_block_till_done() assert hass.states.get(entity_id).state == HVACMode.OFF + assert hass.states.get(entity_id).attributes.get("temperature") == 4.0 + + # Test set hvac mode heat, target temp should be set to last target temp (22) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVACMode.HEAT}, + blocking=True, + ) + mock_block_device.http_request.assert_called_once_with( + "get", "thermostat/0", {"target_t_enabled": 1, "target_t": 22.0} + ) + + monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "targetTemp", 22.0) + mock_block_device.mock_update() + state = hass.states.get(ENTITY_ID) + assert state.state == HVACMode.HEAT + assert hass.states.get(entity_id).attributes.get("temperature") == 22.0 async def test_block_restored_climate_us_customery( @@ -229,36 +253,56 @@ async def test_block_restored_climate_us_customery( "sensor_0", entry, ) - attrs = {"current_temperature": 67, "temperature": 68} - mock_restore_cache(hass, [State(entity_id, HVACMode.HEAT, attributes=attrs)]) + attrs = {"current_temperature": 67, "temperature": 39} + extra_data = {"last_target_temp": 10.0} + mock_restore_cache_with_extra_data( + hass, ((State(entity_id, HVACMode.OFF, attributes=attrs), extra_data),) + ) monkeypatch.setattr(mock_block_device, "initialized", False) await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - assert hass.states.get(entity_id).state == HVACMode.HEAT - assert hass.states.get(entity_id).attributes.get("temperature") == 68 + assert hass.states.get(entity_id).state == HVACMode.OFF + assert hass.states.get(entity_id).attributes.get("temperature") == 39 assert hass.states.get(entity_id).attributes.get("current_temperature") == 67 # Partial update, should not change state mock_block_device.mock_update() await hass.async_block_till_done() - assert hass.states.get(entity_id).state == HVACMode.HEAT - assert hass.states.get(entity_id).attributes.get("temperature") == 68 + assert hass.states.get(entity_id).state == HVACMode.OFF + assert hass.states.get(entity_id).attributes.get("temperature") == 39 assert hass.states.get(entity_id).attributes.get("current_temperature") == 67 # Make device online monkeypatch.setattr(mock_block_device, "initialized", True) - monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "targetTemp", 19.7) + monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "targetTemp", 4.0) monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "temp", 18.2) mock_block_device.mock_update() await hass.async_block_till_done() - assert hass.states.get(entity_id).state == HVACMode.HEAT - assert hass.states.get(entity_id).attributes.get("temperature") == 67 + assert hass.states.get(entity_id).state == HVACMode.OFF + assert hass.states.get(entity_id).attributes.get("temperature") == 39 assert hass.states.get(entity_id).attributes.get("current_temperature") == 65 + # Test set hvac mode heat, target temp should be set to last target temp (10.0/50) + await hass.services.async_call( + CLIMATE_DOMAIN, + SERVICE_SET_HVAC_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVACMode.HEAT}, + blocking=True, + ) + mock_block_device.http_request.assert_called_once_with( + "get", "thermostat/0", {"target_t_enabled": 1, "target_t": 10.0} + ) + + monkeypatch.setattr(mock_block_device.blocks[SENSOR_BLOCK_ID], "targetTemp", 10.0) + mock_block_device.mock_update() + state = hass.states.get(ENTITY_ID) + assert state.state == HVACMode.HEAT + assert hass.states.get(entity_id).attributes.get("temperature") == 50 + async def test_block_restored_climate_unavailable( hass, mock_block_device, device_reg, monkeypatch