This commit is contained in:
Michael Hansen
2026-09-01 14:52:06 -05:00
parent 98d81a3e53
commit 0173475205
5 changed files with 52 additions and 5 deletions
@@ -2,7 +2,7 @@
from functools import partial
from aioesphomeapi import VoiceAssistantTimerEventType
from aioesphomeapi import VoiceAssistantFeature, VoiceAssistantTimerEventType
from homeassistant.components.timer_list import (
InMemoryTimerListEntity,
@@ -46,11 +46,14 @@ async def async_setup_entry(
entry: ESPHomeConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the timer list for a voice-capable ESPHome device."""
"""Set up the timer list for a timer-capable ESPHome device."""
entry_data = entry.runtime_data
device_info = entry_data.device_info
assert device_info is not None
if not device_info.voice_assistant_feature_flags_compat(entry_data.api_version):
feature_flags = device_info.voice_assistant_feature_flags_compat(
entry_data.api_version
)
if not (feature_flags & VoiceAssistantFeature.TIMERS):
return
mac = device_info.mac_address
@@ -101,6 +101,9 @@ class InMemoryTimerListEntity(TimerListEntity):
async def async_cancel_timer(self, timer_id: str) -> None:
"""Cancel a timer, archiving it in the ``cancelled`` state."""
timer = self._get_timer(timer_id)
if timer.status in _FINISHED_STATUSES:
# Already archived (finished or cancelled); nothing to cancel.
return
self._unschedule(timer_id)
timer.status = TimerStatus.CANCELLED
timer.finishes_at = None
@@ -21,7 +21,7 @@
## Active Integrations
Built-in integrations: 23
Built-in integrations: 25
Custom integrations: 1
<details><summary>Built-in integrations</summary>
@@ -48,9 +48,11 @@
repairs | Repairs
stt | Speech-to-text (STT)
system_health | System Health
timer_list | Timer list
tts | Text-to-speech (TTS)
web_rtc | WebRTC
webhook | Webhook
websocket_api | Home Assistant WebSocket API
</details>
@@ -157,7 +159,7 @@
## Active Integrations
Built-in integrations: 23
Built-in integrations: 25
Custom integrations: 0
<details><summary>Built-in integrations</summary>
@@ -184,9 +186,11 @@
repairs | Repairs
stt | Speech-to-text (STT)
system_health | System Health
timer_list | Timer list
tts | Text-to-speech (TTS)
web_rtc | WebRTC
webhook | Webhook
websocket_api | Home Assistant WebSocket API
</details>
@@ -833,6 +833,24 @@ async def test_timer_events(
)
async def test_no_timer_list_without_timers_flag(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_client: APIClient,
mock_esphome_device: MockESPHomeDeviceType,
) -> None:
"""Test a voice device without the TIMERS flag gets no timer list entity."""
await mock_esphome_device(
mock_client=mock_client,
device_info={
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
},
)
await hass.async_block_till_done()
assert hass.states.async_entity_ids("timer_list") == []
async def test_unknown_timer_event(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
+19
View File
@@ -172,6 +172,25 @@ async def test_cancel_timer_archives_timer(hass: HomeAssistant) -> None:
assert timers[0]["status"] == "cancelled"
@pytest.mark.usefixtures("test_entity")
async def test_cancel_already_archived_timer_is_noop(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
) -> None:
"""Test cancelling an already-archived timer does not rewrite it."""
timer_id = await _start_timer(hass)
await _call(hass, "cancel_timer", timer_id=timer_id)
finished_at = (await _get_timers(hass))[0]["finished_at"]
# Cancelling again must not refresh the timestamp or re-fire a change
freezer.tick(timedelta(seconds=5))
await _call(hass, "cancel_timer", timer_id=timer_id)
timers = await _get_timers(hass)
assert len(timers) == 1
assert timers[0]["status"] == "cancelled"
assert timers[0]["finished_at"] == finished_at
@pytest.mark.usefixtures("test_entity")
async def test_archive_limit_evicts_oldest(
hass: HomeAssistant, freezer: FrozenDateTimeFactory