1 """Support for Timers."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from datetime
import datetime, timedelta
8 from typing
import Any, Self
10 import voluptuous
as vol
32 _LOGGER = logging.getLogger(__name__)
35 ENTITY_ID_FORMAT = DOMAIN +
".{}"
38 DEFAULT_RESTORE =
False
40 ATTR_DURATION =
"duration"
41 ATTR_REMAINING =
"remaining"
42 ATTR_FINISHES_AT =
"finishes_at"
43 ATTR_RESTORE =
"restore"
44 ATTR_FINISHED_AT =
"finished_at"
46 CONF_DURATION =
"duration"
47 CONF_RESTORE =
"restore"
50 STATUS_ACTIVE =
"active"
51 STATUS_PAUSED =
"paused"
53 EVENT_TIMER_FINISHED =
"timer.finished"
54 EVENT_TIMER_CANCELLED =
"timer.cancelled"
55 EVENT_TIMER_CHANGED =
"timer.changed"
56 EVENT_TIMER_STARTED =
"timer.started"
57 EVENT_TIMER_RESTARTED =
"timer.restarted"
58 EVENT_TIMER_PAUSED =
"timer.paused"
60 SERVICE_START =
"start"
61 SERVICE_PAUSE =
"pause"
62 SERVICE_CANCEL =
"cancel"
63 SERVICE_CHANGE =
"change"
64 SERVICE_FINISH =
"finish"
69 STORAGE_FIELDS: VolDictType = {
70 vol.Required(CONF_NAME): cv.string,
71 vol.Optional(CONF_ICON): cv.icon,
72 vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): cv.time_period,
73 vol.Optional(CONF_RESTORE, default=DEFAULT_RESTORE): cv.boolean,
78 total_seconds = delta.total_seconds()
79 hours, remainder = divmod(total_seconds, 3600)
80 minutes, seconds = divmod(remainder, 60)
81 return f
"{int(hours)}:{int(minutes):02}:{int(seconds):02}"
84 def _none_to_empty_dict[_T](value: _T |
None) -> _T | dict[Any, Any]:
90 CONFIG_SCHEMA = vol.Schema(
92 DOMAIN: cv.schema_with_slug_keys(
96 vol.Optional(CONF_NAME): cv.string,
97 vol.Optional(CONF_ICON): cv.icon,
98 vol.Optional(CONF_DURATION, default=DEFAULT_DURATION): vol.All(
99 cv.time_period, _format_timedelta
101 vol.Optional(CONF_RESTORE, default=DEFAULT_RESTORE): cv.boolean,
106 extra=vol.ALLOW_EXTRA,
109 RELOAD_SERVICE_SCHEMA = vol.Schema({})
112 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
113 """Set up an input select."""
114 component = EntityComponent[Timer](_LOGGER, DOMAIN, hass)
115 id_manager = collection.IDManager()
117 yaml_collection = collection.YamlCollection(
118 logging.getLogger(f
"{__name__}.yaml_collection"), id_manager
120 collection.sync_entity_lifecycle(
121 hass, DOMAIN, DOMAIN, component, yaml_collection, Timer
125 Store(hass, STORAGE_VERSION, STORAGE_KEY),
128 collection.sync_entity_lifecycle(
129 hass, DOMAIN, DOMAIN, component, storage_collection, Timer
132 await yaml_collection.async_load(
133 [{CONF_ID: id_, **cfg}
for id_, cfg
in config.get(DOMAIN, {}).items()]
135 await storage_collection.async_load()
137 collection.DictStorageCollectionWebsocket(
138 storage_collection, DOMAIN, DOMAIN, STORAGE_FIELDS, STORAGE_FIELDS
141 async
def reload_service_handler(service_call: ServiceCall) ->
None:
142 """Reload yaml entities."""
143 conf = await component.async_prepare_reload(skip_reset=
True)
146 await yaml_collection.async_load(
147 [{CONF_ID: id_, **cfg}
for id_, cfg
in conf.get(DOMAIN, {}).items()]
154 reload_service_handler,
155 schema=RELOAD_SERVICE_SCHEMA,
157 component.async_register_entity_service(
159 {vol.Optional(ATTR_DURATION, default=DEFAULT_DURATION): cv.time_period},
162 component.async_register_entity_service(SERVICE_PAUSE,
None,
"async_pause")
163 component.async_register_entity_service(SERVICE_CANCEL,
None,
"async_cancel")
164 component.async_register_entity_service(SERVICE_FINISH,
None,
"async_finish")
165 component.async_register_entity_service(
167 {vol.Optional(ATTR_DURATION, default=DEFAULT_DURATION): cv.time_period},
175 """Timer storage based collection."""
177 CREATE_UPDATE_SCHEMA = vol.Schema(STORAGE_FIELDS)
180 """Validate the config is valid."""
188 """Suggest an ID based on the config."""
189 return info[CONF_NAME]
192 """Return a new updated data object."""
195 if CONF_DURATION
in update_data:
201 """Representation of a timer."""
206 """Initialize a timer."""
207 self.
_config_config: dict = config
208 self.
_state_state: str = STATUS_IDLE
211 self.
_remaining_remaining: timedelta |
None =
None
212 self.
_end_end: datetime |
None =
None
213 self.
_listener_listener: Callable[[],
None] |
None =
None
221 """Return entity instance initialized from storage."""
223 timer.editable =
True
228 """Return entity instance initialized from yaml."""
230 timer.entity_id = ENTITY_ID_FORMAT.format(config[CONF_ID])
231 timer.editable =
False
236 """Return name of the timer."""
241 """Return the icon to be used for this entity."""
246 """Return the current value of the timer."""
251 """Return the state attributes."""
252 attrs: dict[str, Any] = {
254 ATTR_EDITABLE: self.editable,
256 if self.
_end_end
is not None:
257 attrs[ATTR_FINISHES_AT] = self.
_end_end.isoformat()
261 attrs[ATTR_RESTORE] = self.
_restore_restore
267 """Return unique id for the entity."""
268 return self.
_config_config[CONF_ID]
271 """Call when entity is about to be added to Home Assistant."""
279 self.
_state_state = state.state
282 if self.
_state_state == STATUS_IDLE:
287 if self.
_state_state == STATUS_PAUSED:
288 self.
_remaining_remaining = cv.time_period(state.attributes[ATTR_REMAINING])
292 end = cv.datetime(state.attributes[ATTR_FINISHES_AT])
295 if (remaining := end - dt_util.utcnow().replace(microsecond=0)) >
timedelta(0):
297 self.
_state_state = STATUS_PAUSED
312 event = EVENT_TIMER_STARTED
313 if self.
_state_state
in (STATUS_ACTIVE, STATUS_PAUSED):
314 event = EVENT_TIMER_RESTARTED
316 self.
_state_state = STATUS_ACTIVE
317 start = dt_util.utcnow().replace(microsecond=0)
328 self.
hasshass.bus.async_fire(event, {ATTR_ENTITY_ID: self.
entity_identity_id})
336 """Change duration of a running timer."""
339 f
"Timer {self.entity_id} is not running, only active timers can be changed"
342 new_remaining = (self.
_end_end + duration) - dt_util.utcnow().replace(microsecond=0)
345 f
"Not possible to change timer {self.entity_id} beyond duration"
349 f
"Not possible to change timer {self.entity_id} to negative time remaining"
353 self.
_end_end += duration
356 self.
hasshass.bus.async_fire(EVENT_TIMER_CHANGED, {ATTR_ENTITY_ID: self.
entity_identity_id})
369 self.
_remaining_remaining = self.
_end_end - dt_util.utcnow().replace(microsecond=0)
370 self.
_state_state = STATUS_PAUSED
373 self.
hasshass.bus.async_fire(EVENT_TIMER_PAUSED, {ATTR_ENTITY_ID: self.
entity_identity_id})
377 """Cancel a timer."""
381 self.
_state_state = STATUS_IDLE
386 self.
hasshass.bus.async_fire(
387 EVENT_TIMER_CANCELLED, {ATTR_ENTITY_ID: self.
entity_identity_id}
392 """Reset and updates the states, fire finished event."""
393 if self.
_state_state != STATUS_ACTIVE
or self.
_end_end
is None:
400 self.
_state_state = STATUS_IDLE
405 self.
hasshass.bus.async_fire(
406 EVENT_TIMER_FINISHED,
407 {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_FINISHED_AT: end.isoformat()},
412 """Reset and updates the states, fire finished event."""
413 if self.
_state_state != STATUS_ACTIVE
or self.
_end_end
is None:
417 self.
_state_state = STATUS_IDLE
423 self.
hasshass.bus.async_fire(
424 EVENT_TIMER_FINISHED,
425 {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_FINISHED_AT: end.isoformat()},
429 """Handle when the config is updated."""
432 if self.
_state_state == STATUS_IDLE:
434 self.
_restore_restore = config.get(CONF_RESTORE, DEFAULT_RESTORE)
str _get_suggested_id(self, dict info)
dict _update_data(self, dict item, dict update_data)
dict _process_create_data(self, dict data)
None _async_finished(self, datetime time)
None async_added_to_hass(self)
Self from_storage(cls, ConfigType config)
None async_update_config(self, ConfigType config)
dict[str, Any] extra_state_attributes(self)
None async_start(self, timedelta|None duration=None)
None async_change(self, timedelta duration)
Self from_yaml(cls, ConfigType config)
None __init__(self, ConfigType config)
None async_write_ha_state(self)
State|None async_get_last_state(self)
web.Response get(self, web.Request request, str config_key)
str _format_timedelta(timedelta delta)
bool async_setup(HomeAssistant hass, ConfigType config)
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)
None async_register_admin_service(HomeAssistant hass, str domain, str service, Callable[[ServiceCall], Awaitable[None]|None] service_func, VolSchemaType schema=vol.Schema({}, extra=vol.PREVENT_EXTRA))