"""Building blocks for the heater switch keep-alive feature. The heater switch keep-alive feature consists of regularly refreshing the state of directly controlled switches at a configurable interval (regularly turning the switch 'on' or 'off' again even if it is already turned 'on' or 'off'), just like the keep_alive setting of Home Assistant's Generic Thermostat integration: https://www.home-assistant.io/integrations/generic_thermostat/ """ import logging from collections.abc import Awaitable, Callable from datetime import timedelta, datetime from homeassistant.core import HomeAssistant, CALLBACK_TYPE from homeassistant.helpers.event import async_track_time_interval _LOGGER = logging.getLogger(__name__) class IntervalCaller: """Repeatedly call a given async action function at a given regular interval. Convenience wrapper around Home Assistant's `async_track_time_interval` function. """ def __init__(self, hass: HomeAssistant, interval_sec: float) -> None: self._hass = hass self._interval_sec = interval_sec self._remove_handle: CALLBACK_TYPE | None = None @property def interval_sec(self) -> float: """Return the calling interval in seconds.""" return self._interval_sec def cancel(self): """Cancel the regular calls to the action function.""" if self._remove_handle: self._remove_handle() self._remove_handle = None def set_async_action(self, action: Callable[[], Awaitable[None]]): """Set the async action function to be called at regular intervals.""" if not self._interval_sec: return self.cancel() async def callback(_time: datetime): try: _LOGGER.debug( "Calling keep-alive action '%s' (%ss interval)", action.__name__, self._interval_sec, ) await action() except Exception as e: # pylint: disable=broad-exception-caught _LOGGER.error(e) self.cancel() self._remove_handle = async_track_time_interval( self._hass, callback, timedelta(seconds=self._interval_sec) )