Read door open/close events from the activity log. (#31732)

As polling for lock status is every 15 minutes,
we read lock and unlock events from the activity log.

An upstream update of py-august was needed to
expose the door open and close events.

Door open and close events are now seen within
a few seconds instead of delayed 15+ minutes.
This commit is contained in:
J. Nick Koston 2020-02-11 19:43:56 -06:00 committed by GitHub
parent 787edf9417
commit 54eb740ff6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 6 deletions

View file

@ -205,6 +205,7 @@ class AugustData:
self._house_ids.add(device.house_id)
self._doorbell_detail_by_id = {}
self._door_last_state_update_time_utc_by_id = {}
self._lock_last_status_update_time_utc_by_id = {}
self._lock_status_by_id = {}
self._lock_detail_by_id = {}
@ -290,6 +291,16 @@ class AugustData:
_LOGGER.debug("Completed retrieving doorbell details")
self._doorbell_detail_by_id = detail_by_id
def update_door_state(self, lock_id, door_state, update_start_time_utc):
"""Set the door status and last status update time.
This is called when newer activity is detected on the activity feed
in order to keep the internal data in sync
"""
self._door_state_by_id[lock_id] = door_state
self._door_last_state_update_time_utc_by_id[lock_id] = update_start_time_utc
return True
def update_lock_status(self, lock_id, lock_status, update_start_time_utc):
"""Set the lock status and last status update time.
@ -330,7 +341,8 @@ class AugustData:
def _update_locks_status(self):
status_by_id = {}
state_by_id = {}
last_status_update_by_id = {}
lock_last_status_update_by_id = {}
door_last_state_update_by_id = {}
_LOGGER.debug("Start retrieving lock and door status")
for lock in self._locks:
@ -346,9 +358,10 @@ class AugustData:
# 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
# to activity later we want activity to win over stale lock/door
# state.
last_status_update_by_id[lock.device_id] = update_start_time_utc
lock_last_status_update_by_id[lock.device_id] = update_start_time_utc
door_last_state_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",
@ -365,7 +378,8 @@ 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
self._door_last_state_update_time_utc_by_id = door_last_state_update_by_id
self._lock_last_status_update_time_utc_by_id = lock_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."""
@ -377,6 +391,16 @@ class AugustData:
return self._lock_last_status_update_time_utc_by_id[lock_id]
def get_last_door_state_update_time_utc(self, lock_id):
"""Return the last time that a door 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 door has not
# been updated yet
if lock_id not in self._door_last_state_update_time_utc_by_id:
return dt.utc_from_timestamp(0)
return self._door_last_state_update_time_utc_by_id[lock_id]
@Throttle(MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES)
def _update_locks_detail(self):
detail_by_id = {}

View file

@ -6,6 +6,7 @@ from august.activity import ActivityType
from august.lock import LockDoorStatus
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.util import dt
from . import DATA_AUGUST
@ -137,6 +138,52 @@ class AugustDoorBinarySensor(BinarySensorDevice):
self._state = self._state == LockDoorStatus.OPEN
activity = self._data.get_latest_device_activity(
self._door.device_id, ActivityType.DOOR_OPERATION
)
if activity is not None:
self._sync_door_activity(activity)
def _update_door_state(self, door_state, update_start_time):
new_state = door_state == LockDoorStatus.OPEN
if self._state != new_state:
self._state = new_state
self._data.update_door_state(
self._door.device_id, door_state, update_start_time
)
def _sync_door_activity(self, activity):
"""Check the activity for the latest door open/close activity (events).
We use this to determine the door state in between calls to the lock
api as we update it more frequently
"""
last_door_state_update_time_utc = self._data.get_last_door_state_update_time_utc(
self._door.device_id
)
activity_end_time_utc = dt.as_utc(activity.activity_end_time)
if activity_end_time_utc > last_door_state_update_time_utc:
_LOGGER.debug(
"The activity log has new events for %s: [action=%s] [activity_end_time_utc=%s] > [last_door_state_update_time_utc=%s]",
self.name,
activity.action,
activity_end_time_utc,
last_door_state_update_time_utc,
)
activity_start_time_utc = dt.as_utc(activity.activity_start_time)
if activity.action == "doorclosed":
self._update_door_state(LockDoorStatus.CLOSED, activity_start_time_utc)
elif activity.action == "dooropen":
self._update_door_state(LockDoorStatus.OPEN, activity_start_time_utc)
else:
_LOGGER.info(
"Unhandled door activity action %s for %s",
activity.action,
self.name,
)
@property
def unique_id(self) -> str:
"""Get the unique of the door open binary sensor."""

View file

@ -2,7 +2,7 @@
"domain": "august",
"name": "August",
"documentation": "https://www.home-assistant.io/integrations/august",
"requirements": ["py-august==0.8.1"],
"requirements": ["py-august==0.11.0"],
"dependencies": ["configurator"],
"codeowners": []
}

View file

@ -1074,7 +1074,7 @@ pushover_complete==1.1.1
pwmled==1.4.1
# homeassistant.components.august
py-august==0.8.1
py-august==0.11.0
# homeassistant.components.canary
py-canary==0.5.0