2019-02-13 20:21:14 +00:00
|
|
|
"""Support for August devices."""
|
2018-02-18 08:24:51 +00:00
|
|
|
from datetime import timedelta
|
2019-10-18 00:06:41 +00:00
|
|
|
import logging
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2019-10-18 00:06:41 +00:00
|
|
|
from august.api import Api
|
|
|
|
from august.authenticator import AuthenticationState, Authenticator, ValidationResult
|
|
|
|
from requests import RequestException, Session
|
2018-02-18 08:24:51 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_PASSWORD,
|
|
|
|
CONF_TIMEOUT,
|
2019-10-18 00:06:41 +00:00
|
|
|
CONF_USERNAME,
|
2019-07-31 19:25:30 +00:00
|
|
|
EVENT_HOMEASSISTANT_STOP,
|
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
from homeassistant.helpers import discovery
|
2019-10-18 00:06:41 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2020-02-11 16:57:26 +00:00
|
|
|
from homeassistant.util import Throttle, dt
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
_CONFIGURING = {}
|
|
|
|
|
|
|
|
DEFAULT_TIMEOUT = 10
|
|
|
|
ACTIVITY_FETCH_LIMIT = 10
|
|
|
|
ACTIVITY_INITIAL_FETCH_LIMIT = 20
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONF_LOGIN_METHOD = "login_method"
|
|
|
|
CONF_INSTALL_ID = "install_id"
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
NOTIFICATION_ID = "august_notification"
|
2018-02-18 08:24:51 +00:00
|
|
|
NOTIFICATION_TITLE = "August Setup"
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
AUGUST_CONFIG_FILE = ".august.conf"
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DATA_AUGUST = "august"
|
|
|
|
DOMAIN = "august"
|
|
|
|
DEFAULT_ENTITY_NAMESPACE = "august"
|
2020-02-08 19:22:48 +00:00
|
|
|
|
|
|
|
# Limit battery and hardware updates to 1800 seconds
|
|
|
|
# in order to reduce the number of api requests and
|
|
|
|
# avoid hitting rate limits
|
|
|
|
MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES = timedelta(seconds=1800)
|
|
|
|
|
2020-02-11 16:57:26 +00:00
|
|
|
# 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
|
2020-02-08 19:22:48 +00:00
|
|
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
|
|
|
|
|
2020-02-11 16:57:26 +00:00
|
|
|
DEFAULT_SCAN_INTERVAL = timedelta(seconds=10)
|
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
LOGIN_METHODS = ["phone", "email"]
|
|
|
|
|
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
DOMAIN: vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS),
|
|
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
|
|
vol.Optional(CONF_INSTALL_ID): cv.string,
|
|
|
|
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
|
|
|
|
|
|
|
AUGUST_COMPONENTS = ["camera", "binary_sensor", "lock"]
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
def request_configuration(hass, config, api, authenticator):
|
|
|
|
"""Request configuration steps from the user."""
|
|
|
|
configurator = hass.components.configurator
|
|
|
|
|
|
|
|
def august_configuration_callback(data):
|
|
|
|
"""Run when the configuration callback is called."""
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
result = authenticator.validate_verification_code(data.get("verification_code"))
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
if result == ValidationResult.INVALID_VERIFICATION_CODE:
|
2019-07-31 19:25:30 +00:00
|
|
|
configurator.notify_errors(
|
|
|
|
_CONFIGURING[DOMAIN], "Invalid verification code"
|
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
elif result == ValidationResult.VALIDATED:
|
|
|
|
setup_august(hass, config, api, authenticator)
|
|
|
|
|
|
|
|
if DOMAIN not in _CONFIGURING:
|
|
|
|
authenticator.send_verification_code()
|
|
|
|
|
|
|
|
conf = config[DOMAIN]
|
|
|
|
username = conf.get(CONF_USERNAME)
|
|
|
|
login_method = conf.get(CONF_LOGIN_METHOD)
|
|
|
|
|
|
|
|
_CONFIGURING[DOMAIN] = configurator.request_config(
|
|
|
|
NOTIFICATION_TITLE,
|
|
|
|
august_configuration_callback,
|
|
|
|
description="Please check your {} ({}) and enter the verification "
|
2019-07-31 19:25:30 +00:00
|
|
|
"code below".format(login_method, username),
|
|
|
|
submit_caption="Verify",
|
|
|
|
fields=[
|
|
|
|
{"id": "verification_code", "name": "Verification code", "type": "string"}
|
|
|
|
],
|
2018-02-18 08:24:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def setup_august(hass, config, api, authenticator):
|
|
|
|
"""Set up the August component."""
|
|
|
|
|
|
|
|
authentication = None
|
|
|
|
try:
|
|
|
|
authentication = authenticator.authenticate()
|
|
|
|
except RequestException as ex:
|
|
|
|
_LOGGER.error("Unable to connect to August service: %s", str(ex))
|
|
|
|
|
|
|
|
hass.components.persistent_notification.create(
|
|
|
|
"Error: {}<br />"
|
|
|
|
"You will need to restart hass after fixing."
|
|
|
|
"".format(ex),
|
|
|
|
title=NOTIFICATION_TITLE,
|
2019-07-31 19:25:30 +00:00
|
|
|
notification_id=NOTIFICATION_ID,
|
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
state = authentication.state
|
|
|
|
|
|
|
|
if state == AuthenticationState.AUTHENTICATED:
|
|
|
|
if DOMAIN in _CONFIGURING:
|
|
|
|
hass.components.configurator.request_done(_CONFIGURING.pop(DOMAIN))
|
|
|
|
|
2020-02-12 06:13:54 +00:00
|
|
|
hass.data[DATA_AUGUST] = AugustData(hass, api, authentication, authenticator)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
for component in AUGUST_COMPONENTS:
|
|
|
|
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
|
|
|
|
|
|
|
return True
|
2018-07-23 08:16:05 +00:00
|
|
|
if state == AuthenticationState.BAD_PASSWORD:
|
2018-10-19 07:37:02 +00:00
|
|
|
_LOGGER.error("Invalid password provided")
|
2018-02-18 08:24:51 +00:00
|
|
|
return False
|
2018-07-23 08:16:05 +00:00
|
|
|
if state == AuthenticationState.REQUIRES_VALIDATION:
|
2018-02-18 08:24:51 +00:00
|
|
|
request_configuration(hass, config, api, authenticator)
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def setup(hass, config):
|
|
|
|
"""Set up the August component."""
|
|
|
|
|
|
|
|
conf = config[DOMAIN]
|
2018-11-27 19:41:25 +00:00
|
|
|
api_http_session = None
|
2018-11-19 10:53:27 +00:00
|
|
|
try:
|
|
|
|
api_http_session = Session()
|
|
|
|
except RequestException as ex:
|
|
|
|
_LOGGER.warning("Creating HTTP session failed with: %s", str(ex))
|
|
|
|
|
|
|
|
api = Api(timeout=conf.get(CONF_TIMEOUT), http_session=api_http_session)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
authenticator = Authenticator(
|
|
|
|
api,
|
|
|
|
conf.get(CONF_LOGIN_METHOD),
|
|
|
|
conf.get(CONF_USERNAME),
|
|
|
|
conf.get(CONF_PASSWORD),
|
|
|
|
install_id=conf.get(CONF_INSTALL_ID),
|
2019-07-31 19:25:30 +00:00
|
|
|
access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE),
|
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2018-11-27 19:41:25 +00:00
|
|
|
def close_http_session(event):
|
|
|
|
"""Close API sessions used to connect to August."""
|
|
|
|
_LOGGER.debug("Closing August HTTP sessions")
|
|
|
|
if api_http_session:
|
|
|
|
try:
|
|
|
|
api_http_session.close()
|
|
|
|
except RequestException:
|
|
|
|
pass
|
|
|
|
|
|
|
|
_LOGGER.debug("August HTTP session closed.")
|
|
|
|
|
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_http_session)
|
2020-01-05 12:09:17 +00:00
|
|
|
_LOGGER.debug("Registered for Home Assistant stop event")
|
2018-11-27 19:41:25 +00:00
|
|
|
|
2018-02-18 08:24:51 +00:00
|
|
|
return setup_august(hass, config, api, authenticator)
|
|
|
|
|
|
|
|
|
|
|
|
class AugustData:
|
|
|
|
"""August data object."""
|
|
|
|
|
2020-02-12 06:13:54 +00:00
|
|
|
def __init__(self, hass, api, authentication, authenticator):
|
2018-02-18 08:24:51 +00:00
|
|
|
"""Init August data object."""
|
2018-11-19 10:53:27 +00:00
|
|
|
self._hass = hass
|
2018-02-18 08:24:51 +00:00
|
|
|
self._api = api
|
2020-02-12 06:13:54 +00:00
|
|
|
self._authenticator = authenticator
|
|
|
|
self._access_token = authentication.access_token
|
|
|
|
self._access_token_expires = authentication.access_token_expires
|
|
|
|
|
2018-02-18 08:24:51 +00:00
|
|
|
self._doorbells = self._api.get_doorbells(self._access_token) or []
|
2018-03-04 09:14:47 +00:00
|
|
|
self._locks = self._api.get_operable_locks(self._access_token) or []
|
2020-02-08 19:22:48 +00:00
|
|
|
self._house_ids = set()
|
|
|
|
for device in self._doorbells + self._locks:
|
|
|
|
self._house_ids.add(device.house_id)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
self._doorbell_detail_by_id = {}
|
2020-02-12 01:43:56 +00:00
|
|
|
self._door_last_state_update_time_utc_by_id = {}
|
2020-02-11 16:57:26 +00:00
|
|
|
self._lock_last_status_update_time_utc_by_id = {}
|
2018-02-18 08:24:51 +00:00
|
|
|
self._lock_status_by_id = {}
|
|
|
|
self._lock_detail_by_id = {}
|
2018-10-19 07:37:02 +00:00
|
|
|
self._door_state_by_id = {}
|
2018-02-18 08:24:51 +00:00
|
|
|
self._activities_by_id = {}
|
|
|
|
|
|
|
|
@property
|
|
|
|
def house_ids(self):
|
|
|
|
"""Return a list of house_ids."""
|
|
|
|
return self._house_ids
|
|
|
|
|
|
|
|
@property
|
|
|
|
def doorbells(self):
|
|
|
|
"""Return a list of doorbells."""
|
|
|
|
return self._doorbells
|
|
|
|
|
|
|
|
@property
|
|
|
|
def locks(self):
|
|
|
|
"""Return a list of locks."""
|
|
|
|
return self._locks
|
|
|
|
|
2020-02-12 06:13:54 +00:00
|
|
|
def _refresh_access_token_if_needed(self):
|
|
|
|
"""Refresh the august access token if needed."""
|
|
|
|
|
|
|
|
if self._authenticator.should_refresh():
|
|
|
|
refreshed_authentication = self._authenticator.refresh_access_token(
|
|
|
|
force=False
|
|
|
|
)
|
|
|
|
_LOGGER.info(
|
|
|
|
"Refreshed august access token. The old token expired at %s, and the new token expires at %s",
|
|
|
|
self._access_token_expires,
|
|
|
|
refreshed_authentication.access_token_expires,
|
|
|
|
)
|
|
|
|
self._access_token = refreshed_authentication.access_token
|
|
|
|
self._access_token_expires = refreshed_authentication.access_token_expires
|
|
|
|
|
2018-02-18 08:24:51 +00:00
|
|
|
def get_device_activities(self, device_id, *activity_types):
|
|
|
|
"""Return a list of activities."""
|
2018-10-19 07:37:02 +00:00
|
|
|
_LOGGER.debug("Getting device activities")
|
2018-02-18 08:24:51 +00:00
|
|
|
self._update_device_activities()
|
|
|
|
|
|
|
|
activities = self._activities_by_id.get(device_id, [])
|
|
|
|
if activity_types:
|
|
|
|
return [a for a in activities if a.activity_type in activity_types]
|
|
|
|
return activities
|
|
|
|
|
|
|
|
def get_latest_device_activity(self, device_id, *activity_types):
|
|
|
|
"""Return latest activity."""
|
|
|
|
activities = self.get_device_activities(device_id, *activity_types)
|
|
|
|
return next(iter(activities or []), None)
|
|
|
|
|
|
|
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
|
|
|
def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT):
|
|
|
|
"""Update data object with latest from August API."""
|
2020-02-12 06:13:54 +00:00
|
|
|
|
|
|
|
# This is the only place we refresh the api token
|
|
|
|
# in order to avoid multiple threads from doing it at the same time
|
|
|
|
# since there will only be one activity refresh at a time
|
|
|
|
#
|
|
|
|
# In the future when this module is converted to async we should
|
|
|
|
# use a lock to prevent all api calls while the token
|
|
|
|
# is being refreshed as this is a better solution
|
|
|
|
#
|
|
|
|
self._refresh_access_token_if_needed()
|
|
|
|
|
2018-11-19 10:53:27 +00:00
|
|
|
_LOGGER.debug("Start retrieving device activities")
|
2018-02-18 08:24:51 +00:00
|
|
|
for house_id in self.house_ids:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.debug("Updating device activity for house id %s", house_id)
|
2018-11-19 10:53:27 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
activities = self._api.get_house_activities(
|
|
|
|
self._access_token, house_id, limit=limit
|
|
|
|
)
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
device_ids = {a.device_id for a in activities}
|
|
|
|
for device_id in device_ids:
|
2019-07-31 19:25:30 +00:00
|
|
|
self._activities_by_id[device_id] = [
|
|
|
|
a for a in activities if a.device_id == device_id
|
|
|
|
]
|
2020-02-11 16:57:26 +00:00
|
|
|
|
2018-11-19 10:53:27 +00:00
|
|
|
_LOGGER.debug("Completed retrieving device activities")
|
2018-02-18 08:24:51 +00:00
|
|
|
|
|
|
|
def get_doorbell_detail(self, doorbell_id):
|
|
|
|
"""Return doorbell detail."""
|
|
|
|
self._update_doorbells()
|
|
|
|
return self._doorbell_detail_by_id.get(doorbell_id)
|
|
|
|
|
2020-02-11 16:57:26 +00:00
|
|
|
@Throttle(MIN_TIME_BETWEEN_DOORBELL_STATUS_UPDATES)
|
2018-02-18 08:24:51 +00:00
|
|
|
def _update_doorbells(self):
|
|
|
|
detail_by_id = {}
|
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
_LOGGER.debug("Start retrieving doorbell details")
|
2018-02-18 08:24:51 +00:00
|
|
|
for doorbell in self._doorbells:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.debug("Updating doorbell status for %s", doorbell.device_name)
|
2018-10-23 12:09:08 +00:00
|
|
|
try:
|
2019-07-31 19:25:30 +00:00
|
|
|
detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail(
|
|
|
|
self._access_token, doorbell.device_id
|
|
|
|
)
|
2018-10-23 12:09:08 +00:00
|
|
|
except RequestException as ex:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error(
|
2020-01-02 19:17:10 +00:00
|
|
|
"Request error trying to retrieve doorbell status for %s. %s",
|
2019-07-31 19:25:30 +00:00
|
|
|
doorbell.device_name,
|
|
|
|
ex,
|
|
|
|
)
|
2018-10-23 12:09:08 +00:00
|
|
|
detail_by_id[doorbell.device_id] = None
|
|
|
|
except Exception:
|
|
|
|
detail_by_id[doorbell.device_id] = None
|
|
|
|
raise
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
_LOGGER.debug("Completed retrieving doorbell details")
|
2018-02-18 08:24:51 +00:00
|
|
|
self._doorbell_detail_by_id = detail_by_id
|
|
|
|
|
2020-02-12 01:43:56 +00:00
|
|
|
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
|
|
|
|
|
2020-02-11 16:57:26 +00:00
|
|
|
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
|
|
|
|
|
2018-02-18 08:24:51 +00:00
|
|
|
def get_lock_status(self, lock_id):
|
2018-10-19 07:37:02 +00:00
|
|
|
"""Return status if the door is locked or unlocked.
|
|
|
|
|
|
|
|
This is status for the lock itself.
|
|
|
|
"""
|
2018-02-18 08:24:51 +00:00
|
|
|
self._update_locks()
|
|
|
|
return self._lock_status_by_id.get(lock_id)
|
|
|
|
|
|
|
|
def get_lock_detail(self, lock_id):
|
|
|
|
"""Return lock detail."""
|
|
|
|
self._update_locks()
|
|
|
|
return self._lock_detail_by_id.get(lock_id)
|
|
|
|
|
2018-10-19 07:37:02 +00:00
|
|
|
def get_door_state(self, lock_id):
|
|
|
|
"""Return status if the door is open or closed.
|
|
|
|
|
|
|
|
This is the status from the door sensor.
|
|
|
|
"""
|
2020-02-08 19:22:48 +00:00
|
|
|
self._update_locks_status()
|
2018-10-19 07:37:02 +00:00
|
|
|
return self._door_state_by_id.get(lock_id)
|
|
|
|
|
2020-02-08 19:22:48 +00:00
|
|
|
def _update_locks(self):
|
|
|
|
self._update_locks_status()
|
|
|
|
self._update_locks_detail()
|
|
|
|
|
2020-02-11 16:57:26 +00:00
|
|
|
@Throttle(MIN_TIME_BETWEEN_LOCK_STATUS_UPDATES)
|
2020-02-08 19:22:48 +00:00
|
|
|
def _update_locks_status(self):
|
|
|
|
status_by_id = {}
|
2018-10-19 07:37:02 +00:00
|
|
|
state_by_id = {}
|
2020-02-12 01:43:56 +00:00
|
|
|
lock_last_status_update_by_id = {}
|
|
|
|
door_last_state_update_by_id = {}
|
2018-10-19 07:37:02 +00:00
|
|
|
|
2020-02-08 19:22:48 +00:00
|
|
|
_LOGGER.debug("Start retrieving lock and door status")
|
2018-10-19 07:37:02 +00:00
|
|
|
for lock in self._locks:
|
2020-02-11 16:57:26 +00:00
|
|
|
update_start_time_utc = dt.utcnow()
|
2020-02-08 19:22:48 +00:00
|
|
|
_LOGGER.debug("Updating lock and door status for %s", lock.device_name)
|
2018-10-23 12:09:08 +00:00
|
|
|
try:
|
2020-02-08 19:22:48 +00:00
|
|
|
(
|
|
|
|
status_by_id[lock.device_id],
|
|
|
|
state_by_id[lock.device_id],
|
|
|
|
) = self._api.get_lock_status(
|
|
|
|
self._access_token, lock.device_id, door_status=True
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2020-02-11 16:57:26 +00:00
|
|
|
# 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
|
2020-02-12 01:43:56 +00:00
|
|
|
# to activity later we want activity to win over stale lock/door
|
2020-02-11 16:57:26 +00:00
|
|
|
# state.
|
2020-02-12 01:43:56 +00:00
|
|
|
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
|
2018-10-23 12:09:08 +00:00
|
|
|
except RequestException as ex:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error(
|
2020-02-08 19:22:48 +00:00
|
|
|
"Request error trying to retrieve lock and door status for %s. %s",
|
2019-07-31 19:25:30 +00:00
|
|
|
lock.device_name,
|
|
|
|
ex,
|
|
|
|
)
|
2020-02-08 19:22:48 +00:00
|
|
|
status_by_id[lock.device_id] = None
|
2018-10-23 12:09:08 +00:00
|
|
|
state_by_id[lock.device_id] = None
|
|
|
|
except Exception:
|
2020-02-08 19:22:48 +00:00
|
|
|
status_by_id[lock.device_id] = None
|
2018-10-23 12:09:08 +00:00
|
|
|
state_by_id[lock.device_id] = None
|
|
|
|
raise
|
2018-10-19 07:37:02 +00:00
|
|
|
|
2020-02-08 19:22:48 +00:00
|
|
|
_LOGGER.debug("Completed retrieving lock and door status")
|
|
|
|
self._lock_status_by_id = status_by_id
|
2018-10-19 07:37:02 +00:00
|
|
|
self._door_state_by_id = state_by_id
|
2020-02-12 01:43:56 +00:00
|
|
|
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
|
2020-02-11 16:57:26 +00:00
|
|
|
|
|
|
|
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]
|
2018-10-19 07:37:02 +00:00
|
|
|
|
2020-02-12 01:43:56 +00:00
|
|
|
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]
|
|
|
|
|
2020-02-08 19:22:48 +00:00
|
|
|
@Throttle(MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES)
|
|
|
|
def _update_locks_detail(self):
|
2018-02-18 08:24:51 +00:00
|
|
|
detail_by_id = {}
|
|
|
|
|
2020-02-08 19:22:48 +00:00
|
|
|
_LOGGER.debug("Start retrieving locks detail")
|
2018-02-18 08:24:51 +00:00
|
|
|
for lock in self._locks:
|
2018-10-23 12:09:08 +00:00
|
|
|
try:
|
|
|
|
detail_by_id[lock.device_id] = self._api.get_lock_detail(
|
2019-07-31 19:25:30 +00:00
|
|
|
self._access_token, lock.device_id
|
|
|
|
)
|
2018-10-23 12:09:08 +00:00
|
|
|
except RequestException as ex:
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.error(
|
2020-01-02 19:17:10 +00:00
|
|
|
"Request error trying to retrieve door details for %s. %s",
|
2019-07-31 19:25:30 +00:00
|
|
|
lock.device_name,
|
|
|
|
ex,
|
|
|
|
)
|
2018-10-23 12:09:08 +00:00
|
|
|
detail_by_id[lock.device_id] = None
|
|
|
|
except Exception:
|
|
|
|
detail_by_id[lock.device_id] = None
|
|
|
|
raise
|
2018-02-18 08:24:51 +00:00
|
|
|
|
2020-02-08 19:22:48 +00:00
|
|
|
_LOGGER.debug("Completed retrieving locks detail")
|
2018-02-18 08:24:51 +00:00
|
|
|
self._lock_detail_by_id = detail_by_id
|
|
|
|
|
|
|
|
def lock(self, device_id):
|
|
|
|
"""Lock the device."""
|
|
|
|
return self._api.lock(self._access_token, device_id)
|
|
|
|
|
|
|
|
def unlock(self, device_id):
|
|
|
|
"""Unlock the device."""
|
|
|
|
return self._api.unlock(self._access_token, device_id)
|