@@ -18,6 +18,12 @@ class Timer(Entity):
1818 Actions use intent-specific names: ``start``, ``pause``, ``cancel``,
1919 ``finish``, ``change``.
2020
21+ If the timer helper does not yet exist in Home Assistant, it is created
22+ automatically via the ``timer/create`` WebSocket command the first time
23+ an action method (``start``, ``pause``, etc.) is called. This means
24+ users never need to pre-create timer helpers — the library handles it
25+ transparently.
26+
2127 In addition to the generic ``on_idle`` listener (which fires for both
2228 natural expiry and explicit cancellation), the timer provides
2329 ``on_finished`` and ``on_cancelled`` listeners that fire only for the
@@ -35,6 +41,7 @@ def __init__(self, entity_id: str, client: Any) -> None:
3541 super ().__init__ (entity_id , client )
3642 self ._finished_listeners : list [ValueChangeHandler ] = []
3743 self ._cancelled_listeners : list [ValueChangeHandler ] = []
44+ self ._ensured : bool = False
3845
3946 # -- State properties --
4047
@@ -141,6 +148,51 @@ def time_remaining(self) -> float | None:
141148 return _parse_duration_to_seconds (str (raw ))
142149 return None
143150
151+ # -- Lifecycle --
152+
153+ async def _ensure_exists (self ) -> None :
154+ """Create the timer helper in Home Assistant if it does not exist.
155+
156+ Uses the ``timer/create`` WebSocket command. The call is idempotent:
157+ once the helper has been confirmed (either via the initial state fetch
158+ or a prior ``_ensure_exists`` call), subsequent invocations are no-ops.
159+
160+ The object-id is extracted from the ``entity_id`` (the part after
161+ ``timer.``).
162+ """
163+ if self ._ensured or self .state != "unknown" :
164+ return
165+ object_id = self .entity_id .split ("." , 1 )[1 ]
166+ await self ._client .ws .send_command (
167+ {
168+ "type" : "timer/create" ,
169+ "name" : object_id ,
170+ "duration" : "00:01:00" ,
171+ }
172+ )
173+ self ._ensured = True
174+
175+ async def delete (self ) -> None :
176+ """Delete the timer helper from Home Assistant.
177+
178+ Uses the ``timer/delete`` WebSocket command. After deletion the
179+ entity's ``_ensured`` flag is reset so that a subsequent action
180+ will re-create the helper.
181+
182+ Raises
183+ ------
184+ CommandError
185+ If the timer does not exist in Home Assistant.
186+ """
187+ object_id = self .entity_id .split ("." , 1 )[1 ]
188+ await self ._client .ws .send_command (
189+ {
190+ "type" : "timer/delete" ,
191+ "timer_id" : object_id ,
192+ }
193+ )
194+ self ._ensured = False
195+
144196 # -- Actions --
145197
146198 async def start (self , * , duration : str | None = None ) -> None :
@@ -151,19 +203,23 @@ async def start(self, *, duration: str | None = None) -> None:
151203 duration : str or None, optional
152204 Override duration (e.g. ``"00:05:00"``).
153205 """
206+ await self ._ensure_exists ()
154207 data : dict [str , Any ] | None = {"duration" : duration } if duration else None
155208 await self ._call_service ("start" , data )
156209
157210 async def pause (self ) -> None :
158211 """Pause the timer."""
212+ await self ._ensure_exists ()
159213 await self ._call_service ("pause" )
160214
161215 async def cancel (self ) -> None :
162216 """Cancel the timer (returns to idle)."""
217+ await self ._ensure_exists ()
163218 await self ._call_service ("cancel" )
164219
165220 async def finish (self ) -> None :
166221 """Finish the timer immediately."""
222+ await self ._ensure_exists ()
167223 await self ._call_service ("finish" )
168224
169225 async def change (self , * , duration : str ) -> None :
@@ -174,6 +230,7 @@ async def change(self, *, duration: str) -> None:
174230 duration : str
175231 Duration to add/subtract (e.g. ``"00:01:00"`` or ``"-00:00:30"``).
176232 """
233+ await self ._ensure_exists ()
177234 await self ._call_service ("change" , {"duration" : duration })
178235
179236 # -- Listener decorators --
0 commit comments