mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 10:47:10 +00:00
Change duration for timer.start service to only change running duration (#99628)
* Get back duration for timer * running duration * Mods * Finish * Fix start call * remove restore idle * running duration not None * fix tests
This commit is contained in:
parent
6b19602322
commit
c414e52b55
@ -205,7 +205,8 @@ class Timer(collection.CollectionEntity, RestoreEntity):
|
|||||||
"""Initialize a timer."""
|
"""Initialize a timer."""
|
||||||
self._config: dict = config
|
self._config: dict = config
|
||||||
self._state: str = STATUS_IDLE
|
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._remaining: timedelta | None = None
|
||||||
self._end: datetime | None = None
|
self._end: datetime | None = None
|
||||||
self._listener: Callable[[], None] | None = None
|
self._listener: Callable[[], None] | None = None
|
||||||
@ -248,7 +249,7 @@ class Timer(collection.CollectionEntity, RestoreEntity):
|
|||||||
def extra_state_attributes(self):
|
def extra_state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
attrs = {
|
attrs = {
|
||||||
ATTR_DURATION: _format_timedelta(self._duration),
|
ATTR_DURATION: _format_timedelta(self._running_duration),
|
||||||
ATTR_EDITABLE: self.editable,
|
ATTR_EDITABLE: self.editable,
|
||||||
}
|
}
|
||||||
if self._end is not None:
|
if self._end is not None:
|
||||||
@ -275,12 +276,12 @@ class Timer(collection.CollectionEntity, RestoreEntity):
|
|||||||
|
|
||||||
# Begin restoring state
|
# Begin restoring state
|
||||||
self._state = state.state
|
self._state = state.state
|
||||||
self._duration = cv.time_period(state.attributes[ATTR_DURATION])
|
|
||||||
|
|
||||||
# Nothing more to do if the timer is idle
|
# Nothing more to do if the timer is idle
|
||||||
if self._state == STATUS_IDLE:
|
if self._state == STATUS_IDLE:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._running_duration = cv.time_period(state.attributes[ATTR_DURATION])
|
||||||
# If the timer was paused, we restore the remaining time
|
# If the timer was paused, we restore the remaining time
|
||||||
if self._state == STATUS_PAUSED:
|
if self._state == STATUS_PAUSED:
|
||||||
self._remaining = cv.time_period(state.attributes[ATTR_REMAINING])
|
self._remaining = cv.time_period(state.attributes[ATTR_REMAINING])
|
||||||
@ -314,11 +315,11 @@ class Timer(collection.CollectionEntity, RestoreEntity):
|
|||||||
self._state = STATUS_ACTIVE
|
self._state = STATUS_ACTIVE
|
||||||
start = dt_util.utcnow().replace(microsecond=0)
|
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:
|
if duration:
|
||||||
self._remaining = self._duration = duration
|
self._remaining = self._running_duration = duration
|
||||||
elif not self._remaining:
|
elif not self._remaining:
|
||||||
self._remaining = self._duration
|
self._remaining = self._running_duration
|
||||||
|
|
||||||
self._end = start + self._remaining
|
self._end = start + self._remaining
|
||||||
|
|
||||||
@ -336,9 +337,9 @@ class Timer(collection.CollectionEntity, RestoreEntity):
|
|||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
f"Timer {self.entity_id} is not running, only active timers can be changed"
|
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(
|
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():
|
if self._remaining and (self._remaining + duration) < timedelta():
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
@ -377,6 +378,7 @@ class Timer(collection.CollectionEntity, RestoreEntity):
|
|||||||
self._state = STATUS_IDLE
|
self._state = STATUS_IDLE
|
||||||
self._end = None
|
self._end = None
|
||||||
self._remaining = None
|
self._remaining = None
|
||||||
|
self._running_duration = self._configured_duration
|
||||||
self.hass.bus.async_fire(
|
self.hass.bus.async_fire(
|
||||||
EVENT_TIMER_CANCELLED, {ATTR_ENTITY_ID: self.entity_id}
|
EVENT_TIMER_CANCELLED, {ATTR_ENTITY_ID: self.entity_id}
|
||||||
)
|
)
|
||||||
@ -395,6 +397,7 @@ class Timer(collection.CollectionEntity, RestoreEntity):
|
|||||||
self._state = STATUS_IDLE
|
self._state = STATUS_IDLE
|
||||||
self._end = None
|
self._end = None
|
||||||
self._remaining = None
|
self._remaining = None
|
||||||
|
self._running_duration = self._configured_duration
|
||||||
self.hass.bus.async_fire(
|
self.hass.bus.async_fire(
|
||||||
EVENT_TIMER_FINISHED,
|
EVENT_TIMER_FINISHED,
|
||||||
{ATTR_ENTITY_ID: self.entity_id, ATTR_FINISHED_AT: end.isoformat()},
|
{ATTR_ENTITY_ID: self.entity_id, ATTR_FINISHED_AT: end.isoformat()},
|
||||||
@ -412,6 +415,7 @@ class Timer(collection.CollectionEntity, RestoreEntity):
|
|||||||
end = self._end
|
end = self._end
|
||||||
self._end = None
|
self._end = None
|
||||||
self._remaining = None
|
self._remaining = None
|
||||||
|
self._running_duration = self._configured_duration
|
||||||
self.hass.bus.async_fire(
|
self.hass.bus.async_fire(
|
||||||
EVENT_TIMER_FINISHED,
|
EVENT_TIMER_FINISHED,
|
||||||
{ATTR_ENTITY_ID: self.entity_id, ATTR_FINISHED_AT: end.isoformat()},
|
{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:
|
async def async_update_config(self, config: ConfigType) -> None:
|
||||||
"""Handle when the config is updated."""
|
"""Handle when the config is updated."""
|
||||||
self._config = config
|
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._restore = config.get(CONF_RESTORE, DEFAULT_RESTORE)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
@ -319,7 +319,7 @@ async def test_start_service(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
HomeAssistantError,
|
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(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -370,7 +370,7 @@ async def test_start_service(hass: HomeAssistant) -> None:
|
|||||||
state = hass.states.get("timer.test1")
|
state = hass.states.get("timer.test1")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATUS_IDLE
|
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
|
assert ATTR_REMAINING not in state.attributes
|
||||||
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
@ -387,7 +387,7 @@ async def test_start_service(hass: HomeAssistant) -> None:
|
|||||||
state = hass.states.get("timer.test1")
|
state = hass.states.get("timer.test1")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATUS_IDLE
|
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
|
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())
|
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")
|
@pytest.mark.freeze_time("2023-06-05 17:47:50")
|
||||||
async def test_restore_paused(hass: HomeAssistant) -> None:
|
async def test_restore_paused(hass: HomeAssistant) -> None:
|
||||||
"""Test entity restore logic when timer is paused."""
|
"""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()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entity.state == STATUS_IDLE
|
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_REMAINING not in entity.extra_state_attributes
|
||||||
assert ATTR_FINISHES_AT not in entity.extra_state_attributes
|
assert ATTR_FINISHES_AT not in entity.extra_state_attributes
|
||||||
assert entity.extra_state_attributes[ATTR_RESTORE]
|
assert entity.extra_state_attributes[ATTR_RESTORE]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user