diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index 7c23fdcebcc327..e7fe3887ce9df2 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -4,6 +4,7 @@ from enum import Enum import logging +from typing import Any import voluptuous as vol @@ -29,12 +30,14 @@ STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNAVAILABLE, + STATE_UNKNOWN, ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import Script from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -103,7 +106,9 @@ class TemplateCodeFormat(Enum): ) -async def _async_create_entities(hass, config): +async def _async_create_entities( + hass: HomeAssistant, config: dict[str, Any] +) -> list[AlarmControlPanelTemplate]: """Create Template Alarm Control Panels.""" alarm_control_panels = [] @@ -133,18 +138,18 @@ async def async_setup_platform( async_add_entities(await _async_create_entities(hass, config)) -class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity): +class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity, RestoreEntity): """Representation of a templated Alarm Control Panel.""" _attr_should_poll = False def __init__( self, - hass, - object_id, - config, - unique_id, - ): + hass: HomeAssistant, + object_id: str, + config: dict, + unique_id: str | None, + ) -> None: """Initialize the panel.""" super().__init__( hass, config=config, fallback_name=object_id, unique_id=unique_id @@ -153,6 +158,7 @@ def __init__( ENTITY_ID_FORMAT, object_id, hass=hass ) name = self._attr_name + assert name is not None self._template = config.get(CONF_VALUE_TEMPLATE) self._disarm_script = None self._attr_code_arm_required: bool = config[CONF_CODE_ARM_REQUIRED] @@ -216,6 +222,19 @@ def __init__( ) self._attr_supported_features = supported_features + async def async_added_to_hass(self) -> None: + """Restore last state.""" + await super().async_added_to_hass() + if ( + (last_state := await self.async_get_last_state()) is not None + and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) + and last_state.state in _VALID_STATES + # The trigger might have fired already while we waited for stored data, + # then we should not restore state + and self._state is None + ): + self._state = last_state.state + @property def state(self) -> str | None: """Return the state of the device.""" diff --git a/tests/components/template/test_alarm_control_panel.py b/tests/components/template/test_alarm_control_panel.py index ea63d7b9926e2a..ac9bb2dcb36085 100644 --- a/tests/components/template/test_alarm_control_panel.py +++ b/tests/components/template/test_alarm_control_panel.py @@ -17,8 +17,13 @@ STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, + STATE_UNAVAILABLE, + STATE_UNKNOWN, ) -from homeassistant.core import Event, HomeAssistant, callback +from homeassistant.core import Event, HomeAssistant, State, callback +from homeassistant.setup import async_setup_component + +from tests.common import assert_setup_component, mock_restore_cache TEMPLATE_NAME = "alarm_control_panel.test_template_panel" PANEL_NAME = "alarm_control_panel.test" @@ -400,3 +405,64 @@ async def test_code_config( state = hass.states.get(TEMPLATE_NAME) assert state.attributes.get("code_format") == code_format assert state.attributes.get("code_arm_required") == code_arm_required + + +@pytest.mark.parametrize(("count", "domain"), [(1, "alarm_control_panel")]) +@pytest.mark.parametrize( + "config", + [ + { + "alarm_control_panel": { + "platform": "template", + "panels": {"test_template_panel": TEMPLATE_ALARM_CONFIG}, + } + }, + ], +) +@pytest.mark.parametrize( + ("restored_state", "initial_state"), + [ + (STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY), + (STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_ARMED_CUSTOM_BYPASS), + (STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_HOME), + (STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_NIGHT), + (STATE_ALARM_ARMED_VACATION, STATE_ALARM_ARMED_VACATION), + (STATE_ALARM_ARMING, STATE_ALARM_ARMING), + (STATE_ALARM_DISARMED, STATE_ALARM_DISARMED), + (STATE_ALARM_PENDING, STATE_ALARM_PENDING), + (STATE_ALARM_TRIGGERED, STATE_ALARM_TRIGGERED), + (STATE_UNAVAILABLE, STATE_UNKNOWN), + (STATE_UNKNOWN, STATE_UNKNOWN), + ("faulty_state", STATE_UNKNOWN), + ], +) +async def test_restore_state( + hass: HomeAssistant, + count, + domain, + config, + restored_state, + initial_state, +) -> None: + """Test restoring template alarm control panel.""" + + fake_state = State( + "alarm_control_panel.test_template_panel", + restored_state, + {}, + ) + mock_restore_cache(hass, (fake_state,)) + with assert_setup_component(count, domain): + assert await async_setup_component( + hass, + domain, + config, + ) + + await hass.async_block_till_done() + + await hass.async_start() + await hass.async_block_till_done() + + state = hass.states.get("alarm_control_panel.test_template_panel") + assert state.state == initial_state