diff --git a/custom_components/rental_control/__init__.py b/custom_components/rental_control/__init__.py index aefb1f9..92628b8 100644 --- a/custom_components/rental_control/__init__.py +++ b/custom_components/rental_control/__init__.py @@ -28,7 +28,6 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import dt -from homeassistant.util import Throttle from .const import CONF_CHECKIN from .const import CONF_CHECKOUT @@ -36,7 +35,9 @@ from .const import CONF_EVENT_PREFIX from .const import CONF_IGNORE_NON_RESERVED from .const import CONF_MAX_EVENTS +from .const import CONF_REFRESH_FREQUENCY from .const import CONF_TIMEZONE +from .const import DEFAULT_REFRESH_FREQUENCY from .const import DOMAIN from .const import PLATFORMS from .const import REQUEST_TIMEOUT @@ -124,26 +125,25 @@ def __init__(self, hass, config): self.name = config.get(CONF_NAME) self.event_prefix = config.get(CONF_EVENT_PREFIX) self.url = config.get(CONF_URL) - # Early versions did not have this variable, as such it may not be + # Early versions did not have these variables, as such it may not be # set, this should guard against issues until we're certain we can # remove this guard. try: self.timezone = ZoneInfo(config.get(CONF_TIMEZONE)) except TypeError: self.timezone = dt.DEFAULT_TIME_ZONE + self.refresh_frequency = config.get(CONF_REFRESH_FREQUENCY) + if self.refresh_frequency is None: + self.refresh_frequency = DEFAULT_REFRESH_FREQUENCY + # after initial setup our first refresh should happen ASAP + self.next_refresh = dt.now() # our config flow guarantees that checkin and checkout are valid times # just use cv.time to get the parsed time object self.checkin = cv.time(config.get(CONF_CHECKIN)) self.checkout = cv.time(config.get(CONF_CHECKOUT)) self.max_events = config.get(CONF_MAX_EVENTS) self.days = config.get(CONF_DAYS) - # Early versions did not have this variable, as such it may not be - # set, this should guard against issues until we're certain - # we can remove this guard. - try: - self.ignore_non_reserved = config.get(CONF_IGNORE_NON_RESERVED) - except NameError: - self.ignore_non_reserved = None + self.ignore_non_reserved = config.get(CONF_IGNORE_NON_RESERVED) self.verify_ssl = config.get(CONF_VERIFY_SSL) self.calendar = [] self.event = None @@ -171,24 +171,42 @@ async def async_get_events( events.append(event) return events - @Throttle(MIN_TIME_BETWEEN_UPDATES) async def update(self): """Regularly update the calendar.""" _LOGGER.debug("Running ICalEvents update for calendar %s", self.name) - await self._refresh_calendar() + now = dt.now() + _LOGGER.debug("Refresh frequency is: %d", self.refresh_frequency) + _LOGGER.debug("Current time is: %s", now) + _LOGGER.debug("Next refresh is: %s", self.next_refresh) + if now >= self.next_refresh: + # Update the next refresh time before doing the calendar update + # If refresh_frequency is 0, then set the refresh for a little in + # the future to avoid having multiple calls to the calendar refresh + # happen at the same time + if self.refresh_frequency == 0: + self.next_refresh = now + timedelta(seconds=10) + else: + self.next_refresh = now + timedelta(minutes=self.refresh_frequency) + _LOGGER.debug("Updating next refresh to %s", self.next_refresh) + await self._refresh_calendar() def update_config(self, config): """Update config entries.""" self.name = config.get(CONF_NAME) self.url = config.get(CONF_URL) - # Early versions did not have this variable, as such it may not be + # Early versions did not have these variables, as such it may not be # set, this should guard against issues until we're certain # we can remove this guard. try: self.timezone = ZoneInfo(config.get(CONF_TIMEZONE)) except TypeError: self.timezone = dt.DEFAULT_TIME_ZONE + self.refresh_frequency = config.get(CONF_REFRESH_FREQUENCY) + if self.refresh_frequency is None: + self.refresh_frequency = DEFAULT_REFRESH_FREQUENCY + # always do a refresh ASAP after a config change + self.next_refresh = dt.now() self.event_prefix = config.get(CONF_EVENT_PREFIX) # our config flow guarantees that checkin and checkout are valid times # just use cv.time to get the parsed time object diff --git a/custom_components/rental_control/config_flow.py b/custom_components/rental_control/config_flow.py index 612ba96..05a2291 100644 --- a/custom_components/rental_control/config_flow.py +++ b/custom_components/rental_control/config_flow.py @@ -28,6 +28,7 @@ from .const import CONF_IGNORE_NON_RESERVED from .const import CONF_LOCK_ENTRY from .const import CONF_MAX_EVENTS +from .const import CONF_REFRESH_FREQUENCY from .const import CONF_START_SLOT from .const import CONF_TIMEZONE from .const import DEFAULT_CHECKIN @@ -35,6 +36,7 @@ from .const import DEFAULT_DAYS from .const import DEFAULT_EVENT_PREFIX from .const import DEFAULT_MAX_EVENTS +from .const import DEFAULT_REFRESH_FREQUENCY from .const import DEFAULT_START_SLOT from .const import DOMAIN from .const import LOCK_MANAGER @@ -59,6 +61,7 @@ class RentalControlFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): CONF_IGNORE_NON_RESERVED: True, CONF_EVENT_PREFIX: DEFAULT_EVENT_PREFIX, CONF_MAX_EVENTS: DEFAULT_MAX_EVENTS, + CONF_REFRESH_FREQUENCY: DEFAULT_REFRESH_FREQUENCY, CONF_TIMEZONE: str(dt.DEFAULT_TIME_ZONE), CONF_VERIFY_SSL: True, } @@ -161,6 +164,10 @@ def _get_default(key: str, fallback_default: Any = None) -> None: { vol.Required(CONF_NAME, default=_get_default(CONF_NAME)): cv.string, vol.Required(CONF_URL, default=_get_default(CONF_URL)): cv.string, + vol.Optional( + CONF_REFRESH_FREQUENCY, + default=_get_default(CONF_REFRESH_FREQUENCY, DEFAULT_REFRESH_FREQUENCY), + ): cv.positive_int, vol.Optional( CONF_TIMEZONE, default=_get_default(CONF_TIMEZONE, str(dt.DEFAULT_TIME_ZONE)), @@ -267,6 +274,9 @@ async def _start_config_flow( _LOGGER.exception(err.msg) errors["base"] = "invalid_url" + if user_input[CONF_REFRESH_FREQUENCY] > 1440: + errors["base"] = "bad_refresh" + try: cv.time(user_input["checkin"]) cv.time(user_input["checkout"]) diff --git a/custom_components/rental_control/const.py b/custom_components/rental_control/const.py index 04dba1c..e86091e 100644 --- a/custom_components/rental_control/const.py +++ b/custom_components/rental_control/const.py @@ -27,6 +27,7 @@ CONF_IGNORE_NON_RESERVED = "ignore_non_reserved" CONF_LOCK_ENTRY = "keymaster_entry_id" CONF_MAX_EVENTS = "max_events" +CONF_REFRESH_FREQUENCY = "refresh_frequency" CONF_START_SLOT = "start_slot" CONF_TIMEZONE = "timezone" @@ -37,6 +38,7 @@ DEFAULT_EVENT_PREFIX = "" DEFAULT_MAX_EVENTS = 5 DEFAULT_NAME = DOMAIN +DEFAULT_REFRESH_FREQUENCY = 2 DEFAULT_START_SLOT = 10 STARTUP_MESSAGE = f""" diff --git a/custom_components/rental_control/strings.json b/custom_components/rental_control/strings.json index f707231..f7fe207 100644 --- a/custom_components/rental_control/strings.json +++ b/custom_components/rental_control/strings.json @@ -2,6 +2,7 @@ "config": { "error": { "bad_ics": "URL did not provide valid calendar", + "bad_refresh": "Refresh must be between 0 and 1440.", "bad_time": "Check-in/out time is invalid. Use 24 hour time", "invalid_url": "Only https URLs are supported", "same_name": "Name already in use", @@ -18,6 +19,7 @@ "keymaster_entry_id": "Keymaster configuration to manage", "max_events": "Number of event sensors to create", "name": "Calendar name", + "refresh_frequency": "Refresh calendar in minutes. 0 - 1440", "start_slot": "Starting lock slot for management", "timezone": "Calendar Timezone", "url": "Calendar URL", @@ -30,6 +32,7 @@ "error": { "bad_ics": "URL did not provide valid calendar", "bad_time": "Check-in/out time is invalid. Use 24 hour time", + "bad_refresh": "Refresh must be between 0 and 1440.", "invalid_url": "Only https URLs are supported", "same_name": "Name already in use", "unknown": "Unexpected error, check logs for more details" @@ -45,6 +48,7 @@ "keymaster_entry_id": "Keymaster configuration to manage", "max_events": "Number of event sensors to create", "name": "Calendar name", + "refresh_frequency": "Refresh calendar in minutes. 0 - 1440", "start_slot": "Starting lock slot for management", "timezone": "Calendar Timezone", "url": "Calendar URL", diff --git a/custom_components/rental_control/translations/en.json b/custom_components/rental_control/translations/en.json index f707231..bae6014 100644 --- a/custom_components/rental_control/translations/en.json +++ b/custom_components/rental_control/translations/en.json @@ -3,6 +3,7 @@ "error": { "bad_ics": "URL did not provide valid calendar", "bad_time": "Check-in/out time is invalid. Use 24 hour time", + "bad_refresh": "Refresh must be between 0 and 1440.", "invalid_url": "Only https URLs are supported", "same_name": "Name already in use", "unknown": "Unexpected error, check logs for more details" @@ -18,6 +19,7 @@ "keymaster_entry_id": "Keymaster configuration to manage", "max_events": "Number of event sensors to create", "name": "Calendar name", + "refresh_frequency": "Refresh calendar in minutes. 0 - 1440", "start_slot": "Starting lock slot for management", "timezone": "Calendar Timezone", "url": "Calendar URL", @@ -30,6 +32,7 @@ "error": { "bad_ics": "URL did not provide valid calendar", "bad_time": "Check-in/out time is invalid. Use 24 hour time", + "bad_refresh": "Refresh must be between 0 and 1440.", "invalid_url": "Only https URLs are supported", "same_name": "Name already in use", "unknown": "Unexpected error, check logs for more details" @@ -45,6 +48,7 @@ "keymaster_entry_id": "Keymaster configuration to manage", "max_events": "Number of event sensors to create", "name": "Calendar name", + "refresh_frequency": "Refresh calendar in minutes. 0 - 1440", "start_slot": "Starting lock slot for management", "timezone": "Calendar Timezone", "url": "Calendar URL",