Significantly reduce the number of API calls that the august integration (#31685)
* Significantly reduce the number of API calls that the august integration makes. The poll interval for the lock status API is now 15 minutes instead of every 10 seconds because we can use the activity API to see changes in lock state. The interval for the activity API is 10 seconds which allows for the same frequency of state monitoring without all the additional API calls. With four locks, this change results in an ~80% reduction in the number of API calls. The result of the lock and unlock APIs now update the lock state instead of waiting for the next poll. This change also has the added benefit of making the UI appear far more responsive. * Convert to using UTC times
This commit is contained in:
parent
81b159f424
commit
ecd7ec385d
2 changed files with 96 additions and 6 deletions
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
|||
)
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.util import Throttle, dt
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -42,9 +42,22 @@ DEFAULT_ENTITY_NAMESPACE = "august"
|
|||
# avoid hitting rate limits
|
||||
MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES = timedelta(seconds=1800)
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=10)
|
||||
# Limit locks status check to 900 seconds now that
|
||||
# we get the state from the lock and unlock api calls
|
||||
# and the lock and unlock activities are now captured
|
||||
MIN_TIME_BETWEEN_LOCK_STATUS_UPDATES = timedelta(seconds=900)
|
||||
|
||||
# Doorbells need to update more frequently than locks
|
||||
# since we get an image from the doorbell api
|
||||
MIN_TIME_BETWEEN_DOORBELL_STATUS_UPDATES = timedelta(seconds=20)
|
||||
|
||||
# Activity needs to be checked more frequently as the
|
||||
# doorbell motion and rings are included here
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
|
||||
LOGIN_METHODS = ["phone", "email"]
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
|
@ -192,6 +205,7 @@ class AugustData:
|
|||
self._house_ids.add(device.house_id)
|
||||
|
||||
self._doorbell_detail_by_id = {}
|
||||
self._lock_last_status_update_time_utc_by_id = {}
|
||||
self._lock_status_by_id = {}
|
||||
self._lock_detail_by_id = {}
|
||||
self._door_state_by_id = {}
|
||||
|
@ -243,6 +257,7 @@ class AugustData:
|
|||
self._activities_by_id[device_id] = [
|
||||
a for a in activities if a.device_id == device_id
|
||||
]
|
||||
|
||||
_LOGGER.debug("Completed retrieving device activities")
|
||||
|
||||
def get_doorbell_detail(self, doorbell_id):
|
||||
|
@ -250,7 +265,7 @@ class AugustData:
|
|||
self._update_doorbells()
|
||||
return self._doorbell_detail_by_id.get(doorbell_id)
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
@Throttle(MIN_TIME_BETWEEN_DOORBELL_STATUS_UPDATES)
|
||||
def _update_doorbells(self):
|
||||
detail_by_id = {}
|
||||
|
||||
|
@ -275,6 +290,17 @@ class AugustData:
|
|||
_LOGGER.debug("Completed retrieving doorbell details")
|
||||
self._doorbell_detail_by_id = detail_by_id
|
||||
|
||||
def update_lock_status(self, lock_id, lock_status, update_start_time_utc):
|
||||
"""Set the lock status and last status update time.
|
||||
|
||||
This is used when the lock, unlock apis are called
|
||||
or newer activity is detected on the activity feed
|
||||
in order to keep the internal data in sync
|
||||
"""
|
||||
self._lock_status_by_id[lock_id] = lock_status
|
||||
self._lock_last_status_update_time_utc_by_id[lock_id] = update_start_time_utc
|
||||
return True
|
||||
|
||||
def get_lock_status(self, lock_id):
|
||||
"""Return status if the door is locked or unlocked.
|
||||
|
||||
|
@ -300,13 +326,15 @@ class AugustData:
|
|||
self._update_locks_status()
|
||||
self._update_locks_detail()
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
@Throttle(MIN_TIME_BETWEEN_LOCK_STATUS_UPDATES)
|
||||
def _update_locks_status(self):
|
||||
status_by_id = {}
|
||||
state_by_id = {}
|
||||
last_status_update_by_id = {}
|
||||
|
||||
_LOGGER.debug("Start retrieving lock and door status")
|
||||
for lock in self._locks:
|
||||
update_start_time_utc = dt.utcnow()
|
||||
_LOGGER.debug("Updating lock and door status for %s", lock.device_name)
|
||||
try:
|
||||
(
|
||||
|
@ -315,6 +343,12 @@ class AugustData:
|
|||
) = self._api.get_lock_status(
|
||||
self._access_token, lock.device_id, door_status=True
|
||||
)
|
||||
# Since there is a a race condition between calling the
|
||||
# lock and activity apis, we set the last update time
|
||||
# BEFORE making the api call since we will compare this
|
||||
# to activity later we want activity to win over stale lock
|
||||
# state.
|
||||
last_status_update_by_id[lock.device_id] = update_start_time_utc
|
||||
except RequestException as ex:
|
||||
_LOGGER.error(
|
||||
"Request error trying to retrieve lock and door status for %s. %s",
|
||||
|
@ -331,6 +365,17 @@ class AugustData:
|
|||
_LOGGER.debug("Completed retrieving lock and door status")
|
||||
self._lock_status_by_id = status_by_id
|
||||
self._door_state_by_id = state_by_id
|
||||
self._lock_last_status_update_time_utc_by_id = last_status_update_by_id
|
||||
|
||||
def get_last_lock_status_update_time_utc(self, lock_id):
|
||||
"""Return the last time that a lock status update was seen from the august API."""
|
||||
# Since the activity api is called more frequently than
|
||||
# the lock api it is possible that the lock has not
|
||||
# been updated yet
|
||||
if lock_id not in self._lock_last_status_update_time_utc_by_id:
|
||||
return dt.utc_from_timestamp(0)
|
||||
|
||||
return self._lock_last_status_update_time_utc_by_id[lock_id]
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES)
|
||||
def _update_locks_detail(self):
|
||||
|
|
|
@ -7,6 +7,7 @@ from august.lock import LockStatus
|
|||
|
||||
from homeassistant.components.lock import LockDevice
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
from homeassistant.util import dt
|
||||
|
||||
from . import DATA_AUGUST
|
||||
|
||||
|
@ -41,11 +42,23 @@ class AugustLock(LockDevice):
|
|||
|
||||
def lock(self, **kwargs):
|
||||
"""Lock the device."""
|
||||
self._data.lock(self._lock.device_id)
|
||||
update_start_time_utc = dt.utcnow()
|
||||
lock_status = self._data.lock(self._lock.device_id)
|
||||
self._update_lock_status(lock_status, update_start_time_utc)
|
||||
|
||||
def unlock(self, **kwargs):
|
||||
"""Unlock the device."""
|
||||
self._data.unlock(self._lock.device_id)
|
||||
update_start_time_utc = dt.utcnow()
|
||||
lock_status = self._data.unlock(self._lock.device_id)
|
||||
self._update_lock_status(lock_status, update_start_time_utc)
|
||||
|
||||
def _update_lock_status(self, lock_status, update_start_time_utc):
|
||||
if self._lock_status != lock_status:
|
||||
self._lock_status = lock_status
|
||||
self._data.update_lock_status(
|
||||
self._lock.device_id, lock_status, update_start_time_utc
|
||||
)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state of the sensor."""
|
||||
|
@ -60,6 +73,38 @@ class AugustLock(LockDevice):
|
|||
|
||||
if activity is not None:
|
||||
self._changed_by = activity.operated_by
|
||||
self._sync_lock_activity(activity)
|
||||
|
||||
def _sync_lock_activity(self, activity):
|
||||
"""Check the activity for the latest lock/unlock activity (events).
|
||||
|
||||
We use this to determine the lock state in between calls to the lock
|
||||
api as we update it more frequently
|
||||
"""
|
||||
last_lock_status_update_time_utc = self._data.get_last_lock_status_update_time_utc(
|
||||
self._lock.device_id
|
||||
)
|
||||
activity_end_time_utc = dt.as_utc(activity.activity_end_time)
|
||||
|
||||
if activity_end_time_utc > last_lock_status_update_time_utc:
|
||||
_LOGGER.debug(
|
||||
"The activity log has new events for %s: [action=%s] [activity_end_time_utc=%s] > [last_lock_status_update_time_utc=%s]",
|
||||
self.name,
|
||||
activity.action,
|
||||
activity_end_time_utc,
|
||||
last_lock_status_update_time_utc,
|
||||
)
|
||||
activity_start_time_utc = dt.as_utc(activity.activity_start_time)
|
||||
if activity.action == "lock" or activity.action == "onetouchlock":
|
||||
self._update_lock_status(LockStatus.LOCKED, activity_start_time_utc)
|
||||
elif activity.action == "unlock":
|
||||
self._update_lock_status(LockStatus.UNLOCKED, activity_start_time_utc)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Unhandled lock activity action %s for %s",
|
||||
activity.action,
|
||||
self.name,
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue