1 """Timer implementation for intents."""
3 from __future__
import annotations
6 from collections.abc
import Callable
7 from dataclasses
import dataclass
8 from enum
import StrEnum
11 from typing
import Any
13 from propcache
import cached_property
14 import voluptuous
as vol
20 config_validation
as cv,
21 device_registry
as dr,
26 from .const
import TIMER_DATA
28 _LOGGER = logging.getLogger(__name__)
30 TIMER_NOT_FOUND_RESPONSE =
"timer_not_found"
31 MULTIPLE_TIMERS_MATCHED_RESPONSE =
"multiple_timers_matched"
32 NO_TIMER_SUPPORT_RESPONSE =
"no_timer_support"
37 """Information for a single timer."""
40 """Unique id of the timer."""
43 """User-provided name for timer."""
46 """Total number of seconds the timer should run for."""
49 """Id of the device where the timer was set.
51 May be None only if conversation_command is set.
54 start_hours: int |
None
55 """Number of hours the timer should run as given by the user."""
57 start_minutes: int |
None
58 """Number of minutes the timer should run as given by the user."""
60 start_seconds: int |
None
61 """Number of seconds the timer should run as given by the user."""
64 """Timestamp when timer was created (time.monotonic_ns)"""
67 """Timestamp when timer was last updated (time.monotonic_ns)"""
70 """Language of command used to set the timer."""
72 is_active: bool =
True
73 """True if timer is ticking down."""
75 area_id: str |
None =
None
76 """Id of area that the device belongs to."""
78 area_name: str |
None =
None
79 """Normalized name of the area that the device belongs to."""
81 floor_id: str |
None =
None
82 """Id of floor that the device's area belongs to."""
84 conversation_command: str |
None =
None
85 """Text of conversation command to execute when timer is finished.
87 This command must be in the language used to set the timer.
90 conversation_agent_id: str |
None =
None
91 """Id of the conversation agent used to set the timer.
93 This agent will be used to execute the conversation command.
96 _created_seconds: int = 0
97 """Number of seconds on the timer when it was created."""
100 """Post initialization."""
105 """Return number of seconds left on the timer."""
109 now = time.monotonic_ns()
110 seconds_running =
int((now - self.
updated_atupdated_at) / 1e9)
111 return max(0, self.
secondsseconds - seconds_running)
115 """Return number of seconds on the timer when it was created.
117 This value is increased if time is added to the timer, exceeding its
118 original created_seconds.
124 """Return normalized timer name."""
128 """Cancel the timer."""
134 """Pause the timer."""
136 self.
updated_atupdated_at = time.monotonic_ns()
140 """Unpause the timer."""
141 self.
updated_atupdated_at = time.monotonic_ns()
145 """Add time to the timer.
147 Seconds may be negative to remove time instead.
151 self.
updated_atupdated_at = time.monotonic_ns()
154 """Finish the timer."""
156 self.
updated_atupdated_at = time.monotonic_ns()
161 """Event type in timer handler."""
164 """Timer has started."""
167 """Timer has been increased, decreased, paused, or unpaused."""
169 CANCELLED =
"cancelled"
170 """Timer has been cancelled."""
172 FINISHED =
"finished"
173 """Timer finished without being cancelled."""
176 type TimerHandler = Callable[[TimerEventType, TimerInfo],
None]
180 """Error when a timer could not be found by name or start time."""
183 """Initialize error."""
184 super().
__init__(
"Timer not found", TIMER_NOT_FOUND_RESPONSE)
188 """Error when multiple timers matched name or start time."""
191 """Initialize error."""
192 super().
__init__(
"Multiple timers matched", MULTIPLE_TIMERS_MATCHED_RESPONSE)
196 """Error when a timer intent is used from a device that isn't registered to handle timer events."""
198 def __init__(self, device_id: str |
None =
None) ->
None:
199 """Initialize error."""
201 f
"Device does not support timers: device_id={device_id}",
202 NO_TIMER_SUPPORT_RESPONSE,
207 """Manager for intent timers."""
210 """Initialize timer manager."""
214 self.timers: dict[str, TimerInfo] = {}
215 self.timer_tasks: dict[str, asyncio.Task] = {}
218 self.handlers: dict[str, TimerHandler] = {}
221 self, device_id: str, handler: TimerHandler
222 ) -> Callable[[],
None]:
223 """Register a timer handler.
225 Returns a callable to unregister.
227 self.handlers[device_id] = handler
229 def unregister() -> None:
230 self.handlers.pop(device_id)
236 device_id: str |
None,
241 name: str |
None =
None,
242 conversation_command: str |
None =
None,
243 conversation_agent_id: str |
None =
None,
246 if (
not conversation_command)
and (device_id
is None):
247 raise ValueError(
"Conversation command must be set if no device id")
249 if (
not conversation_command)
and (
250 (device_id
is None)
or (
not self.
is_timer_deviceis_timer_device(device_id))
255 if hours
is not None:
256 total_seconds += 60 * 60 * hours
258 if minutes
is not None:
259 total_seconds += 60 * minutes
261 if seconds
is not None:
262 total_seconds += seconds
264 timer_id = ulid.ulid_now()
265 created_at = time.monotonic_ns()
270 start_minutes=minutes,
271 start_seconds=seconds,
272 seconds=total_seconds,
275 created_at=created_at,
276 updated_at=created_at,
277 conversation_command=conversation_command,
278 conversation_agent_id=conversation_agent_id,
282 device_registry = dr.async_get(self.
hasshass)
283 if device_id
and (device := device_registry.async_get(device_id)):
284 timer.area_id = device.area_id
285 area_registry = ar.async_get(self.
hasshass)
286 if device.area_id
and (
287 area := area_registry.async_get_area(device.area_id)
290 timer.floor_id = area.floor_id
292 self.timers[timer_id] = timer
293 self.timer_tasks[timer_id] = self.
hasshass.async_create_background_task(
294 self.
_wait_for_timer_wait_for_timer(timer_id, total_seconds, created_at),
295 name=f
"Timer {timer_id}",
298 if (
not timer.conversation_command)
and (timer.device_id
in self.handlers):
299 self.handlers[timer.device_id](TimerEventType.STARTED, timer)
301 "Timer started: id=%s, name=%s, hours=%s, minutes=%s, seconds=%s, device_id=%s",
313 self, timer_id: str, seconds: int, updated_at: int
315 """Sleep until timer is up. Timer is only finished if it hasn't been updated."""
317 await asyncio.sleep(seconds)
318 if (timer := self.timers.
get(timer_id))
and (
319 timer.updated_at == updated_at
322 except asyncio.CancelledError:
326 """Cancel a timer."""
327 timer = self.timers.pop(timer_id,
None)
329 raise TimerNotFoundError
332 task = self.timer_tasks.pop(timer_id)
337 if (
not timer.conversation_command)
and (timer.device_id
in self.handlers):
338 self.handlers[timer.device_id](TimerEventType.CANCELLED, timer)
340 "Timer cancelled: id=%s, name=%s, seconds_left=%s, device_id=%s",
347 def add_time(self, timer_id: str, seconds: int) ->
None:
348 """Add time to a timer."""
349 timer = self.timers.
get(timer_id)
351 raise TimerNotFoundError
357 timer.add_time(seconds)
359 task = self.timer_tasks.pop(timer_id)
361 self.timer_tasks[timer_id] = self.
hasshass.async_create_background_task(
362 self.
_wait_for_timer_wait_for_timer(timer_id, timer.seconds, timer.updated_at),
363 name=f
"Timer {timer_id}",
366 if (
not timer.conversation_command)
and (timer.device_id
in self.handlers):
367 self.handlers[timer.device_id](TimerEventType.UPDATED, timer)
370 log_verb =
"increased"
371 log_seconds = seconds
373 log_verb =
"decreased"
374 log_seconds = -seconds
377 "Timer %s by %s second(s): id=%s, name=%s, seconds_left=%s, device_id=%s",
387 """Remove time from a timer."""
388 self.
add_timeadd_time(timer_id, -seconds)
391 """Pauses a timer."""
392 timer = self.timers.
get(timer_id)
394 raise TimerNotFoundError
396 if not timer.is_active:
401 task = self.timer_tasks.pop(timer_id)
404 if (
not timer.conversation_command)
and (timer.device_id
in self.handlers):
405 self.handlers[timer.device_id](TimerEventType.UPDATED, timer)
407 "Timer paused: id=%s, name=%s, seconds_left=%s, device_id=%s",
415 """Unpause a timer."""
416 timer = self.timers.
get(timer_id)
418 raise TimerNotFoundError
425 self.timer_tasks[timer_id] = self.
hasshass.async_create_background_task(
426 self.
_wait_for_timer_wait_for_timer(timer_id, timer.seconds_left, timer.updated_at),
427 name=f
"Timer {timer.id}",
430 if (
not timer.conversation_command)
and (timer.device_id
in self.handlers):
431 self.handlers[timer.device_id](TimerEventType.UPDATED, timer)
433 "Timer unpaused: id=%s, name=%s, seconds_left=%s, device_id=%s",
441 """Call event handlers when a timer finishes."""
442 timer = self.timers.pop(timer_id)
446 if timer.conversation_command:
450 self.
hasshass.async_create_background_task(
453 timer.conversation_command,
454 conversation_id=
None,
456 language=timer.language,
457 agent_id=timer.conversation_agent_id,
458 device_id=timer.device_id,
460 "timer assist command",
462 elif timer.device_id
in self.handlers:
463 self.handlers[timer.device_id](TimerEventType.FINISHED, timer)
466 "Timer finished: id=%s, name=%s, device_id=%s",
473 """Return True if device has been registered to handle timer events."""
474 return device_id
in self.handlers
479 """Return True if device has been registered to handle timer events."""
480 timer_manager: TimerManager |
None = hass.data.get(TIMER_DATA)
481 if timer_manager
is None:
483 return timer_manager.is_timer_device(device_id)
488 hass: HomeAssistant, device_id: str, handler: TimerHandler
489 ) -> Callable[[],
None]:
490 """Register a handler for timer events.
492 Returns a callable to unregister.
494 timer_manager: TimerManager = hass.data[TIMER_DATA]
495 return timer_manager.register_handler(device_id, handler)
502 """Type of filter to apply when finding a timer."""
504 ONLY_ACTIVE =
"only_active"
505 ONLY_INACTIVE =
"only_inactive"
510 device_id: str |
None,
511 slots: dict[str, Any],
512 find_filter: FindTimerFilter |
None =
None,
514 """Match a single timer with constraints or raise an error."""
515 timer_manager: TimerManager = hass.data[TIMER_DATA]
518 matching_timers: list[TimerInfo] = [
519 t
for t
in timer_manager.timers.values()
if not t.conversation_command
526 if find_filter == FindTimerFilter.ONLY_ACTIVE:
527 matching_timers = [t
for t
in matching_timers
if t.is_active]
528 elif find_filter == FindTimerFilter.ONLY_INACTIVE:
529 matching_timers = [t
for t
in matching_timers
if not t.is_active]
531 if len(matching_timers) == 1:
533 return matching_timers[0]
536 name: str |
None =
None
539 name = slots[
"name"][
"value"]
540 assert name
is not None
543 matching_timers = [t
for t
in matching_timers
if t.name_normalized == name_norm]
544 if len(matching_timers) == 1:
546 return matching_timers[0]
549 area_name: str |
None =
None
552 area_name = slots[
"area"][
"value"]
553 assert area_name
is not None
556 matching_timers = [t
for t
in matching_timers
if t.area_name == area_name_norm]
557 if len(matching_timers) == 1:
559 return matching_timers[0]
562 start_hours: int |
None =
None
563 if "start_hours" in slots:
564 start_hours =
int(slots[
"start_hours"][
"value"])
566 start_minutes: int |
None =
None
567 if "start_minutes" in slots:
568 start_minutes =
int(slots[
"start_minutes"][
"value"])
570 start_seconds: int |
None =
None
571 if "start_seconds" in slots:
572 start_seconds =
int(slots[
"start_seconds"][
"value"])
575 (start_hours
is not None)
576 or (start_minutes
is not None)
577 or (start_seconds
is not None)
582 for t
in matching_timers
583 if (t.start_hours == start_hours)
584 and (t.start_minutes == start_minutes)
585 and (t.start_seconds == start_seconds)
588 if len(matching_timers) == 1:
590 return matching_timers[0]
592 if (
not has_filter)
and (len(matching_timers) == 1):
594 return matching_timers[0]
597 if matching_timers
and device_id:
598 matching_device_timers = [
599 t
for t
in matching_timers
if (t.device_id == device_id)
601 if len(matching_device_timers) == 1:
603 return matching_device_timers[0]
606 device_registry = dr.async_get(hass)
607 area_registry = ar.async_get(hass)
609 (device := device_registry.async_get(device_id))
611 and (area := area_registry.async_get_area(device.area_id))
614 matching_area_timers = [
615 t
for t
in matching_timers
if (t.area_id == area.id)
617 if len(matching_area_timers) == 1:
619 return matching_area_timers[0]
622 matching_floor_timers = [
623 t
for t
in matching_timers
if (t.floor_id == area.floor_id)
625 if len(matching_floor_timers) == 1:
627 return matching_floor_timers[0]
630 raise MultipleTimersMatchedError
633 "Timer not found: name=%s, area=%s, hours=%s, minutes=%s, seconds=%s, device_id=%s",
642 raise TimerNotFoundError
646 hass: HomeAssistant, device_id: str |
None, slots: dict[str, Any]
647 ) -> list[TimerInfo]:
648 """Match multiple timers with constraints or raise an error."""
649 timer_manager: TimerManager = hass.data[TIMER_DATA]
652 matching_timers: list[TimerInfo] = [
653 t
for t
in timer_manager.timers.values()
if not t.conversation_command
657 name: str |
None =
None
659 name = slots[
"name"][
"value"]
660 assert name
is not None
663 matching_timers = [t
for t
in matching_timers
if t.name_normalized == name_norm]
664 if not matching_timers:
666 return matching_timers
669 area_name: str |
None =
None
671 area_name = slots[
"area"][
"value"]
672 assert area_name
is not None
675 matching_timers = [t
for t
in matching_timers
if t.area_name == area_name_norm]
676 if not matching_timers:
678 return matching_timers
681 start_hours: int |
None =
None
682 if "start_hours" in slots:
683 start_hours =
int(slots[
"start_hours"][
"value"])
685 start_minutes: int |
None =
None
686 if "start_minutes" in slots:
687 start_minutes =
int(slots[
"start_minutes"][
"value"])
689 start_seconds: int |
None =
None
690 if "start_seconds" in slots:
691 start_seconds =
int(slots[
"start_seconds"][
"value"])
694 (start_hours
is not None)
695 or (start_minutes
is not None)
696 or (start_seconds
is not None)
700 for t
in matching_timers
701 if (t.start_hours == start_hours)
702 and (t.start_minutes == start_minutes)
703 and (t.start_seconds == start_seconds)
705 if not matching_timers:
707 return matching_timers
711 return matching_timers
714 device_registry = dr.async_get(hass)
715 device = device_registry.async_get(device_id)
716 if (device
is None)
or (device.area_id
is None):
717 return matching_timers
719 area_registry = ar.async_get(hass)
720 area = area_registry.async_get_area(device.area_id)
722 return matching_timers
724 def area_floor_sort(timer: TimerInfo) -> int:
725 """Sort by area, then floor."""
726 if timer.area_id == area.id:
729 if timer.floor_id == area.floor_id:
734 matching_timers.sort(key=area_floor_sort)
736 return matching_timers
740 """Normalize name for comparison."""
741 return name.strip().casefold()
745 """Return the total number of seconds from hours/minutes/seconds slots."""
748 total_seconds += 60 * 60 *
int(slots[
"hours"][
"value"])
750 if "minutes" in slots:
751 total_seconds += 60 *
int(slots[
"minutes"][
"value"])
753 if "seconds" in slots:
754 total_seconds +=
int(slots[
"seconds"][
"value"])
759 def _round_time(hours: int, minutes: int, seconds: int) -> tuple[int, int, int]:
760 """Round time to a lower precision for feedback."""
763 rounded_hours = hours
778 rounded_minutes = minutes
800 rounded_seconds = seconds
803 rounded_seconds = seconds - (seconds % 10)
805 return rounded_hours, rounded_minutes, rounded_seconds
809 """Intent handler for starting a new timer."""
811 intent_type = intent.INTENT_START_TIMER
812 description =
"Starts a new timer"
814 vol.Required(vol.Any(
"hours",
"minutes",
"seconds")): cv.positive_int,
815 vol.Optional(
"name"): cv.string,
816 vol.Optional(
"conversation_command"): cv.string,
819 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
820 """Handle the intent."""
821 hass = intent_obj.hass
822 timer_manager: TimerManager = hass.data[TIMER_DATA]
823 slots = self.async_validate_slots(intent_obj.slots)
825 conversation_command: str |
None =
None
826 if "conversation_command" in slots:
827 conversation_command = slots[
"conversation_command"][
"value"].strip()
829 if (
not conversation_command)
and (
832 and timer_manager.is_timer_device(intent_obj.device_id)
838 name: str |
None =
None
840 name = slots[
"name"][
"value"]
842 hours: int |
None =
None
844 hours =
int(slots[
"hours"][
"value"])
846 minutes: int |
None =
None
847 if "minutes" in slots:
848 minutes =
int(slots[
"minutes"][
"value"])
850 seconds: int |
None =
None
851 if "seconds" in slots:
852 seconds =
int(slots[
"seconds"][
"value"])
854 timer_manager.start_timer(
855 intent_obj.device_id,
859 language=intent_obj.language,
861 conversation_command=conversation_command,
862 conversation_agent_id=intent_obj.conversation_agent_id,
865 return intent_obj.create_response()
869 """Intent handler for cancelling a timer."""
871 intent_type = intent.INTENT_CANCEL_TIMER
872 description =
"Cancels a timer"
874 vol.Any(
"start_hours",
"start_minutes",
"start_seconds"): cv.positive_int,
875 vol.Optional(
"name"): cv.string,
876 vol.Optional(
"area"): cv.string,
879 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
880 """Handle the intent."""
881 hass = intent_obj.hass
882 timer_manager: TimerManager = hass.data[TIMER_DATA]
883 slots = self.async_validate_slots(intent_obj.slots)
885 timer =
_find_timer(hass, intent_obj.device_id, slots)
886 timer_manager.cancel_timer(timer.id)
887 return intent_obj.create_response()
891 """Intent handler for cancelling all timers."""
893 intent_type = intent.INTENT_CANCEL_ALL_TIMERS
894 description =
"Cancels all timers"
896 vol.Optional(
"area"): cv.string,
899 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
900 """Handle the intent."""
901 hass = intent_obj.hass
902 timer_manager: TimerManager = hass.data[TIMER_DATA]
903 slots = self.async_validate_slots(intent_obj.slots)
906 for timer
in _find_timers(hass, intent_obj.device_id, slots):
907 timer_manager.cancel_timer(timer.id)
910 response = intent_obj.create_response()
911 speech_slots = {
"canceled": canceled}
913 speech_slots[
"area"] = slots[
"area"][
"value"]
915 response.async_set_speech_slots(speech_slots)
921 """Intent handler for increasing the time of a timer."""
923 intent_type = intent.INTENT_INCREASE_TIMER
924 description =
"Adds more time to a timer"
926 vol.Any(
"hours",
"minutes",
"seconds"): cv.positive_int,
927 vol.Any(
"start_hours",
"start_minutes",
"start_seconds"): cv.positive_int,
928 vol.Optional(
"name"): cv.string,
929 vol.Optional(
"area"): cv.string,
932 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
933 """Handle the intent."""
934 hass = intent_obj.hass
935 timer_manager: TimerManager = hass.data[TIMER_DATA]
936 slots = self.async_validate_slots(intent_obj.slots)
939 timer =
_find_timer(hass, intent_obj.device_id, slots)
940 timer_manager.add_time(timer.id, total_seconds)
941 return intent_obj.create_response()
945 """Intent handler for decreasing the time of a timer."""
947 intent_type = intent.INTENT_DECREASE_TIMER
948 description =
"Removes time from a timer"
950 vol.Required(vol.Any(
"hours",
"minutes",
"seconds")): cv.positive_int,
951 vol.Any(
"start_hours",
"start_minutes",
"start_seconds"): cv.positive_int,
952 vol.Optional(
"name"): cv.string,
953 vol.Optional(
"area"): cv.string,
956 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
957 """Handle the intent."""
958 hass = intent_obj.hass
959 timer_manager: TimerManager = hass.data[TIMER_DATA]
960 slots = self.async_validate_slots(intent_obj.slots)
963 timer =
_find_timer(hass, intent_obj.device_id, slots)
964 timer_manager.remove_time(timer.id, total_seconds)
965 return intent_obj.create_response()
969 """Intent handler for pausing a running timer."""
971 intent_type = intent.INTENT_PAUSE_TIMER
972 description =
"Pauses a running timer"
974 vol.Any(
"start_hours",
"start_minutes",
"start_seconds"): cv.positive_int,
975 vol.Optional(
"name"): cv.string,
976 vol.Optional(
"area"): cv.string,
979 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
980 """Handle the intent."""
981 hass = intent_obj.hass
982 timer_manager: TimerManager = hass.data[TIMER_DATA]
983 slots = self.async_validate_slots(intent_obj.slots)
986 hass, intent_obj.device_id, slots, find_filter=FindTimerFilter.ONLY_ACTIVE
988 timer_manager.pause_timer(timer.id)
989 return intent_obj.create_response()
993 """Intent handler for unpausing a paused timer."""
995 intent_type = intent.INTENT_UNPAUSE_TIMER
996 description =
"Resumes a paused timer"
998 vol.Any(
"start_hours",
"start_minutes",
"start_seconds"): cv.positive_int,
999 vol.Optional(
"name"): cv.string,
1000 vol.Optional(
"area"): cv.string,
1003 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
1004 """Handle the intent."""
1005 hass = intent_obj.hass
1006 timer_manager: TimerManager = hass.data[TIMER_DATA]
1007 slots = self.async_validate_slots(intent_obj.slots)
1010 hass, intent_obj.device_id, slots, find_filter=FindTimerFilter.ONLY_INACTIVE
1012 timer_manager.unpause_timer(timer.id)
1013 return intent_obj.create_response()
1017 """Intent handler for reporting the status of a timer."""
1019 intent_type = intent.INTENT_TIMER_STATUS
1020 description =
"Reports the current status of timers"
1022 vol.Any(
"start_hours",
"start_minutes",
"start_seconds"): cv.positive_int,
1023 vol.Optional(
"name"): cv.string,
1024 vol.Optional(
"area"): cv.string,
1027 async
def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
1028 """Handle the intent."""
1029 hass = intent_obj.hass
1030 slots = self.async_validate_slots(intent_obj.slots)
1032 statuses: list[dict[str, Any]] = []
1033 for timer
in _find_timers(hass, intent_obj.device_id, slots):
1034 total_seconds = timer.seconds_left
1036 minutes, seconds = divmod(total_seconds, 60)
1037 hours, minutes = divmod(minutes, 60)
1040 rounded_hours, rounded_minutes, rounded_seconds =
_round_time(
1041 hours, minutes, seconds
1047 ATTR_NAME: timer.name
or "",
1048 ATTR_DEVICE_ID: timer.device_id
or "",
1049 "language": timer.language,
1050 "start_hours": timer.start_hours
or 0,
1051 "start_minutes": timer.start_minutes
or 0,
1052 "start_seconds": timer.start_seconds
or 0,
1053 "is_active": timer.is_active,
1054 "hours_left": hours,
1055 "minutes_left": minutes,
1056 "seconds_left": seconds,
1057 "rounded_hours_left": rounded_hours,
1058 "rounded_minutes_left": rounded_minutes,
1059 "rounded_seconds_left": rounded_seconds,
1060 "total_seconds_left": total_seconds,
1064 response = intent_obj.create_response()
1065 response.async_set_speech_slots({
"timers": statuses})
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
None add_time(self, int seconds)
str name_normalized(self)
int created_seconds(self)
None cancel_timer(self, str timer_id)
str start_timer(self, str|None device_id, int|None hours, int|None minutes, int|None seconds, str language, str|None name=None, str|None conversation_command=None, str|None conversation_agent_id=None)
None remove_time(self, str timer_id, int seconds)
bool is_timer_device(self, str device_id)
None _timer_finished(self, str timer_id)
Callable[[], None] register_handler(self, str device_id, TimerHandler handler)
None unpause_timer(self, str timer_id)
None add_time(self, str timer_id, int seconds)
None _wait_for_timer(self, str timer_id, int seconds, int updated_at)
None __init__(self, HomeAssistant hass)
None pause_timer(self, str timer_id)
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
None __init__(self, str|None device_id=None)
intent.IntentResponse async_handle(self, intent.Intent intent_obj)
web.Response get(self, web.Request request, str config_key)
ConversationResult async_converse(HomeAssistant hass, str text, str|None conversation_id, Context context, str|None language=None, str|None agent_id=None, str|None device_id=None)
TimerInfo _find_timer(HomeAssistant hass, str|None device_id, dict[str, Any] slots, FindTimerFilter|None find_filter=None)
int _get_total_seconds(dict[str, Any] slots)
tuple[int, int, int] _round_time(int hours, int minutes, int seconds)
str _normalize_name(str name)
list[TimerInfo] _find_timers(HomeAssistant hass, str|None device_id, dict[str, Any] slots)
bool async_device_supports_timers(HomeAssistant hass, str device_id)
Callable[[], None] async_register_timer_handler(HomeAssistant hass, str device_id, TimerHandler handler)