Allow delayed commands to not have a device id (#118390)

This commit is contained in:
Michael Hansen 2024-05-29 11:39:41 -05:00 committed by GitHub
parent 181ae1227a
commit 3ffbbcfa5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 14 deletions

View File

@ -45,8 +45,11 @@ class TimerInfo:
seconds: int
"""Total number of seconds the timer should run for."""
device_id: str
"""Id of the device where the timer was set."""
device_id: str | None
"""Id of the device where the timer was set.
May be None only if conversation_command is set.
"""
start_hours: int | None
"""Number of hours the timer should run as given by the user."""
@ -213,7 +216,7 @@ class TimerManager:
def start_timer(
self,
device_id: str,
device_id: str | None,
hours: int | None,
minutes: int | None,
seconds: int | None,
@ -223,7 +226,10 @@ class TimerManager:
conversation_agent_id: str | None = None,
) -> str:
"""Start a timer."""
if not self.is_timer_device(device_id):
if (not conversation_command) and (device_id is None):
raise ValueError("Conversation command must be set if no device id")
if (device_id is not None) and (not self.is_timer_device(device_id)):
raise TimersNotSupportedError(device_id)
total_seconds = 0
@ -270,7 +276,8 @@ class TimerManager:
name=f"Timer {timer_id}",
)
self.handlers[timer.device_id](TimerEventType.STARTED, timer)
if timer.device_id is not None:
self.handlers[timer.device_id](TimerEventType.STARTED, timer)
_LOGGER.debug(
"Timer started: id=%s, name=%s, hours=%s, minutes=%s, seconds=%s, device_id=%s",
timer_id,
@ -487,7 +494,11 @@ def _find_timer(
) -> TimerInfo:
"""Match a single timer with constraints or raise an error."""
timer_manager: TimerManager = hass.data[TIMER_DATA]
matching_timers: list[TimerInfo] = list(timer_manager.timers.values())
# Ignore delayed command timers
matching_timers: list[TimerInfo] = [
t for t in timer_manager.timers.values() if not t.conversation_command
]
has_filter = False
if find_filter:
@ -617,7 +628,11 @@ def _find_timers(
) -> list[TimerInfo]:
"""Match multiple timers with constraints or raise an error."""
timer_manager: TimerManager = hass.data[TIMER_DATA]
matching_timers: list[TimerInfo] = list(timer_manager.timers.values())
# Ignore delayed command timers
matching_timers: list[TimerInfo] = [
t for t in timer_manager.timers.values() if not t.conversation_command
]
# Filter by name first
name: str | None = None
@ -784,10 +799,17 @@ class StartTimerIntentHandler(intent.IntentHandler):
timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots)
if not (
intent_obj.device_id and timer_manager.is_timer_device(intent_obj.device_id)
conversation_command: str | None = None
if "conversation_command" in slots:
conversation_command = slots["conversation_command"]["value"].strip()
if (not conversation_command) and (
not (
intent_obj.device_id
and timer_manager.is_timer_device(intent_obj.device_id)
)
):
# Fail early
# Fail early if this is not a delayed command
raise TimersNotSupportedError(intent_obj.device_id)
name: str | None = None
@ -806,10 +828,6 @@ class StartTimerIntentHandler(intent.IntentHandler):
if "seconds" in slots:
seconds = int(slots["seconds"]["value"])
conversation_command: str | None = None
if "conversation_command" in slots:
conversation_command = slots["conversation_command"]["value"]
timer_manager.start_timer(
intent_obj.device_id,
hours,

View File

@ -13,6 +13,7 @@ from homeassistant.components.intent.timers import (
TimerNotFoundError,
TimersNotSupportedError,
_round_time,
async_device_supports_timers,
async_register_timer_handler,
)
from homeassistant.const import ATTR_DEVICE_ID, ATTR_NAME
@ -1440,6 +1441,17 @@ async def test_start_timer_with_conversation_command(
async_register_timer_handler(hass, device_id, handle_timer)
# Device id is required if no conversation command
timer_manager = TimerManager(hass)
with pytest.raises(ValueError):
timer_manager.start_timer(
device_id=None,
hours=None,
minutes=5,
seconds=None,
language=hass.config.language,
)
with patch("homeassistant.components.conversation.async_converse") as mock_converse:
result = await intent.async_handle(
hass,
@ -1566,3 +1578,24 @@ async def test_pause_unpause_timer_disambiguate(
await updated_event.wait()
assert len(unpaused_timer_ids) == 2
assert unpaused_timer_ids[1] == started_timer_ids[0]
async def test_async_device_supports_timers(hass: HomeAssistant) -> None:
"""Test async_device_supports_timers function."""
device_id = "test_device"
# Before intent initialization
assert not async_device_supports_timers(hass, device_id)
# After intent initialization
assert await async_setup_component(hass, "intent", {})
assert not async_device_supports_timers(hass, device_id)
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
pass
async_register_timer_handler(hass, device_id, handle_timer)
# After handler registration
assert async_device_supports_timers(hass, device_id)