diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py index 228e2071b4a..17712b6aef1 100644 --- a/homeassistant/components/timer/__init__.py +++ b/homeassistant/components/timer/__init__.py @@ -205,7 +205,8 @@ class Timer(collection.CollectionEntity, RestoreEntity): """Initialize a timer.""" self._config: dict = config self._state: str = STATUS_IDLE - self._duration = cv.time_period_str(config[CONF_DURATION]) + self._configured_duration = cv.time_period_str(config[CONF_DURATION]) + self._running_duration: timedelta = self._configured_duration self._remaining: timedelta | None = None self._end: datetime | None = None self._listener: Callable[[], None] | None = None @@ -248,7 +249,7 @@ class Timer(collection.CollectionEntity, RestoreEntity): def extra_state_attributes(self): """Return the state attributes.""" attrs = { - ATTR_DURATION: _format_timedelta(self._duration), + ATTR_DURATION: _format_timedelta(self._running_duration), ATTR_EDITABLE: self.editable, } if self._end is not None: @@ -275,12 +276,12 @@ class Timer(collection.CollectionEntity, RestoreEntity): # Begin restoring state self._state = state.state - self._duration = cv.time_period(state.attributes[ATTR_DURATION]) # Nothing more to do if the timer is idle if self._state == STATUS_IDLE: return + self._running_duration = cv.time_period(state.attributes[ATTR_DURATION]) # If the timer was paused, we restore the remaining time if self._state == STATUS_PAUSED: self._remaining = cv.time_period(state.attributes[ATTR_REMAINING]) @@ -314,11 +315,11 @@ class Timer(collection.CollectionEntity, RestoreEntity): self._state = STATUS_ACTIVE start = dt_util.utcnow().replace(microsecond=0) - # Set remaining to new value if needed + # Set remaining and running duration unless resuming or restarting if duration: - self._remaining = self._duration = duration + self._remaining = self._running_duration = duration elif not self._remaining: - self._remaining = self._duration + self._remaining = self._running_duration self._end = start + self._remaining @@ -336,9 +337,9 @@ class Timer(collection.CollectionEntity, RestoreEntity): raise HomeAssistantError( f"Timer {self.entity_id} is not running, only active timers can be changed" ) - if self._remaining and (self._remaining + duration) > self._duration: + if self._remaining and (self._remaining + duration) > self._running_duration: raise HomeAssistantError( - f"Not possible to change timer {self.entity_id} beyond configured duration" + f"Not possible to change timer {self.entity_id} beyond duration" ) if self._remaining and (self._remaining + duration) < timedelta(): raise HomeAssistantError( @@ -377,6 +378,7 @@ class Timer(collection.CollectionEntity, RestoreEntity): self._state = STATUS_IDLE self._end = None self._remaining = None + self._running_duration = self._configured_duration self.hass.bus.async_fire( EVENT_TIMER_CANCELLED, {ATTR_ENTITY_ID: self.entity_id} ) @@ -395,6 +397,7 @@ class Timer(collection.CollectionEntity, RestoreEntity): self._state = STATUS_IDLE self._end = None self._remaining = None + self._running_duration = self._configured_duration self.hass.bus.async_fire( EVENT_TIMER_FINISHED, {ATTR_ENTITY_ID: self.entity_id, ATTR_FINISHED_AT: end.isoformat()}, @@ -412,6 +415,7 @@ class Timer(collection.CollectionEntity, RestoreEntity): end = self._end self._end = None self._remaining = None + self._running_duration = self._configured_duration self.hass.bus.async_fire( EVENT_TIMER_FINISHED, {ATTR_ENTITY_ID: self.entity_id, ATTR_FINISHED_AT: end.isoformat()}, @@ -421,6 +425,8 @@ class Timer(collection.CollectionEntity, RestoreEntity): async def async_update_config(self, config: ConfigType) -> None: """Handle when the config is updated.""" self._config = config - self._duration = cv.time_period_str(config[CONF_DURATION]) + self._configured_duration = cv.time_period_str(config[CONF_DURATION]) + if self._state == STATUS_IDLE: + self._running_duration = self._configured_duration self._restore = config.get(CONF_RESTORE, DEFAULT_RESTORE) self.async_write_ha_state() diff --git a/tests/components/timer/test_init.py b/tests/components/timer/test_init.py index eabc5e04e0b..6b6929e88ec 100644 --- a/tests/components/timer/test_init.py +++ b/tests/components/timer/test_init.py @@ -319,7 +319,7 @@ async def test_start_service(hass: HomeAssistant) -> None: with pytest.raises( HomeAssistantError, - match="Not possible to change timer timer.test1 beyond configured duration", + match="Not possible to change timer timer.test1 beyond duration", ): await hass.services.async_call( DOMAIN, @@ -370,7 +370,7 @@ async def test_start_service(hass: HomeAssistant) -> None: state = hass.states.get("timer.test1") assert state assert state.state == STATUS_IDLE - assert state.attributes[ATTR_DURATION] == "0:00:15" + assert state.attributes[ATTR_DURATION] == "0:00:10" assert ATTR_REMAINING not in state.attributes with pytest.raises( @@ -387,7 +387,7 @@ async def test_start_service(hass: HomeAssistant) -> None: state = hass.states.get("timer.test1") assert state assert state.state == STATUS_IDLE - assert state.attributes[ATTR_DURATION] == "0:00:15" + assert state.attributes[ATTR_DURATION] == "0:00:10" assert ATTR_REMAINING not in state.attributes @@ -844,43 +844,6 @@ async def test_setup_no_config(hass: HomeAssistant, hass_admin_user: MockUser) - assert count_start == len(hass.states.async_entity_ids()) -async def test_restore_idle(hass: HomeAssistant) -> None: - """Test entity restore logic when timer is idle.""" - utc_now = utcnow() - stored_state = StoredState( - State( - "timer.test", - STATUS_IDLE, - {ATTR_DURATION: "0:00:30"}, - ), - None, - utc_now, - ) - - data = async_get(hass) - await data.store.async_save([stored_state.as_dict()]) - await data.async_load() - - entity = Timer.from_storage( - { - CONF_ID: "test", - CONF_NAME: "test", - CONF_DURATION: "0:01:00", - CONF_RESTORE: True, - } - ) - entity.hass = hass - entity.entity_id = "timer.test" - - await entity.async_added_to_hass() - await hass.async_block_till_done() - assert entity.state == STATUS_IDLE - assert entity.extra_state_attributes[ATTR_DURATION] == "0:00:30" - assert ATTR_REMAINING not in entity.extra_state_attributes - assert ATTR_FINISHES_AT not in entity.extra_state_attributes - assert entity.extra_state_attributes[ATTR_RESTORE] - - @pytest.mark.freeze_time("2023-06-05 17:47:50") async def test_restore_paused(hass: HomeAssistant) -> None: """Test entity restore logic when timer is paused.""" @@ -1007,7 +970,7 @@ async def test_restore_active_finished_outside_grace(hass: HomeAssistant) -> Non await hass.async_block_till_done() assert entity.state == STATUS_IDLE - assert entity.extra_state_attributes[ATTR_DURATION] == "0:00:30" + assert entity.extra_state_attributes[ATTR_DURATION] == "0:01:00" assert ATTR_REMAINING not in entity.extra_state_attributes assert ATTR_FINISHES_AT not in entity.extra_state_attributes assert entity.extra_state_attributes[ATTR_RESTORE]