Skip to content

Commit

Permalink
Feat: Configurable calendar refresh frequency
Browse files Browse the repository at this point in the history
Some calendars have a limited number of times per day that the calendar
can be refreshed. Since the refresh was originally hard coded at no more
than once every 2 minutes this caused polling exhaustion for some users.

The configuration now supports a polling frequency between as often as
possible (roughly every 30 seconds because of the HA event machine) up
to once per day. This configuration is given as a number of minutes
between 0 and 1440.

Issue: #79
Signed-off-by: Andrew Grimberg <tykeal@bardicgrove.org>
  • Loading branch information
tykeal committed Mar 18, 2022
1 parent 965b9a6 commit 0eb557c
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 12 deletions.
42 changes: 30 additions & 12 deletions custom_components/rental_control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@
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
from .const import CONF_DAYS
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions custom_components/rental_control/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
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
from .const import DEFAULT_CHECKOUT
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
Expand All @@ -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,
}
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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"])
Expand Down
2 changes: 2 additions & 0 deletions custom_components/rental_control/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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"""
Expand Down
4 changes: 4 additions & 0 deletions custom_components/rental_control/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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"
Expand All @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions custom_components/rental_control/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand All @@ -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"
Expand All @@ -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",
Expand Down

0 comments on commit 0eb557c

Please sign in to comment.