diff --git a/homeassistant/components/intent/timers.py b/homeassistant/components/intent/timers.py index f93b9a0e2b8..1dc6b279a61 100644 --- a/homeassistant/components/intent/timers.py +++ b/homeassistant/components/intent/timers.py @@ -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, diff --git a/tests/components/intent/test_timers.py b/tests/components/intent/test_timers.py index f014bb5880c..a884fd13de5 100644 --- a/tests/components/intent/test_timers.py +++ b/tests/components/intent/test_timers.py @@ -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)