Require registered device id for all timer intents (#117946)

* Require device id when registering timer handlers

* Require device id for timer intents

* Raise errors for unregistered device ids

* Add callback

* Add types for callback to __all__

* Clean up

* More clean up
This commit is contained in:
Michael Hansen 2024-05-24 12:55:52 -05:00 committed by GitHub
parent 77e385db52
commit 5be15c94bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 333 additions and 79 deletions

View File

@ -45,6 +45,8 @@ from .timers import (
IncreaseTimerIntentHandler, IncreaseTimerIntentHandler,
PauseTimerIntentHandler, PauseTimerIntentHandler,
StartTimerIntentHandler, StartTimerIntentHandler,
TimerEventType,
TimerInfo,
TimerManager, TimerManager,
TimerStatusIntentHandler, TimerStatusIntentHandler,
UnpauseTimerIntentHandler, UnpauseTimerIntentHandler,
@ -57,6 +59,8 @@ CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
__all__ = [ __all__ = [
"async_register_timer_handler", "async_register_timer_handler",
"TimerInfo",
"TimerEventType",
"DOMAIN", "DOMAIN",
] ]

View File

@ -29,6 +29,7 @@ _LOGGER = logging.getLogger(__name__)
TIMER_NOT_FOUND_RESPONSE = "timer_not_found" TIMER_NOT_FOUND_RESPONSE = "timer_not_found"
MULTIPLE_TIMERS_MATCHED_RESPONSE = "multiple_timers_matched" MULTIPLE_TIMERS_MATCHED_RESPONSE = "multiple_timers_matched"
NO_TIMER_SUPPORT_RESPONSE = "no_timer_support"
@dataclass @dataclass
@ -44,7 +45,7 @@ class TimerInfo:
seconds: int seconds: int
"""Total number of seconds the timer should run for.""" """Total number of seconds the timer should run for."""
device_id: str | None device_id: str
"""Id of the device where the timer was set.""" """Id of the device where the timer was set."""
start_hours: int | None start_hours: int | None
@ -159,6 +160,17 @@ class MultipleTimersMatchedError(intent.IntentHandleError):
super().__init__("Multiple timers matched", MULTIPLE_TIMERS_MATCHED_RESPONSE) super().__init__("Multiple timers matched", MULTIPLE_TIMERS_MATCHED_RESPONSE)
class TimersNotSupportedError(intent.IntentHandleError):
"""Error when a timer intent is used from a device that isn't registered to handle timer events."""
def __init__(self, device_id: str | None = None) -> None:
"""Initialize error."""
super().__init__(
f"Device does not support timers: device_id={device_id}",
NO_TIMER_SUPPORT_RESPONSE,
)
class TimerManager: class TimerManager:
"""Manager for intent timers.""" """Manager for intent timers."""
@ -170,26 +182,36 @@ class TimerManager:
self.timers: dict[str, TimerInfo] = {} self.timers: dict[str, TimerInfo] = {}
self.timer_tasks: dict[str, asyncio.Task] = {} self.timer_tasks: dict[str, asyncio.Task] = {}
self.handlers: list[TimerHandler] = [] # device_id -> handler
self.handlers: dict[str, TimerHandler] = {}
def register_handler(self, handler: TimerHandler) -> Callable[[], None]: def register_handler(
self, device_id: str, handler: TimerHandler
) -> Callable[[], None]:
"""Register a timer handler. """Register a timer handler.
Returns a callable to unregister. Returns a callable to unregister.
""" """
self.handlers.append(handler) self.handlers[device_id] = handler
return lambda: self.handlers.remove(handler)
def unregister() -> None:
self.handlers.pop(device_id)
return unregister
def start_timer( def start_timer(
self, self,
device_id: str,
hours: int | None, hours: int | None,
minutes: int | None, minutes: int | None,
seconds: int | None, seconds: int | None,
language: str, language: str,
device_id: str | None,
name: str | None = None, name: str | None = None,
) -> str: ) -> str:
"""Start a timer.""" """Start a timer."""
if not self.is_timer_device(device_id):
raise TimersNotSupportedError(device_id)
total_seconds = 0 total_seconds = 0
if hours is not None: if hours is not None:
total_seconds += 60 * 60 * hours total_seconds += 60 * 60 * hours
@ -232,9 +254,7 @@ class TimerManager:
name=f"Timer {timer_id}", name=f"Timer {timer_id}",
) )
for handler in self.handlers: self.handlers[timer.device_id](TimerEventType.STARTED, timer)
handler(TimerEventType.STARTED, timer)
_LOGGER.debug( _LOGGER.debug(
"Timer started: id=%s, name=%s, hours=%s, minutes=%s, seconds=%s, device_id=%s", "Timer started: id=%s, name=%s, hours=%s, minutes=%s, seconds=%s, device_id=%s",
timer_id, timer_id,
@ -266,15 +286,16 @@ class TimerManager:
if timer is None: if timer is None:
raise TimerNotFoundError raise TimerNotFoundError
if not self.is_timer_device(timer.device_id):
raise TimersNotSupportedError(timer.device_id)
if timer.is_active: if timer.is_active:
task = self.timer_tasks.pop(timer_id) task = self.timer_tasks.pop(timer_id)
task.cancel() task.cancel()
timer.cancel() timer.cancel()
for handler in self.handlers: self.handlers[timer.device_id](TimerEventType.CANCELLED, timer)
handler(TimerEventType.CANCELLED, timer)
_LOGGER.debug( _LOGGER.debug(
"Timer cancelled: id=%s, name=%s, seconds_left=%s, device_id=%s", "Timer cancelled: id=%s, name=%s, seconds_left=%s, device_id=%s",
timer_id, timer_id,
@ -289,6 +310,9 @@ class TimerManager:
if timer is None: if timer is None:
raise TimerNotFoundError raise TimerNotFoundError
if not self.is_timer_device(timer.device_id):
raise TimersNotSupportedError(timer.device_id)
if seconds == 0: if seconds == 0:
# Don't bother cancelling and recreating the timer task # Don't bother cancelling and recreating the timer task
return return
@ -302,8 +326,7 @@ class TimerManager:
name=f"Timer {timer_id}", name=f"Timer {timer_id}",
) )
for handler in self.handlers: self.handlers[timer.device_id](TimerEventType.UPDATED, timer)
handler(TimerEventType.UPDATED, timer)
if seconds > 0: if seconds > 0:
log_verb = "increased" log_verb = "increased"
@ -332,6 +355,9 @@ class TimerManager:
if timer is None: if timer is None:
raise TimerNotFoundError raise TimerNotFoundError
if not self.is_timer_device(timer.device_id):
raise TimersNotSupportedError(timer.device_id)
if not timer.is_active: if not timer.is_active:
# Already paused # Already paused
return return
@ -340,9 +366,7 @@ class TimerManager:
task = self.timer_tasks.pop(timer_id) task = self.timer_tasks.pop(timer_id)
task.cancel() task.cancel()
for handler in self.handlers: self.handlers[timer.device_id](TimerEventType.UPDATED, timer)
handler(TimerEventType.UPDATED, timer)
_LOGGER.debug( _LOGGER.debug(
"Timer paused: id=%s, name=%s, seconds_left=%s, device_id=%s", "Timer paused: id=%s, name=%s, seconds_left=%s, device_id=%s",
timer_id, timer_id,
@ -357,6 +381,9 @@ class TimerManager:
if timer is None: if timer is None:
raise TimerNotFoundError raise TimerNotFoundError
if not self.is_timer_device(timer.device_id):
raise TimersNotSupportedError(timer.device_id)
if timer.is_active: if timer.is_active:
# Already unpaused # Already unpaused
return return
@ -367,9 +394,7 @@ class TimerManager:
name=f"Timer {timer.id}", name=f"Timer {timer.id}",
) )
for handler in self.handlers: self.handlers[timer.device_id](TimerEventType.UPDATED, timer)
handler(TimerEventType.UPDATED, timer)
_LOGGER.debug( _LOGGER.debug(
"Timer unpaused: id=%s, name=%s, seconds_left=%s, device_id=%s", "Timer unpaused: id=%s, name=%s, seconds_left=%s, device_id=%s",
timer_id, timer_id,
@ -383,9 +408,8 @@ class TimerManager:
timer = self.timers.pop(timer_id) timer = self.timers.pop(timer_id)
timer.finish() timer.finish()
for handler in self.handlers:
handler(TimerEventType.FINISHED, timer)
self.handlers[timer.device_id](TimerEventType.FINISHED, timer)
_LOGGER.debug( _LOGGER.debug(
"Timer finished: id=%s, name=%s, device_id=%s", "Timer finished: id=%s, name=%s, device_id=%s",
timer_id, timer_id,
@ -393,24 +417,28 @@ class TimerManager:
timer.device_id, timer.device_id,
) )
def is_timer_device(self, device_id: str) -> bool:
"""Return True if device has been registered to handle timer events."""
return device_id in self.handlers
@callback @callback
def async_register_timer_handler( def async_register_timer_handler(
hass: HomeAssistant, handler: TimerHandler hass: HomeAssistant, device_id: str, handler: TimerHandler
) -> Callable[[], None]: ) -> Callable[[], None]:
"""Register a handler for timer events. """Register a handler for timer events.
Returns a callable to unregister. Returns a callable to unregister.
""" """
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
return timer_manager.register_handler(handler) return timer_manager.register_handler(device_id, handler)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def _find_timer( def _find_timer(
hass: HomeAssistant, slots: dict[str, Any], device_id: str | None hass: HomeAssistant, device_id: str, slots: dict[str, Any]
) -> TimerInfo: ) -> TimerInfo:
"""Match a single timer with constraints or raise an error.""" """Match a single timer with constraints or raise an error."""
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
@ -479,7 +507,7 @@ def _find_timer(
return matching_timers[0] return matching_timers[0]
# Use device id # Use device id
if matching_timers and device_id: if matching_timers:
matching_device_timers = [ matching_device_timers = [
t for t in matching_timers if (t.device_id == device_id) t for t in matching_timers if (t.device_id == device_id)
] ]
@ -528,7 +556,7 @@ def _find_timer(
def _find_timers( def _find_timers(
hass: HomeAssistant, slots: dict[str, Any], device_id: str | None hass: HomeAssistant, device_id: str, slots: dict[str, Any]
) -> list[TimerInfo]: ) -> list[TimerInfo]:
"""Match multiple timers with constraints or raise an error.""" """Match multiple timers with constraints or raise an error."""
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
@ -587,10 +615,6 @@ def _find_timers(
# No matches # No matches
return matching_timers return matching_timers
if not device_id:
# Can't re-order based on area/floor
return matching_timers
# Use device id to order remaining timers # Use device id to order remaining timers
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
device = device_registry.async_get(device_id) device = device_registry.async_get(device_id)
@ -702,6 +726,12 @@ class StartTimerIntentHandler(intent.IntentHandler):
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots) slots = self.async_validate_slots(intent_obj.slots)
if not (
intent_obj.device_id and timer_manager.is_timer_device(intent_obj.device_id)
):
# Fail early
raise TimersNotSupportedError(intent_obj.device_id)
name: str | None = None name: str | None = None
if "name" in slots: if "name" in slots:
name = slots["name"]["value"] name = slots["name"]["value"]
@ -719,11 +749,11 @@ class StartTimerIntentHandler(intent.IntentHandler):
seconds = int(slots["seconds"]["value"]) seconds = int(slots["seconds"]["value"])
timer_manager.start_timer( timer_manager.start_timer(
intent_obj.device_id,
hours, hours,
minutes, minutes,
seconds, seconds,
language=intent_obj.language, language=intent_obj.language,
device_id=intent_obj.device_id,
name=name, name=name,
) )
@ -747,9 +777,16 @@ class CancelTimerIntentHandler(intent.IntentHandler):
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots) slots = self.async_validate_slots(intent_obj.slots)
timer = _find_timer(hass, slots, intent_obj.device_id) if not (
timer_manager.cancel_timer(timer.id) intent_obj.device_id and timer_manager.is_timer_device(intent_obj.device_id)
):
# Fail early
raise TimersNotSupportedError(intent_obj.device_id)
assert intent_obj.device_id is not None
timer = _find_timer(hass, intent_obj.device_id, slots)
timer_manager.cancel_timer(timer.id)
return intent_obj.create_response() return intent_obj.create_response()
@ -771,10 +808,17 @@ class IncreaseTimerIntentHandler(intent.IntentHandler):
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots) slots = self.async_validate_slots(intent_obj.slots)
total_seconds = _get_total_seconds(slots) if not (
timer = _find_timer(hass, slots, intent_obj.device_id) intent_obj.device_id and timer_manager.is_timer_device(intent_obj.device_id)
timer_manager.add_time(timer.id, total_seconds) ):
# Fail early
raise TimersNotSupportedError(intent_obj.device_id)
assert intent_obj.device_id is not None
total_seconds = _get_total_seconds(slots)
timer = _find_timer(hass, intent_obj.device_id, slots)
timer_manager.add_time(timer.id, total_seconds)
return intent_obj.create_response() return intent_obj.create_response()
@ -796,10 +840,17 @@ class DecreaseTimerIntentHandler(intent.IntentHandler):
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots) slots = self.async_validate_slots(intent_obj.slots)
total_seconds = _get_total_seconds(slots) if not (
timer = _find_timer(hass, slots, intent_obj.device_id) intent_obj.device_id and timer_manager.is_timer_device(intent_obj.device_id)
timer_manager.remove_time(timer.id, total_seconds) ):
# Fail early
raise TimersNotSupportedError(intent_obj.device_id)
assert intent_obj.device_id is not None
total_seconds = _get_total_seconds(slots)
timer = _find_timer(hass, intent_obj.device_id, slots)
timer_manager.remove_time(timer.id, total_seconds)
return intent_obj.create_response() return intent_obj.create_response()
@ -820,9 +871,16 @@ class PauseTimerIntentHandler(intent.IntentHandler):
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots) slots = self.async_validate_slots(intent_obj.slots)
timer = _find_timer(hass, slots, intent_obj.device_id) if not (
timer_manager.pause_timer(timer.id) intent_obj.device_id and timer_manager.is_timer_device(intent_obj.device_id)
):
# Fail early
raise TimersNotSupportedError(intent_obj.device_id)
assert intent_obj.device_id is not None
timer = _find_timer(hass, intent_obj.device_id, slots)
timer_manager.pause_timer(timer.id)
return intent_obj.create_response() return intent_obj.create_response()
@ -843,9 +901,16 @@ class UnpauseTimerIntentHandler(intent.IntentHandler):
timer_manager: TimerManager = hass.data[TIMER_DATA] timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots) slots = self.async_validate_slots(intent_obj.slots)
timer = _find_timer(hass, slots, intent_obj.device_id) if not (
timer_manager.unpause_timer(timer.id) intent_obj.device_id and timer_manager.is_timer_device(intent_obj.device_id)
):
# Fail early
raise TimersNotSupportedError(intent_obj.device_id)
assert intent_obj.device_id is not None
timer = _find_timer(hass, intent_obj.device_id, slots)
timer_manager.unpause_timer(timer.id)
return intent_obj.create_response() return intent_obj.create_response()
@ -863,10 +928,19 @@ class TimerStatusIntentHandler(intent.IntentHandler):
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse: async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the intent.""" """Handle the intent."""
hass = intent_obj.hass hass = intent_obj.hass
timer_manager: TimerManager = hass.data[TIMER_DATA]
slots = self.async_validate_slots(intent_obj.slots) slots = self.async_validate_slots(intent_obj.slots)
if not (
intent_obj.device_id and timer_manager.is_timer_device(intent_obj.device_id)
):
# Fail early
raise TimersNotSupportedError(intent_obj.device_id)
assert intent_obj.device_id is not None
statuses: list[dict[str, Any]] = [] statuses: list[dict[str, Any]] = []
for timer in _find_timers(hass, slots, intent_obj.device_id): for timer in _find_timers(hass, intent_obj.device_id, slots):
total_seconds = timer.seconds_left total_seconds = timer.seconds_left
minutes, seconds = divmod(total_seconds, 60) minutes, seconds = divmod(total_seconds, 60)

View File

@ -11,11 +11,12 @@ from homeassistant.components.intent.timers import (
TimerInfo, TimerInfo,
TimerManager, TimerManager,
TimerNotFoundError, TimerNotFoundError,
TimersNotSupportedError,
_round_time, _round_time,
async_register_timer_handler, async_register_timer_handler,
) )
from homeassistant.const import ATTR_DEVICE_ID, ATTR_NAME from homeassistant.const import ATTR_DEVICE_ID, ATTR_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import ( from homeassistant.helpers import (
area_registry as ar, area_registry as ar,
device_registry as dr, device_registry as dr,
@ -42,6 +43,7 @@ async def test_start_finish_timer(hass: HomeAssistant, init_components) -> None:
timer_id: str | None = None timer_id: str | None = None
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None: def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal timer_id nonlocal timer_id
@ -59,7 +61,7 @@ async def test_start_finish_timer(hass: HomeAssistant, init_components) -> None:
assert timer.id == timer_id assert timer.id == timer_id
finished_event.set() finished_event.set()
async_register_timer_handler(hass, handle_timer) async_register_timer_handler(hass, device_id, handle_timer)
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
@ -87,6 +89,7 @@ async def test_cancel_timer(hass: HomeAssistant, init_components) -> None:
timer_id: str | None = None timer_id: str | None = None
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None: def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal timer_id nonlocal timer_id
@ -112,7 +115,7 @@ async def test_cancel_timer(hass: HomeAssistant, init_components) -> None:
assert timer.seconds_left == 0 assert timer.seconds_left == 0
cancelled_event.set() cancelled_event.set()
async_register_timer_handler(hass, handle_timer) async_register_timer_handler(hass, device_id, handle_timer)
# Cancel by starting time # Cancel by starting time
result = await intent.async_handle( result = await intent.async_handle(
@ -139,6 +142,7 @@ async def test_cancel_timer(hass: HomeAssistant, init_components) -> None:
"start_minutes": {"value": 2}, "start_minutes": {"value": 2},
"start_seconds": {"value": 3}, "start_seconds": {"value": 3},
}, },
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -172,6 +176,7 @@ async def test_cancel_timer(hass: HomeAssistant, init_components) -> None:
"test", "test",
intent.INTENT_CANCEL_TIMER, intent.INTENT_CANCEL_TIMER,
{"name": {"value": timer_name}}, {"name": {"value": timer_name}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -191,6 +196,7 @@ async def test_increase_timer(hass: HomeAssistant, init_components) -> None:
timer_id: str | None = None timer_id: str | None = None
original_total_seconds = -1 original_total_seconds = -1
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None: def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal timer_id, original_total_seconds nonlocal timer_id, original_total_seconds
@ -220,7 +226,7 @@ async def test_increase_timer(hass: HomeAssistant, init_components) -> None:
assert timer.id == timer_id assert timer.id == timer_id
cancelled_event.set() cancelled_event.set()
async_register_timer_handler(hass, handle_timer) async_register_timer_handler(hass, device_id, handle_timer)
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
@ -286,6 +292,7 @@ async def test_increase_timer(hass: HomeAssistant, init_components) -> None:
"test", "test",
intent.INTENT_CANCEL_TIMER, intent.INTENT_CANCEL_TIMER,
{"name": {"value": timer_name}}, {"name": {"value": timer_name}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -305,6 +312,7 @@ async def test_decrease_timer(hass: HomeAssistant, init_components) -> None:
timer_id: str | None = None timer_id: str | None = None
original_total_seconds = 0 original_total_seconds = 0
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None: def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal timer_id, original_total_seconds nonlocal timer_id, original_total_seconds
@ -335,7 +343,7 @@ async def test_decrease_timer(hass: HomeAssistant, init_components) -> None:
assert timer.id == timer_id assert timer.id == timer_id
cancelled_event.set() cancelled_event.set()
async_register_timer_handler(hass, handle_timer) async_register_timer_handler(hass, device_id, handle_timer)
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
@ -380,6 +388,7 @@ async def test_decrease_timer(hass: HomeAssistant, init_components) -> None:
"test", "test",
intent.INTENT_CANCEL_TIMER, intent.INTENT_CANCEL_TIMER,
{"name": {"value": timer_name}}, {"name": {"value": timer_name}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -394,13 +403,15 @@ async def test_decrease_timer_below_zero(hass: HomeAssistant, init_components) -
updated_event = asyncio.Event() updated_event = asyncio.Event()
finished_event = asyncio.Event() finished_event = asyncio.Event()
device_id = "test_device"
timer_id: str | None = None timer_id: str | None = None
original_total_seconds = 0 original_total_seconds = 0
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None: def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal timer_id, original_total_seconds nonlocal timer_id, original_total_seconds
assert timer.device_id is None assert timer.device_id == device_id
assert timer.name is None assert timer.name is None
assert timer.start_hours == 1 assert timer.start_hours == 1
assert timer.start_minutes == 2 assert timer.start_minutes == 2
@ -425,7 +436,7 @@ async def test_decrease_timer_below_zero(hass: HomeAssistant, init_components) -
assert timer.id == timer_id assert timer.id == timer_id
finished_event.set() finished_event.set()
async_register_timer_handler(hass, handle_timer) async_register_timer_handler(hass, device_id, handle_timer)
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
@ -436,6 +447,7 @@ async def test_decrease_timer_below_zero(hass: HomeAssistant, init_components) -
"minutes": {"value": 2}, "minutes": {"value": 2},
"seconds": {"value": 3}, "seconds": {"value": 3},
}, },
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -454,6 +466,7 @@ async def test_decrease_timer_below_zero(hass: HomeAssistant, init_components) -
"start_seconds": {"value": 3}, "start_seconds": {"value": 3},
"seconds": {"value": original_total_seconds + 1}, "seconds": {"value": original_total_seconds + 1},
}, },
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -466,12 +479,60 @@ async def test_decrease_timer_below_zero(hass: HomeAssistant, init_components) -
async def test_find_timer_failed(hass: HomeAssistant, init_components) -> None: async def test_find_timer_failed(hass: HomeAssistant, init_components) -> None:
"""Test finding a timer with the wrong info.""" """Test finding a timer with the wrong info."""
device_id = "test_device"
for intent_name in (
intent.INTENT_START_TIMER,
intent.INTENT_CANCEL_TIMER,
intent.INTENT_PAUSE_TIMER,
intent.INTENT_UNPAUSE_TIMER,
intent.INTENT_INCREASE_TIMER,
intent.INTENT_DECREASE_TIMER,
intent.INTENT_TIMER_STATUS,
):
if intent_name in (
intent.INTENT_START_TIMER,
intent.INTENT_INCREASE_TIMER,
intent.INTENT_DECREASE_TIMER,
):
slots = {"minutes": {"value": 5}}
else:
slots = {}
# No device id
with pytest.raises(TimersNotSupportedError):
await intent.async_handle(
hass,
"test",
intent_name,
slots,
device_id=None,
)
# Unregistered device
with pytest.raises(TimersNotSupportedError):
await intent.async_handle(
hass,
"test",
intent_name,
slots,
device_id=device_id,
)
# Must register a handler before we can do anything with timers
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
pass
async_register_timer_handler(hass, device_id, handle_timer)
# Start a 5 minute timer for pizza # Start a 5 minute timer for pizza
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
"test", "test",
intent.INTENT_START_TIMER, intent.INTENT_START_TIMER,
{"name": {"value": "pizza"}, "minutes": {"value": 5}}, {"name": {"value": "pizza"}, "minutes": {"value": 5}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -481,6 +542,7 @@ async def test_find_timer_failed(hass: HomeAssistant, init_components) -> None:
"test", "test",
intent.INTENT_INCREASE_TIMER, intent.INTENT_INCREASE_TIMER,
{"name": {"value": "PIZZA "}, "minutes": {"value": 1}}, {"name": {"value": "PIZZA "}, "minutes": {"value": 1}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -491,6 +553,7 @@ async def test_find_timer_failed(hass: HomeAssistant, init_components) -> None:
"test", "test",
intent.INTENT_CANCEL_TIMER, intent.INTENT_CANCEL_TIMER,
{"name": {"value": "does-not-exist"}}, {"name": {"value": "does-not-exist"}},
device_id=device_id,
) )
# Right start time # Right start time
@ -499,6 +562,7 @@ async def test_find_timer_failed(hass: HomeAssistant, init_components) -> None:
"test", "test",
intent.INTENT_INCREASE_TIMER, intent.INTENT_INCREASE_TIMER,
{"start_minutes": {"value": 5}, "minutes": {"value": 1}}, {"start_minutes": {"value": 5}, "minutes": {"value": 1}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -509,6 +573,7 @@ async def test_find_timer_failed(hass: HomeAssistant, init_components) -> None:
"test", "test",
intent.INTENT_CANCEL_TIMER, intent.INTENT_CANCEL_TIMER,
{"start_minutes": {"value": 1}}, {"start_minutes": {"value": 1}},
device_id=device_id,
) )
@ -523,6 +588,17 @@ async def test_disambiguation(
entry = MockConfigEntry() entry = MockConfigEntry()
entry.add_to_hass(hass) entry.add_to_hass(hass)
cancelled_event = asyncio.Event()
timer_info: TimerInfo | None = None
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal timer_info
if event_type == TimerEventType.CANCELLED:
timer_info = timer
cancelled_event.set()
# Alice is upstairs in the study # Alice is upstairs in the study
floor_upstairs = floor_registry.async_create("upstairs") floor_upstairs = floor_registry.async_create("upstairs")
area_study = area_registry.async_create("study") area_study = area_registry.async_create("study")
@ -551,6 +627,9 @@ async def test_disambiguation(
device_bob_kitchen_1.id, area_id=area_kitchen.id device_bob_kitchen_1.id, area_id=area_kitchen.id
) )
async_register_timer_handler(hass, device_alice_study.id, handle_timer)
async_register_timer_handler(hass, device_bob_kitchen_1.id, handle_timer)
# Alice: set a 3 minute timer # Alice: set a 3 minute timer
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
@ -591,20 +670,9 @@ async def test_disambiguation(
assert timers[0].get(ATTR_DEVICE_ID) == device_bob_kitchen_1.id assert timers[0].get(ATTR_DEVICE_ID) == device_bob_kitchen_1.id
assert timers[1].get(ATTR_DEVICE_ID) == device_alice_study.id assert timers[1].get(ATTR_DEVICE_ID) == device_alice_study.id
# Listen for timer cancellation
cancelled_event = asyncio.Event()
timer_info: TimerInfo | None = None
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal timer_info
if event_type == TimerEventType.CANCELLED:
timer_info = timer
cancelled_event.set()
async_register_timer_handler(hass, handle_timer)
# Alice: cancel my timer # Alice: cancel my timer
cancelled_event.clear()
timer_info = None
result = await intent.async_handle( result = await intent.async_handle(
hass, "test", intent.INTENT_CANCEL_TIMER, {}, device_id=device_alice_study.id hass, "test", intent.INTENT_CANCEL_TIMER, {}, device_id=device_alice_study.id
) )
@ -651,6 +719,9 @@ async def test_disambiguation(
device_bob_living_room.id, area_id=area_living_room.id device_bob_living_room.id, area_id=area_living_room.id
) )
async_register_timer_handler(hass, device_alice_bedroom.id, handle_timer)
async_register_timer_handler(hass, device_bob_living_room.id, handle_timer)
# Alice: set a 3 minute timer (study) # Alice: set a 3 minute timer (study)
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
@ -720,13 +791,23 @@ async def test_disambiguation(
assert timer_info.device_id == device_alice_study.id assert timer_info.device_id == device_alice_study.id
assert timer_info.start_minutes == 3 assert timer_info.start_minutes == 3
# Trying to cancel the remaining two timers without area/floor info fails # Trying to cancel the remaining two timers from a disconnected area fails
area_garage = area_registry.async_create("garage")
device_garage = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
connections=set(),
identifiers={("test", "garage")},
)
device_registry.async_update_device(device_garage.id, area_id=area_garage.id)
async_register_timer_handler(hass, device_garage.id, handle_timer)
with pytest.raises(MultipleTimersMatchedError): with pytest.raises(MultipleTimersMatchedError):
await intent.async_handle( await intent.async_handle(
hass, hass,
"test", "test",
intent.INTENT_CANCEL_TIMER, intent.INTENT_CANCEL_TIMER,
{}, {},
device_id=device_garage.id,
) )
# Alice cancels the bedroom timer from study (same floor) # Alice cancels the bedroom timer from study (same floor)
@ -755,6 +836,8 @@ async def test_disambiguation(
device_bob_kitchen_2.id, area_id=area_kitchen.id device_bob_kitchen_2.id, area_id=area_kitchen.id
) )
async_register_timer_handler(hass, device_bob_kitchen_2.id, handle_timer)
# Bob cancels the kitchen timer from a different device # Bob cancels the kitchen timer from a different device
cancelled_event.clear() cancelled_event.clear()
timer_info = None timer_info = None
@ -788,11 +871,14 @@ async def test_disambiguation(
async def test_pause_unpause_timer(hass: HomeAssistant, init_components) -> None: async def test_pause_unpause_timer(hass: HomeAssistant, init_components) -> None:
"""Test pausing and unpausing a running timer.""" """Test pausing and unpausing a running timer."""
device_id = "test_device"
started_event = asyncio.Event() started_event = asyncio.Event()
updated_event = asyncio.Event() updated_event = asyncio.Event()
expected_active = True expected_active = True
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None: def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
if event_type == TimerEventType.STARTED: if event_type == TimerEventType.STARTED:
started_event.set() started_event.set()
@ -800,10 +886,14 @@ async def test_pause_unpause_timer(hass: HomeAssistant, init_components) -> None
assert timer.is_active == expected_active assert timer.is_active == expected_active
updated_event.set() updated_event.set()
async_register_timer_handler(hass, handle_timer) async_register_timer_handler(hass, device_id, handle_timer)
result = await intent.async_handle( result = await intent.async_handle(
hass, "test", intent.INTENT_START_TIMER, {"minutes": {"value": 5}} hass,
"test",
intent.INTENT_START_TIMER,
{"minutes": {"value": 5}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -812,7 +902,9 @@ async def test_pause_unpause_timer(hass: HomeAssistant, init_components) -> None
# Pause the timer # Pause the timer
expected_active = False expected_active = False
result = await intent.async_handle(hass, "test", intent.INTENT_PAUSE_TIMER, {}) result = await intent.async_handle(
hass, "test", intent.INTENT_PAUSE_TIMER, {}, device_id=device_id
)
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
async with asyncio.timeout(1): async with asyncio.timeout(1):
@ -820,14 +912,18 @@ async def test_pause_unpause_timer(hass: HomeAssistant, init_components) -> None
# Pausing again will not fire the event # Pausing again will not fire the event
updated_event.clear() updated_event.clear()
result = await intent.async_handle(hass, "test", intent.INTENT_PAUSE_TIMER, {}) result = await intent.async_handle(
hass, "test", intent.INTENT_PAUSE_TIMER, {}, device_id=device_id
)
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
assert not updated_event.is_set() assert not updated_event.is_set()
# Unpause the timer # Unpause the timer
updated_event.clear() updated_event.clear()
expected_active = True expected_active = True
result = await intent.async_handle(hass, "test", intent.INTENT_UNPAUSE_TIMER, {}) result = await intent.async_handle(
hass, "test", intent.INTENT_UNPAUSE_TIMER, {}, device_id=device_id
)
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
async with asyncio.timeout(1): async with asyncio.timeout(1):
@ -835,7 +931,9 @@ async def test_pause_unpause_timer(hass: HomeAssistant, init_components) -> None
# Unpausing again will not fire the event # Unpausing again will not fire the event
updated_event.clear() updated_event.clear()
result = await intent.async_handle(hass, "test", intent.INTENT_UNPAUSE_TIMER, {}) result = await intent.async_handle(
hass, "test", intent.INTENT_UNPAUSE_TIMER, {}, device_id=device_id
)
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
assert not updated_event.is_set() assert not updated_event.is_set()
@ -860,11 +958,63 @@ async def test_timer_not_found(hass: HomeAssistant) -> None:
timer_manager.unpause_timer("does-not-exist") timer_manager.unpause_timer("does-not-exist")
async def test_timers_not_supported(hass: HomeAssistant) -> None:
"""Test unregistered device ids raise TimersNotSupportedError."""
timer_manager = TimerManager(hass)
with pytest.raises(TimersNotSupportedError):
timer_manager.start_timer(
"does-not-exist",
hours=None,
minutes=5,
seconds=None,
language=hass.config.language,
)
# Start a timer
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
pass
device_id = "test_device"
unregister = timer_manager.register_handler(device_id, handle_timer)
timer_id = timer_manager.start_timer(
device_id,
hours=None,
minutes=5,
seconds=None,
language=hass.config.language,
)
# Unregister handler so device no longer "supports" timers
unregister()
# All operations on the timer should fail now
with pytest.raises(TimersNotSupportedError):
timer_manager.add_time(timer_id, 1)
with pytest.raises(TimersNotSupportedError):
timer_manager.remove_time(timer_id, 1)
with pytest.raises(TimersNotSupportedError):
timer_manager.pause_timer(timer_id)
with pytest.raises(TimersNotSupportedError):
timer_manager.unpause_timer(timer_id)
with pytest.raises(TimersNotSupportedError):
timer_manager.cancel_timer(timer_id)
async def test_timer_status_with_names(hass: HomeAssistant, init_components) -> None: async def test_timer_status_with_names(hass: HomeAssistant, init_components) -> None:
"""Test getting the status of named timers.""" """Test getting the status of named timers."""
device_id = "test_device"
started_event = asyncio.Event() started_event = asyncio.Event()
num_started = 0 num_started = 0
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None: def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal num_started nonlocal num_started
@ -873,7 +1023,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
if num_started == 4: if num_started == 4:
started_event.set() started_event.set()
async_register_timer_handler(hass, handle_timer) async_register_timer_handler(hass, device_id, handle_timer)
# Start timers with names # Start timers with names
result = await intent.async_handle( result = await intent.async_handle(
@ -881,6 +1031,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"test", "test",
intent.INTENT_START_TIMER, intent.INTENT_START_TIMER,
{"name": {"value": "pizza"}, "minutes": {"value": 10}}, {"name": {"value": "pizza"}, "minutes": {"value": 10}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -889,6 +1040,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"test", "test",
intent.INTENT_START_TIMER, intent.INTENT_START_TIMER,
{"name": {"value": "pizza"}, "minutes": {"value": 15}}, {"name": {"value": "pizza"}, "minutes": {"value": 15}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -897,6 +1049,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"test", "test",
intent.INTENT_START_TIMER, intent.INTENT_START_TIMER,
{"name": {"value": "cookies"}, "minutes": {"value": 20}}, {"name": {"value": "cookies"}, "minutes": {"value": 20}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -905,6 +1058,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"test", "test",
intent.INTENT_START_TIMER, intent.INTENT_START_TIMER,
{"name": {"value": "chicken"}, "hours": {"value": 2}, "seconds": {"value": 30}}, {"name": {"value": "chicken"}, "hours": {"value": 2}, "seconds": {"value": 30}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -913,7 +1067,9 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
await started_event.wait() await started_event.wait()
# No constraints returns all timers # No constraints returns all timers
result = await intent.async_handle(hass, "test", intent.INTENT_TIMER_STATUS, {}) result = await intent.async_handle(
hass, "test", intent.INTENT_TIMER_STATUS, {}, device_id=device_id
)
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
assert len(timers) == 4 assert len(timers) == 4
@ -925,6 +1081,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"test", "test",
intent.INTENT_TIMER_STATUS, intent.INTENT_TIMER_STATUS,
{"name": {"value": "cookies"}}, {"name": {"value": "cookies"}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -938,6 +1095,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"test", "test",
intent.INTENT_TIMER_STATUS, intent.INTENT_TIMER_STATUS,
{"name": {"value": "pizza"}}, {"name": {"value": "pizza"}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -952,6 +1110,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"test", "test",
intent.INTENT_TIMER_STATUS, intent.INTENT_TIMER_STATUS,
{"name": {"value": "pizza"}, "start_minutes": {"value": 10}}, {"name": {"value": "pizza"}, "start_minutes": {"value": 10}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -969,6 +1128,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"start_hours": {"value": 2}, "start_hours": {"value": 2},
"start_seconds": {"value": 30}, "start_seconds": {"value": 30},
}, },
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -980,7 +1140,11 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
# Wrong name results in an empty list # Wrong name results in an empty list
result = await intent.async_handle( result = await intent.async_handle(
hass, "test", intent.INTENT_TIMER_STATUS, {"name": {"value": "does-not-exist"}} hass,
"test",
intent.INTENT_TIMER_STATUS,
{"name": {"value": "does-not-exist"}},
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -996,6 +1160,7 @@ async def test_timer_status_with_names(hass: HomeAssistant, init_components) ->
"start_minutes": {"value": 100}, "start_minutes": {"value": 100},
"start_seconds": {"value": 100}, "start_seconds": {"value": 100},
}, },
device_id=device_id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -1034,6 +1199,7 @@ async def test_area_filter(
num_timers = 3 num_timers = 3
num_started = 0 num_started = 0
@callback
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None: def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
nonlocal num_started nonlocal num_started
@ -1042,7 +1208,8 @@ async def test_area_filter(
if num_started == num_timers: if num_started == num_timers:
started_event.set() started_event.set()
async_register_timer_handler(hass, handle_timer) async_register_timer_handler(hass, device_kitchen.id, handle_timer)
async_register_timer_handler(hass, device_living_room.id, handle_timer)
# Start timers in different areas # Start timers in different areas
result = await intent.async_handle( result = await intent.async_handle(
@ -1077,30 +1244,34 @@ async def test_area_filter(
await started_event.wait() await started_event.wait()
# No constraints returns all timers # No constraints returns all timers
result = await intent.async_handle(hass, "test", intent.INTENT_TIMER_STATUS, {}) result = await intent.async_handle(
hass, "test", intent.INTENT_TIMER_STATUS, {}, device_id=device_kitchen.id
)
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
assert len(timers) == num_timers assert len(timers) == num_timers
assert {t.get(ATTR_NAME) for t in timers} == {"pizza", "tv", "media"} assert {t.get(ATTR_NAME) for t in timers} == {"pizza", "tv", "media"}
# Filter by area (kitchen) # Filter by area (target kitchen from living room)
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
"test", "test",
intent.INTENT_TIMER_STATUS, intent.INTENT_TIMER_STATUS,
{"area": {"value": "kitchen"}}, {"area": {"value": "kitchen"}},
device_id=device_living_room.id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
assert len(timers) == 1 assert len(timers) == 1
assert timers[0].get(ATTR_NAME) == "pizza" assert timers[0].get(ATTR_NAME) == "pizza"
# Filter by area (living room) # Filter by area (target living room from kitchen)
result = await intent.async_handle( result = await intent.async_handle(
hass, hass,
"test", "test",
intent.INTENT_TIMER_STATUS, intent.INTENT_TIMER_STATUS,
{"area": {"value": "living room"}}, {"area": {"value": "living room"}},
device_id=device_kitchen.id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -1113,6 +1284,7 @@ async def test_area_filter(
"test", "test",
intent.INTENT_TIMER_STATUS, intent.INTENT_TIMER_STATUS,
{"area": {"value": "living room"}, "name": {"value": "tv"}}, {"area": {"value": "living room"}, "name": {"value": "tv"}},
device_id=device_kitchen.id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -1125,6 +1297,7 @@ async def test_area_filter(
"test", "test",
intent.INTENT_TIMER_STATUS, intent.INTENT_TIMER_STATUS,
{"area": {"value": "living room"}, "start_minutes": {"value": 15}}, {"area": {"value": "living room"}, "start_minutes": {"value": 15}},
device_id=device_kitchen.id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -1137,6 +1310,7 @@ async def test_area_filter(
"test", "test",
intent.INTENT_TIMER_STATUS, intent.INTENT_TIMER_STATUS,
{"area": {"value": "does-not-exist"}}, {"area": {"value": "does-not-exist"}},
device_id=device_kitchen.id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
timers = result.speech_slots.get("timers", []) timers = result.speech_slots.get("timers", [])
@ -1148,6 +1322,7 @@ async def test_area_filter(
"test", "test",
intent.INTENT_CANCEL_TIMER, intent.INTENT_CANCEL_TIMER,
{"area": {"value": "living room"}, "start_minutes": {"value": 15}}, {"area": {"value": "living room"}, "start_minutes": {"value": 15}},
device_id=device_living_room.id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE
@ -1157,6 +1332,7 @@ async def test_area_filter(
"test", "test",
intent.INTENT_CANCEL_TIMER, intent.INTENT_CANCEL_TIMER,
{"area": {"value": "living room"}}, {"area": {"value": "living room"}},
device_id=device_living_room.id,
) )
assert result.response_type == intent.IntentResponseType.ACTION_DONE assert result.response_type == intent.IntentResponseType.ACTION_DONE