Significantly reduce code in august integration (#32030)
* Significantly reduce code in august integration * Activity updates can now be processed by py-august this allows us to eliminate the activity sync code for the door sensors and locks * Lock and door state can now be consumed from the lock detail api which allows us to remove the status call apis and reduce the number of API calls to august * Refactor the testing method for locks (part #1) * Update homeassistant/components/august/binary_sensor.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * Switch to asynctest instead of unittest for mock.patch Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
a12c4da0ca
commit
d4075fb262
17 changed files with 579 additions and 429 deletions
|
@ -18,7 +18,7 @@ from homeassistant.const import (
|
|||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle, dt
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -45,11 +45,6 @@ DEFAULT_ENTITY_NAMESPACE = "august"
|
|||
# avoid hitting rate limits
|
||||
MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES = timedelta(seconds=1800)
|
||||
|
||||
# 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)
|
||||
|
@ -218,16 +213,11 @@ 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 = {}
|
||||
self._door_state_by_id = {}
|
||||
self._activities_by_id = {}
|
||||
|
||||
# We check the locks right away so we can
|
||||
# remove inoperative ones
|
||||
self._update_locks_status()
|
||||
self._update_locks_detail()
|
||||
|
||||
self._filter_inoperative_locks()
|
||||
|
@ -344,8 +334,13 @@ class AugustData:
|
|||
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
|
||||
# When syncing the door state became available via py-august, this
|
||||
# function caused to be actively used. It will be again as we will
|
||||
# update the door state from lock/unlock operations as the august api
|
||||
# does report the door state on lock/unlock, however py-august does not
|
||||
# expose this to us yet.
|
||||
self._lock_detail_by_id[lock_id].door_state = door_state
|
||||
self._lock_detail_by_id[lock_id].door_state_datetime = update_start_time_utc
|
||||
return True
|
||||
|
||||
def update_lock_status(self, lock_id, lock_status, update_start_time_utc):
|
||||
|
@ -355,8 +350,8 @@ class AugustData:
|
|||
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
|
||||
self._lock_detail_by_id[lock_id].lock_status = lock_status
|
||||
self._lock_detail_by_id[lock_id].lock_status_datetime = update_start_time_utc
|
||||
return True
|
||||
|
||||
def lock_has_doorsense(self, lock_id):
|
||||
|
@ -367,18 +362,10 @@ class AugustData:
|
|||
return False
|
||||
return self._lock_detail_by_id[lock_id].doorsense
|
||||
|
||||
async def async_get_lock_status(self, lock_id):
|
||||
"""Return status if the door is locked or unlocked.
|
||||
|
||||
This is status for the lock itself.
|
||||
"""
|
||||
await self._async_update_locks()
|
||||
return self._lock_status_by_id.get(lock_id)
|
||||
|
||||
async def async_get_lock_detail(self, lock_id):
|
||||
"""Return lock detail."""
|
||||
await self._async_update_locks()
|
||||
return self._lock_detail_by_id.get(lock_id)
|
||||
await self._async_update_locks_detail()
|
||||
return self._lock_detail_by_id[lock_id]
|
||||
|
||||
def get_lock_name(self, device_id):
|
||||
"""Return lock name as August has it stored."""
|
||||
|
@ -386,85 +373,6 @@ class AugustData:
|
|||
if lock.device_id == device_id:
|
||||
return lock.device_name
|
||||
|
||||
async def async_get_door_state(self, lock_id):
|
||||
"""Return status if the door is open or closed.
|
||||
|
||||
This is the status from the door sensor.
|
||||
"""
|
||||
await self._async_update_locks_status()
|
||||
return self._door_state_by_id.get(lock_id)
|
||||
|
||||
async def _async_update_locks(self):
|
||||
await self._async_update_locks_status()
|
||||
await self._async_update_locks_detail()
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_LOCK_STATUS_UPDATES)
|
||||
async def _async_update_locks_status(self):
|
||||
await self._hass.async_add_executor_job(self._update_locks_status)
|
||||
|
||||
def _update_locks_status(self):
|
||||
status_by_id = {}
|
||||
state_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:
|
||||
update_start_time_utc = dt.utcnow()
|
||||
_LOGGER.debug("Updating lock and door status for %s", lock.device_name)
|
||||
try:
|
||||
(
|
||||
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
|
||||
)
|
||||
# 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/door
|
||||
# state.
|
||||
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",
|
||||
lock.device_name,
|
||||
ex,
|
||||
)
|
||||
status_by_id[lock.device_id] = None
|
||||
state_by_id[lock.device_id] = None
|
||||
except Exception:
|
||||
status_by_id[lock.device_id] = None
|
||||
state_by_id[lock.device_id] = None
|
||||
raise
|
||||
|
||||
_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._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."""
|
||||
# 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]
|
||||
|
||||
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)
|
||||
async def _async_update_locks_detail(self):
|
||||
await self._hass.async_add_executor_job(self._update_locks_detail)
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
from august.activity import ACTIVITY_ACTION_STATES, ActivityType
|
||||
from august.activity import ActivityType
|
||||
from august.lock import LockDoorStatus
|
||||
from august.util import update_lock_detail_from_activity
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.util import dt
|
||||
|
||||
from . import DATA_AUGUST
|
||||
|
||||
|
@ -15,11 +15,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
|
||||
async def _async_retrieve_door_state(data, lock):
|
||||
"""Get the latest state of the DoorSense sensor."""
|
||||
return await data.async_get_door_state(lock.device_id)
|
||||
|
||||
|
||||
async def _async_retrieve_online_state(data, doorbell):
|
||||
"""Get the latest state of the sensor."""
|
||||
detail = await data.async_get_doorbell_detail(doorbell.device_id)
|
||||
|
@ -61,8 +56,6 @@ SENSOR_DEVICE_CLASS = 1
|
|||
SENSOR_STATE_PROVIDER = 2
|
||||
|
||||
# sensor_type: [name, device_class, async_state_provider]
|
||||
SENSOR_TYPES_DOOR = {"door_open": ["Open", "door", _async_retrieve_door_state]}
|
||||
|
||||
SENSOR_TYPES_DOORBELL = {
|
||||
"doorbell_ding": ["Ding", "occupancy", _async_retrieve_ding_state],
|
||||
"doorbell_motion": ["Motion", "motion", _async_retrieve_motion_state],
|
||||
|
@ -76,21 +69,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
devices = []
|
||||
|
||||
for door in data.locks:
|
||||
for sensor_type in SENSOR_TYPES_DOOR:
|
||||
if not data.lock_has_doorsense(door.device_id):
|
||||
_LOGGER.debug(
|
||||
"Not adding sensor class %s for lock %s ",
|
||||
SENSOR_TYPES_DOOR[sensor_type][SENSOR_DEVICE_CLASS],
|
||||
door.device_name,
|
||||
)
|
||||
continue
|
||||
|
||||
if not data.lock_has_doorsense(door.device_id):
|
||||
_LOGGER.debug(
|
||||
"Adding sensor class %s for %s",
|
||||
SENSOR_TYPES_DOOR[sensor_type][SENSOR_DEVICE_CLASS],
|
||||
door.device_name,
|
||||
"Not adding sensor class door for lock %s ", door.device_name,
|
||||
)
|
||||
devices.append(AugustDoorBinarySensor(data, sensor_type, door))
|
||||
continue
|
||||
|
||||
_LOGGER.debug(
|
||||
"Adding sensor class door for %s", door.device_name,
|
||||
)
|
||||
devices.append(AugustDoorBinarySensor(data, "door_open", door))
|
||||
|
||||
for doorbell in data.doorbells:
|
||||
for sensor_type in SENSOR_TYPES_DOORBELL:
|
||||
|
@ -127,81 +115,35 @@ class AugustDoorBinarySensor(BinarySensorDevice):
|
|||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return SENSOR_TYPES_DOOR[self._sensor_type][SENSOR_DEVICE_CLASS]
|
||||
"""Return the class of this device."""
|
||||
return "door"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the binary sensor."""
|
||||
return "{} {}".format(
|
||||
self._door.device_name, SENSOR_TYPES_DOOR[self._sensor_type][SENSOR_NAME]
|
||||
)
|
||||
return "{} Open".format(self._door.device_name)
|
||||
|
||||
async def async_update(self):
|
||||
"""Get the latest state of the sensor and update activity."""
|
||||
async_state_provider = SENSOR_TYPES_DOOR[self._sensor_type][
|
||||
SENSOR_STATE_PROVIDER
|
||||
]
|
||||
lock_door_state = await async_state_provider(self._data, self._door)
|
||||
self._available = (
|
||||
lock_door_state is not None and lock_door_state != LockDoorStatus.UNKNOWN
|
||||
)
|
||||
self._state = lock_door_state == LockDoorStatus.OPEN
|
||||
|
||||
door_activity = await self._data.async_get_latest_device_activity(
|
||||
self._door.device_id, ActivityType.DOOR_OPERATION
|
||||
)
|
||||
detail = await self._data.async_get_lock_detail(self._door.device_id)
|
||||
|
||||
if door_activity is not None:
|
||||
self._sync_door_activity(door_activity)
|
||||
update_lock_detail_from_activity(detail, door_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
|
||||
)
|
||||
lock_door_state = None
|
||||
if detail is not None:
|
||||
lock_door_state = detail.door_state
|
||||
|
||||
def _sync_door_activity(self, door_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(door_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,
|
||||
door_activity.action,
|
||||
activity_end_time_utc,
|
||||
last_door_state_update_time_utc,
|
||||
)
|
||||
activity_start_time_utc = dt.as_utc(door_activity.activity_start_time)
|
||||
if door_activity.action in ACTIVITY_ACTION_STATES:
|
||||
self._update_door_state(
|
||||
ACTIVITY_ACTION_STATES[door_activity.action],
|
||||
activity_start_time_utc,
|
||||
)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Unhandled door activity action %s for %s",
|
||||
door_activity.action,
|
||||
self.name,
|
||||
)
|
||||
self._available = lock_door_state != LockDoorStatus.UNKNOWN
|
||||
self._state = lock_door_state == LockDoorStatus.OPEN
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Get the unique of the door open binary sensor."""
|
||||
return "{:s}_{:s}".format(
|
||||
self._door.device_id,
|
||||
SENSOR_TYPES_DOOR[self._sensor_type][SENSOR_NAME].lower(),
|
||||
)
|
||||
return f"{self._door.device_id}_open"
|
||||
|
||||
|
||||
class AugustDoorbellBinarySensor(BinarySensorDevice):
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from august.activity import ACTIVITY_ACTION_STATES, ActivityType
|
||||
from august.activity import ActivityType
|
||||
from august.lock import LockStatus
|
||||
from august.util import update_lock_detail_from_activity
|
||||
|
||||
from homeassistant.components.lock import LockDevice
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
|
@ -13,7 +14,7 @@ from . import DATA_AUGUST
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
|
@ -66,51 +67,19 @@ class AugustLock(LockDevice):
|
|||
|
||||
async def async_update(self):
|
||||
"""Get the latest state of the sensor and update activity."""
|
||||
self._lock_status = await self._data.async_get_lock_status(self._lock.device_id)
|
||||
self._available = (
|
||||
self._lock_status is not None and self._lock_status != LockStatus.UNKNOWN
|
||||
)
|
||||
self._lock_detail = await self._data.async_get_lock_detail(self._lock.device_id)
|
||||
|
||||
lock_activity = await self._data.async_get_latest_device_activity(
|
||||
self._lock.device_id, ActivityType.LOCK_OPERATION
|
||||
)
|
||||
|
||||
if lock_activity is not None:
|
||||
self._changed_by = lock_activity.operated_by
|
||||
self._sync_lock_activity(lock_activity)
|
||||
update_lock_detail_from_activity(self._lock_detail, lock_activity)
|
||||
|
||||
def _sync_lock_activity(self, lock_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
|
||||
self._lock_status = self._lock_detail.lock_status
|
||||
self._available = (
|
||||
self._lock_status is not None and self._lock_status != LockStatus.UNKNOWN
|
||||
)
|
||||
activity_end_time_utc = dt.as_utc(lock_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,
|
||||
lock_activity.action,
|
||||
activity_end_time_utc,
|
||||
last_lock_status_update_time_utc,
|
||||
)
|
||||
activity_start_time_utc = dt.as_utc(lock_activity.activity_start_time)
|
||||
if lock_activity.action in ACTIVITY_ACTION_STATES:
|
||||
self._update_lock_status(
|
||||
ACTIVITY_ACTION_STATES[lock_activity.action],
|
||||
activity_start_time_utc,
|
||||
)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Unhandled lock activity action %s for %s",
|
||||
lock_activity.action,
|
||||
self.name,
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"domain": "august",
|
||||
"name": "August",
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"requirements": ["py-august==0.14.0"],
|
||||
"requirements": ["py-august==0.17.0"],
|
||||
"dependencies": ["configurator"],
|
||||
"codeowners": ["@bdraco"]
|
||||
}
|
||||
|
|
|
@ -1075,7 +1075,7 @@ pushover_complete==1.1.1
|
|||
pwmled==1.5.0
|
||||
|
||||
# homeassistant.components.august
|
||||
py-august==0.14.0
|
||||
py-august==0.17.0
|
||||
|
||||
# homeassistant.components.canary
|
||||
py-canary==0.5.0
|
||||
|
|
|
@ -391,7 +391,7 @@ pure-python-adb==0.2.2.dev0
|
|||
pushbullet.py==0.11.0
|
||||
|
||||
# homeassistant.components.august
|
||||
py-august==0.14.0
|
||||
py-august==0.17.0
|
||||
|
||||
# homeassistant.components.canary
|
||||
py-canary==0.5.0
|
||||
|
|
|
@ -1,17 +1,83 @@
|
|||
"""Mocks for the august component."""
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
from asynctest import mock
|
||||
from august.activity import Activity
|
||||
from august.api import Api
|
||||
from august.authenticator import AuthenticationState
|
||||
from august.doorbell import Doorbell, DoorbellDetail
|
||||
from august.exceptions import AugustApiHTTPError
|
||||
from august.lock import Lock, LockDetail
|
||||
from august.lock import Lock, LockDetail, LockStatus
|
||||
|
||||
from homeassistant.components.august import AugustData
|
||||
from homeassistant.components.august import (
|
||||
CONF_LOGIN_METHOD,
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
DOMAIN,
|
||||
AugustData,
|
||||
)
|
||||
from homeassistant.components.august.binary_sensor import AugustDoorBinarySensor
|
||||
from homeassistant.components.august.lock import AugustLock
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt
|
||||
|
||||
from tests.common import load_fixture
|
||||
|
||||
|
||||
def _mock_get_config():
|
||||
"""Return a default august config."""
|
||||
return {
|
||||
DOMAIN: {
|
||||
CONF_LOGIN_METHOD: "email",
|
||||
CONF_USERNAME: "mocked_username",
|
||||
CONF_PASSWORD: "mocked_password",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@mock.patch("homeassistant.components.august.Api")
|
||||
@mock.patch("homeassistant.components.august.Authenticator.authenticate")
|
||||
async def _mock_setup_august(hass, api_mocks_callback, authenticate_mock, api_mock):
|
||||
"""Set up august integration."""
|
||||
authenticate_mock.side_effect = MagicMock(
|
||||
return_value=_mock_august_authentication("original_token", 1234)
|
||||
)
|
||||
api_mocks_callback(api_mock)
|
||||
assert await async_setup_component(hass, DOMAIN, _mock_get_config())
|
||||
await hass.async_block_till_done()
|
||||
return True
|
||||
|
||||
|
||||
async def _create_august_with_devices(hass, lock_details=[], doorbell_details=[]):
|
||||
locks = []
|
||||
doorbells = []
|
||||
for lock in lock_details:
|
||||
if isinstance(lock, LockDetail):
|
||||
locks.append(_mock_august_lock(lock.device_id))
|
||||
for doorbell in doorbell_details:
|
||||
if isinstance(lock, DoorbellDetail):
|
||||
doorbells.append(_mock_august_doorbell(doorbell.device_id))
|
||||
|
||||
def api_mocks_callback(api):
|
||||
def get_lock_detail_side_effect(access_token, device_id):
|
||||
for lock in lock_details:
|
||||
if isinstance(lock, LockDetail) and lock.device_id == device_id:
|
||||
return lock
|
||||
|
||||
api_instance = MagicMock()
|
||||
api_instance.get_lock_detail.side_effect = get_lock_detail_side_effect
|
||||
api_instance.get_operable_locks.return_value = locks
|
||||
api_instance.get_doorbells.return_value = doorbells
|
||||
api_instance.lock.return_value = LockStatus.LOCKED
|
||||
api_instance.unlock.return_value = LockStatus.UNLOCKED
|
||||
api.return_value = api_instance
|
||||
|
||||
await _mock_setup_august(hass, api_mocks_callback)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class MockAugustApiFailing(Api):
|
||||
"""A mock for py-august Api class that always has an AugustApiHTTPError."""
|
||||
|
@ -61,21 +127,6 @@ class MockAugustComponentDoorBinarySensor(AugustDoorBinarySensor):
|
|||
self.last_update_door_state["activity_start_time_utc"] = activity_start_time_utc
|
||||
|
||||
|
||||
class MockAugustComponentLock(AugustLock):
|
||||
"""A mock for august component AugustLock class."""
|
||||
|
||||
def _update_lock_status(self, lock_status, activity_start_time_utc):
|
||||
"""Mock updating the lock status."""
|
||||
self._data.set_last_lock_status_update_time_utc(
|
||||
self._lock.device_id, activity_start_time_utc
|
||||
)
|
||||
self.last_update_lock_status = {}
|
||||
self.last_update_lock_status["lock_status"] = lock_status
|
||||
self.last_update_lock_status[
|
||||
"activity_start_time_utc"
|
||||
] = activity_start_time_utc
|
||||
|
||||
|
||||
class MockAugustComponentData(AugustData):
|
||||
"""A wrapper to mock AugustData."""
|
||||
|
||||
|
@ -143,6 +194,9 @@ def _mock_august_authenticator():
|
|||
|
||||
def _mock_august_authentication(token_text, token_timestamp):
|
||||
authentication = MagicMock(name="august.authentication")
|
||||
type(authentication).state = PropertyMock(
|
||||
return_value=AuthenticationState.AUTHENTICATED
|
||||
)
|
||||
type(authentication).access_token = PropertyMock(return_value=token_text)
|
||||
type(authentication).access_token_expires = PropertyMock(
|
||||
return_value=token_timestamp
|
||||
|
@ -154,6 +208,31 @@ def _mock_august_lock(lockid="mocklockid1", houseid="mockhouseid1"):
|
|||
return Lock(lockid, _mock_august_lock_data(lockid=lockid, houseid=houseid))
|
||||
|
||||
|
||||
def _mock_august_doorbell(deviceid="mockdeviceid1", houseid="mockhouseid1"):
|
||||
return Doorbell(
|
||||
deviceid, _mock_august_doorbell_data(device=deviceid, houseid=houseid)
|
||||
)
|
||||
|
||||
|
||||
def _mock_august_doorbell_data(deviceid="mockdeviceid1", houseid="mockhouseid1"):
|
||||
return {
|
||||
"_id": deviceid,
|
||||
"DeviceID": deviceid,
|
||||
"DeviceName": deviceid + " Name",
|
||||
"HouseID": houseid,
|
||||
"UserType": "owner",
|
||||
"SerialNumber": "mockserial",
|
||||
"battery": 90,
|
||||
"currentFirmwareVersion": "mockfirmware",
|
||||
"Bridge": {
|
||||
"_id": "bridgeid1",
|
||||
"firmwareVersion": "mockfirm",
|
||||
"operative": True,
|
||||
},
|
||||
"LockStatus": {"doorState": "open"},
|
||||
}
|
||||
|
||||
|
||||
def _mock_august_lock_data(lockid="mocklockid1", houseid="mockhouseid1"):
|
||||
return {
|
||||
"_id": lockid,
|
||||
|
@ -189,6 +268,18 @@ def _mock_doorsense_enabled_august_lock_detail(lockid):
|
|||
return LockDetail(doorsense_lock_detail_data)
|
||||
|
||||
|
||||
async def _mock_lock_from_fixture(hass, path):
|
||||
json_dict = await _load_json_fixture(hass, path)
|
||||
return LockDetail(json_dict)
|
||||
|
||||
|
||||
async def _load_json_fixture(hass, path):
|
||||
fixture = await hass.async_add_executor_job(
|
||||
load_fixture, os.path.join("august", path)
|
||||
)
|
||||
return json.loads(fixture)
|
||||
|
||||
|
||||
def _mock_doorsense_missing_august_lock_detail(lockid):
|
||||
doorsense_lock_detail_data = _mock_august_lock_data(lockid=lockid)
|
||||
del doorsense_lock_detail_data["LockStatus"]["doorState"]
|
||||
|
|
|
@ -1,89 +1 @@
|
|||
"""The lock tests for the august platform."""
|
||||
|
||||
import datetime
|
||||
|
||||
from august.activity import ACTION_DOOR_CLOSED, ACTION_DOOR_OPEN
|
||||
from august.lock import LockDoorStatus
|
||||
|
||||
from homeassistant.util import dt
|
||||
|
||||
from tests.components.august.mocks import (
|
||||
MockActivity,
|
||||
MockAugustComponentData,
|
||||
MockAugustComponentDoorBinarySensor,
|
||||
_mock_august_lock,
|
||||
)
|
||||
|
||||
|
||||
def test__sync_door_activity_doored_via_dooropen():
|
||||
"""Test _sync_door_activity dooropen."""
|
||||
data = MockAugustComponentData(last_door_state_update_timestamp=1)
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
door = MockAugustComponentDoorBinarySensor(data, "door_open", lock)
|
||||
door_activity_start_timestamp = 1234
|
||||
door_activity = MockActivity(
|
||||
action=ACTION_DOOR_OPEN,
|
||||
activity_start_timestamp=door_activity_start_timestamp,
|
||||
activity_end_timestamp=5678,
|
||||
)
|
||||
door._sync_door_activity(door_activity)
|
||||
assert door.last_update_door_state["door_state"] == LockDoorStatus.OPEN
|
||||
assert door.last_update_door_state["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(door_activity_start_timestamp)
|
||||
)
|
||||
|
||||
|
||||
def test__sync_door_activity_doorclosed():
|
||||
"""Test _sync_door_activity doorclosed."""
|
||||
data = MockAugustComponentData(last_door_state_update_timestamp=1)
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
door = MockAugustComponentDoorBinarySensor(data, "door_open", lock)
|
||||
door_activity_timestamp = 1234
|
||||
door_activity = MockActivity(
|
||||
action=ACTION_DOOR_CLOSED,
|
||||
activity_start_timestamp=door_activity_timestamp,
|
||||
activity_end_timestamp=door_activity_timestamp,
|
||||
)
|
||||
door._sync_door_activity(door_activity)
|
||||
assert door.last_update_door_state["door_state"] == LockDoorStatus.CLOSED
|
||||
assert door.last_update_door_state["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(door_activity_timestamp)
|
||||
)
|
||||
|
||||
|
||||
def test__sync_door_activity_ignores_old_data():
|
||||
"""Test _sync_door_activity dooropen then expired doorclosed."""
|
||||
data = MockAugustComponentData(last_door_state_update_timestamp=1)
|
||||
lock = _mock_august_lock()
|
||||
data.set_mocked_locks([lock])
|
||||
door = MockAugustComponentDoorBinarySensor(data, "door_open", lock)
|
||||
first_door_activity_timestamp = 1234
|
||||
door_activity = MockActivity(
|
||||
action=ACTION_DOOR_OPEN,
|
||||
activity_start_timestamp=first_door_activity_timestamp,
|
||||
activity_end_timestamp=first_door_activity_timestamp,
|
||||
)
|
||||
door._sync_door_activity(door_activity)
|
||||
assert door.last_update_door_state["door_state"] == LockDoorStatus.OPEN
|
||||
assert door.last_update_door_state["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(first_door_activity_timestamp)
|
||||
)
|
||||
|
||||
# Now we do the update with an older start time to
|
||||
# make sure it ignored
|
||||
data.set_last_door_state_update_time_utc(
|
||||
lock.device_id, dt.as_utc(datetime.datetime.fromtimestamp(1000))
|
||||
)
|
||||
door_activity_timestamp = 2
|
||||
door_activity = MockActivity(
|
||||
action=ACTION_DOOR_CLOSED,
|
||||
activity_start_timestamp=door_activity_timestamp,
|
||||
activity_end_timestamp=door_activity_timestamp,
|
||||
)
|
||||
door._sync_door_activity(door_activity)
|
||||
assert door.last_update_door_state["door_state"] == LockDoorStatus.OPEN
|
||||
assert door.last_update_door_state["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(first_door_activity_timestamp)
|
||||
)
|
||||
"""The binary_sensor tests for the august platform."""
|
||||
|
|
|
@ -128,7 +128,6 @@ def _create_august_data_with_lock_details(lock_details):
|
|||
authenticator = _mock_august_authenticator()
|
||||
token_refresh_lock = MagicMock()
|
||||
api = MagicMock()
|
||||
api.get_lock_status = MagicMock(return_value=(MagicMock(), MagicMock()))
|
||||
api.get_lock_detail = MagicMock(side_effect=lock_details)
|
||||
api.get_operable_locks = MagicMock(return_value=locks)
|
||||
api.get_doorbells = MagicMock(return_value=[])
|
||||
|
|
|
@ -1,110 +1,46 @@
|
|||
"""The lock tests for the august platform."""
|
||||
|
||||
import datetime
|
||||
|
||||
from august.activity import (
|
||||
ACTION_LOCK_LOCK,
|
||||
ACTION_LOCK_ONETOUCHLOCK,
|
||||
ACTION_LOCK_UNLOCK,
|
||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_UNLOCK,
|
||||
STATE_LOCKED,
|
||||
STATE_ON,
|
||||
STATE_UNLOCKED,
|
||||
)
|
||||
from august.lock import LockStatus
|
||||
|
||||
from homeassistant.util import dt
|
||||
|
||||
from tests.components.august.mocks import (
|
||||
MockActivity,
|
||||
MockAugustComponentData,
|
||||
MockAugustComponentLock,
|
||||
_mock_august_lock,
|
||||
_create_august_with_devices,
|
||||
_mock_lock_from_fixture,
|
||||
)
|
||||
|
||||
|
||||
def test__sync_lock_activity_locked_via_onetouchlock():
|
||||
"""Test _sync_lock_activity locking."""
|
||||
lock = _mocked_august_component_lock()
|
||||
lock_activity_start_timestamp = 1234
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_ONETOUCHLOCK,
|
||||
activity_start_timestamp=lock_activity_start_timestamp,
|
||||
activity_end_timestamp=5678,
|
||||
async def test_one_lock_unlock_happy_path(hass):
|
||||
"""Test creation of a lock with doorsense and bridge."""
|
||||
lock_one = await _mock_lock_from_fixture(
|
||||
hass, "get_lock.online_with_doorsense.json"
|
||||
)
|
||||
lock._sync_lock_activity(lock_activity)
|
||||
assert lock.last_update_lock_status["lock_status"] == LockStatus.LOCKED
|
||||
assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(lock_activity_start_timestamp)
|
||||
lock_details = [lock_one]
|
||||
await _create_august_with_devices(hass, lock_details=lock_details)
|
||||
|
||||
lock_abc_name = hass.states.get("lock.abc_name")
|
||||
|
||||
assert lock_abc_name.state == STATE_LOCKED
|
||||
|
||||
assert lock_abc_name.attributes.get("battery_level") == 92
|
||||
assert lock_abc_name.attributes.get("friendly_name") == "ABC Name"
|
||||
|
||||
data = {}
|
||||
data[ATTR_ENTITY_ID] = "lock.abc_name"
|
||||
assert await hass.services.async_call(
|
||||
LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True
|
||||
)
|
||||
|
||||
lock_abc_name = hass.states.get("lock.abc_name")
|
||||
assert lock_abc_name.state == STATE_UNLOCKED
|
||||
|
||||
def test__sync_lock_activity_locked_via_lock():
|
||||
"""Test _sync_lock_activity locking."""
|
||||
lock = _mocked_august_component_lock()
|
||||
lock_activity_start_timestamp = 1234
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_LOCK,
|
||||
activity_start_timestamp=lock_activity_start_timestamp,
|
||||
activity_end_timestamp=5678,
|
||||
)
|
||||
lock._sync_lock_activity(lock_activity)
|
||||
assert lock.last_update_lock_status["lock_status"] == LockStatus.LOCKED
|
||||
assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(lock_activity_start_timestamp)
|
||||
)
|
||||
assert lock_abc_name.attributes.get("battery_level") == 92
|
||||
assert lock_abc_name.attributes.get("friendly_name") == "ABC Name"
|
||||
|
||||
|
||||
def test__sync_lock_activity_unlocked():
|
||||
"""Test _sync_lock_activity unlocking."""
|
||||
lock = _mocked_august_component_lock()
|
||||
lock_activity_timestamp = 1234
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_UNLOCK,
|
||||
activity_start_timestamp=lock_activity_timestamp,
|
||||
activity_end_timestamp=lock_activity_timestamp,
|
||||
)
|
||||
lock._sync_lock_activity(lock_activity)
|
||||
assert lock.last_update_lock_status["lock_status"] == LockStatus.UNLOCKED
|
||||
assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(lock_activity_timestamp)
|
||||
)
|
||||
|
||||
|
||||
def test__sync_lock_activity_ignores_old_data():
|
||||
"""Test _sync_lock_activity unlocking."""
|
||||
data = MockAugustComponentData(last_lock_status_update_timestamp=1)
|
||||
august_lock = _mock_august_lock()
|
||||
data.set_mocked_locks([august_lock])
|
||||
lock = MockAugustComponentLock(data, august_lock)
|
||||
first_lock_activity_timestamp = 1234
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_UNLOCK,
|
||||
activity_start_timestamp=first_lock_activity_timestamp,
|
||||
activity_end_timestamp=first_lock_activity_timestamp,
|
||||
)
|
||||
lock._sync_lock_activity(lock_activity)
|
||||
assert lock.last_update_lock_status["lock_status"] == LockStatus.UNLOCKED
|
||||
assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(first_lock_activity_timestamp)
|
||||
)
|
||||
|
||||
# Now we do the update with an older start time to
|
||||
# make sure it ignored
|
||||
data.set_last_lock_status_update_time_utc(
|
||||
august_lock.device_id, dt.as_utc(datetime.datetime.fromtimestamp(1000))
|
||||
)
|
||||
lock_activity_timestamp = 2
|
||||
lock_activity = MockActivity(
|
||||
action=ACTION_LOCK_LOCK,
|
||||
activity_start_timestamp=lock_activity_timestamp,
|
||||
activity_end_timestamp=lock_activity_timestamp,
|
||||
)
|
||||
lock._sync_lock_activity(lock_activity)
|
||||
assert lock.last_update_lock_status["lock_status"] == LockStatus.UNLOCKED
|
||||
assert lock.last_update_lock_status["activity_start_time_utc"] == dt.as_utc(
|
||||
datetime.datetime.fromtimestamp(first_lock_activity_timestamp)
|
||||
)
|
||||
|
||||
|
||||
def _mocked_august_component_lock():
|
||||
data = MockAugustComponentData(last_lock_status_update_timestamp=1)
|
||||
august_lock = _mock_august_lock()
|
||||
data.set_mocked_locks([august_lock])
|
||||
return MockAugustComponentLock(data, august_lock)
|
||||
binary_sensor_abc_name = hass.states.get("binary_sensor.abc_name_open")
|
||||
assert binary_sensor_abc_name.state == STATE_ON
|
||||
|
|
103
tests/fixtures/august/get_lock.doorsense_init.json
vendored
Normal file
103
tests/fixtures/august/get_lock.doorsense_init.json
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
"LockName": "Front Door Lock",
|
||||
"Type": 2,
|
||||
"Created": "2017-12-10T03:12:09.210Z",
|
||||
"Updated": "2017-12-10T03:12:09.210Z",
|
||||
"LockID": "A6697750D607098BAE8D6BAA11EF8063",
|
||||
"HouseID": "000000000000",
|
||||
"HouseName": "My House",
|
||||
"Calibrated": false,
|
||||
"skuNumber": "AUG-SL02-M02-S02",
|
||||
"timeZone": "America/Vancouver",
|
||||
"battery": 0.88,
|
||||
"SerialNumber": "X2FSW05DGA",
|
||||
"LockStatus": {
|
||||
"status": "locked",
|
||||
"doorState": "init",
|
||||
"dateTime": "2017-12-10T04:48:30.272Z",
|
||||
"isLockStatusChanged": false,
|
||||
"valid": true
|
||||
},
|
||||
"currentFirmwareVersion": "109717e9-3.0.44-3.0.30",
|
||||
"homeKitEnabled": false,
|
||||
"zWaveEnabled": false,
|
||||
"isGalileo": false,
|
||||
"Bridge": {
|
||||
"_id": "aaacab87f7efxa0015884999",
|
||||
"mfgBridgeID": "AAGPP102XX",
|
||||
"deviceModel": "august-doorbell",
|
||||
"firmwareVersion": "2.3.0-RC153+201711151527",
|
||||
"operative": true
|
||||
},
|
||||
"keypad": {
|
||||
"_id": "5bc65c24e6ef2a263e1450a8",
|
||||
"serialNumber": "K1GXB0054Z",
|
||||
"lockID": "92412D1B44004595B5DEB134E151A8D3",
|
||||
"currentFirmwareVersion": "2.27.0",
|
||||
"battery": {},
|
||||
"batteryLevel": "Medium",
|
||||
"batteryRaw": 170
|
||||
},
|
||||
"OfflineKeys": {
|
||||
"created": [],
|
||||
"loaded": [
|
||||
{
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"slot": 1,
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941111",
|
||||
"created": "2017-12-10T03:12:09.215Z",
|
||||
"loaded": "2017-12-10T03:12:54.391Z"
|
||||
}
|
||||
],
|
||||
"deleted": [],
|
||||
"loadedhk": [
|
||||
{
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941222",
|
||||
"slot": 256,
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"created": "2017-12-10T03:12:09.218Z",
|
||||
"loaded": "2017-12-10T03:12:55.563Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parametersToSet": {},
|
||||
"users": {
|
||||
"cccca94e-373e-aaaa-bbbb-333396827777": {
|
||||
"UserType": "superuser",
|
||||
"FirstName": "Foo",
|
||||
"LastName": "Bar",
|
||||
"identifiers": [
|
||||
"email:foo@bar.com",
|
||||
"phone:+177777777777"
|
||||
],
|
||||
"imageInfo": {
|
||||
"original": {
|
||||
"width": 948,
|
||||
"height": 949,
|
||||
"format": "jpg",
|
||||
"url": "http://www.image.com/foo.jpeg",
|
||||
"secure_url": "https://www.image.com/foo.jpeg"
|
||||
},
|
||||
"thumbnail": {
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"format": "jpg",
|
||||
"url": "http://www.image.com/foo.jpeg",
|
||||
"secure_url": "https://www.image.com/foo.jpeg"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pubsubChannel": "3333a674-ffff-aaaa-b351-b3a4473f3333",
|
||||
"ruleHash": {},
|
||||
"cameras": [],
|
||||
"geofenceLimits": {
|
||||
"ios": {
|
||||
"debounceInterval": 90,
|
||||
"gpsAccuracyMultiplier": 2.5,
|
||||
"maximumGeofence": 5000,
|
||||
"minimumGeofence": 100,
|
||||
"minGPSAccuracyRequired": 80
|
||||
}
|
||||
}
|
||||
}
|
68
tests/fixtures/august/get_lock.offline.json
vendored
Normal file
68
tests/fixtures/august/get_lock.offline.json
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"Calibrated" : false,
|
||||
"Created" : "2000-00-00T00:00:00.447Z",
|
||||
"HouseID" : "houseid",
|
||||
"HouseName" : "MockName",
|
||||
"LockID" : "ABC",
|
||||
"LockName" : "Test",
|
||||
"LockStatus" : {
|
||||
"status" : "unknown"
|
||||
},
|
||||
"OfflineKeys" : {
|
||||
"created" : [],
|
||||
"createdhk" : [
|
||||
{
|
||||
"UserID" : "mock-user-id",
|
||||
"created" : "2000-00-00T00:00:00.447Z",
|
||||
"key" : "mockkey",
|
||||
"slot" : 12
|
||||
}
|
||||
],
|
||||
"deleted" : [],
|
||||
"loaded" : [
|
||||
{
|
||||
"UserID" : "userid",
|
||||
"created" : "2000-00-00T00:00:00.447Z",
|
||||
"key" : "key",
|
||||
"loaded" : "2000-00-00T00:00:00.447Z",
|
||||
"slot" : 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"SerialNumber" : "ABC",
|
||||
"Type" : 3,
|
||||
"Updated" : "2000-00-00T00:00:00.447Z",
|
||||
"battery" : -1,
|
||||
"cameras" : [],
|
||||
"currentFirmwareVersion" : "undefined-1.59.0-1.13.2",
|
||||
"geofenceLimits" : {
|
||||
"ios" : {
|
||||
"debounceInterval" : 90,
|
||||
"gpsAccuracyMultiplier" : 2.5,
|
||||
"maximumGeofence" : 5000,
|
||||
"minGPSAccuracyRequired" : 80,
|
||||
"minimumGeofence" : 100
|
||||
}
|
||||
},
|
||||
"homeKitEnabled" : false,
|
||||
"isGalileo" : false,
|
||||
"macAddress" : "a:b:c",
|
||||
"parametersToSet" : {},
|
||||
"pubsubChannel" : "mockpubsub",
|
||||
"ruleHash" : {},
|
||||
"skuNumber" : "AUG-X",
|
||||
"supportsEntryCodes" : false,
|
||||
"users" : {
|
||||
"mockuserid" : {
|
||||
"FirstName" : "MockName",
|
||||
"LastName" : "House",
|
||||
"UserType" : "superuser",
|
||||
"identifiers" : [
|
||||
"phone:+15558675309",
|
||||
"email:mockme@mock.org"
|
||||
]
|
||||
}
|
||||
},
|
||||
"zWaveDSK" : "1-2-3-4",
|
||||
"zWaveEnabled" : true
|
||||
}
|
103
tests/fixtures/august/get_lock.online.json
vendored
Normal file
103
tests/fixtures/august/get_lock.online.json
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
"LockName": "Front Door Lock",
|
||||
"Type": 2,
|
||||
"Created": "2017-12-10T03:12:09.210Z",
|
||||
"Updated": "2017-12-10T03:12:09.210Z",
|
||||
"LockID": "A6697750D607098BAE8D6BAA11EF8063",
|
||||
"HouseID": "000000000000",
|
||||
"HouseName": "My House",
|
||||
"Calibrated": false,
|
||||
"skuNumber": "AUG-SL02-M02-S02",
|
||||
"timeZone": "America/Vancouver",
|
||||
"battery": 0.88,
|
||||
"SerialNumber": "X2FSW05DGA",
|
||||
"LockStatus": {
|
||||
"status": "locked",
|
||||
"doorState": "closed",
|
||||
"dateTime": "2017-12-10T04:48:30.272Z",
|
||||
"isLockStatusChanged": true,
|
||||
"valid": true
|
||||
},
|
||||
"currentFirmwareVersion": "109717e9-3.0.44-3.0.30",
|
||||
"homeKitEnabled": false,
|
||||
"zWaveEnabled": false,
|
||||
"isGalileo": false,
|
||||
"Bridge": {
|
||||
"_id": "aaacab87f7efxa0015884999",
|
||||
"mfgBridgeID": "AAGPP102XX",
|
||||
"deviceModel": "august-doorbell",
|
||||
"firmwareVersion": "2.3.0-RC153+201711151527",
|
||||
"operative": true
|
||||
},
|
||||
"keypad": {
|
||||
"_id": "5bc65c24e6ef2a263e1450a8",
|
||||
"serialNumber": "K1GXB0054Z",
|
||||
"lockID": "92412D1B44004595B5DEB134E151A8D3",
|
||||
"currentFirmwareVersion": "2.27.0",
|
||||
"battery": {},
|
||||
"batteryLevel": "Medium",
|
||||
"batteryRaw": 170
|
||||
},
|
||||
"OfflineKeys": {
|
||||
"created": [],
|
||||
"loaded": [
|
||||
{
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"slot": 1,
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941111",
|
||||
"created": "2017-12-10T03:12:09.215Z",
|
||||
"loaded": "2017-12-10T03:12:54.391Z"
|
||||
}
|
||||
],
|
||||
"deleted": [],
|
||||
"loadedhk": [
|
||||
{
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941222",
|
||||
"slot": 256,
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"created": "2017-12-10T03:12:09.218Z",
|
||||
"loaded": "2017-12-10T03:12:55.563Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parametersToSet": {},
|
||||
"users": {
|
||||
"cccca94e-373e-aaaa-bbbb-333396827777": {
|
||||
"UserType": "superuser",
|
||||
"FirstName": "Foo",
|
||||
"LastName": "Bar",
|
||||
"identifiers": [
|
||||
"email:foo@bar.com",
|
||||
"phone:+177777777777"
|
||||
],
|
||||
"imageInfo": {
|
||||
"original": {
|
||||
"width": 948,
|
||||
"height": 949,
|
||||
"format": "jpg",
|
||||
"url": "http://www.image.com/foo.jpeg",
|
||||
"secure_url": "https://www.image.com/foo.jpeg"
|
||||
},
|
||||
"thumbnail": {
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"format": "jpg",
|
||||
"url": "http://www.image.com/foo.jpeg",
|
||||
"secure_url": "https://www.image.com/foo.jpeg"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pubsubChannel": "3333a674-ffff-aaaa-b351-b3a4473f3333",
|
||||
"ruleHash": {},
|
||||
"cameras": [],
|
||||
"geofenceLimits": {
|
||||
"ios": {
|
||||
"debounceInterval": 90,
|
||||
"gpsAccuracyMultiplier": 2.5,
|
||||
"maximumGeofence": 5000,
|
||||
"minimumGeofence": 100,
|
||||
"minGPSAccuracyRequired": 80
|
||||
}
|
||||
}
|
||||
}
|
51
tests/fixtures/august/get_lock.online_with_doorsense.json
vendored
Normal file
51
tests/fixtures/august/get_lock.online_with_doorsense.json
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"Bridge" : {
|
||||
"_id" : "bridgeid",
|
||||
"deviceModel" : "august-connect",
|
||||
"firmwareVersion" : "2.2.1",
|
||||
"hyperBridge" : true,
|
||||
"mfgBridgeID" : "C5WY200WSH",
|
||||
"operative" : true,
|
||||
"status" : {
|
||||
"current" : "online",
|
||||
"lastOffline" : "2000-00-00T00:00:00.447Z",
|
||||
"lastOnline" : "2000-00-00T00:00:00.447Z",
|
||||
"updated" : "2000-00-00T00:00:00.447Z"
|
||||
}
|
||||
},
|
||||
"Calibrated" : false,
|
||||
"Created" : "2000-00-00T00:00:00.447Z",
|
||||
"HouseID" : "123",
|
||||
"HouseName" : "Test",
|
||||
"LockID" : "ABC",
|
||||
"LockName" : "Online door with doorsense",
|
||||
"LockStatus" : {
|
||||
"dateTime" : "2017-12-10T04:48:30.272Z",
|
||||
"doorState" : "open",
|
||||
"isLockStatusChanged" : false,
|
||||
"status" : "locked",
|
||||
"valid" : true
|
||||
},
|
||||
"SerialNumber" : "XY",
|
||||
"Type" : 1001,
|
||||
"Updated" : "2000-00-00T00:00:00.447Z",
|
||||
"battery" : 0.922,
|
||||
"currentFirmwareVersion" : "undefined-4.3.0-1.8.14",
|
||||
"homeKitEnabled" : true,
|
||||
"hostLockInfo" : {
|
||||
"manufacturer" : "yale",
|
||||
"productID" : 1536,
|
||||
"productTypeID" : 32770,
|
||||
"serialNumber" : "ABC"
|
||||
},
|
||||
"isGalileo" : false,
|
||||
"macAddress" : "12:22",
|
||||
"pins" : {
|
||||
"created" : [],
|
||||
"loaded" : []
|
||||
},
|
||||
"skuNumber" : "AUG-MD01",
|
||||
"supportsEntryCodes" : true,
|
||||
"timeZone" : "Pacific/Hawaii",
|
||||
"zWaveEnabled" : false
|
||||
}
|
16
tests/fixtures/august/get_locks.json
vendored
Normal file
16
tests/fixtures/august/get_locks.json
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"A6697750D607098BAE8D6BAA11EF8063": {
|
||||
"LockName": "Front Door Lock",
|
||||
"UserType": "superuser",
|
||||
"macAddress": "2E:BA:C4:14:3F:09",
|
||||
"HouseID": "000000000000",
|
||||
"HouseName": "A House"
|
||||
},
|
||||
"A6697750D607098BAE8D6BAA11EF9999": {
|
||||
"LockName": "Back Door Lock",
|
||||
"UserType": "user",
|
||||
"macAddress": "2E:BA:C4:14:3F:88",
|
||||
"HouseID": "000000000011",
|
||||
"HouseName": "A House"
|
||||
}
|
||||
}
|
26
tests/fixtures/august/lock_open.json
vendored
Normal file
26
tests/fixtures/august/lock_open.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"status" : "kAugLockState_Locked",
|
||||
"resultsFromOperationCache" : false,
|
||||
"retryCount" : 1,
|
||||
"info" : {
|
||||
"wlanRSSI" : -54,
|
||||
"lockType" : "lock_version_1001",
|
||||
"lockStatusChanged" : false,
|
||||
"serialNumber" : "ABC",
|
||||
"serial" : "123",
|
||||
"action" : "lock",
|
||||
"context" : {
|
||||
"startDate" : "2020-02-19T01:59:39.516Z",
|
||||
"retryCount" : 1,
|
||||
"transactionID" : "mock"
|
||||
},
|
||||
"bridgeID" : "mock",
|
||||
"wlanSNR" : 41,
|
||||
"startTime" : "2020-02-19T01:59:39.517Z",
|
||||
"duration" : 5149,
|
||||
"lockID" : "ABC",
|
||||
"rssi" : -77
|
||||
},
|
||||
"totalTime" : 5162,
|
||||
"doorState" : "kAugDoorState_Open"
|
||||
}
|
26
tests/fixtures/august/unlock_closed.json
vendored
Normal file
26
tests/fixtures/august/unlock_closed.json
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"status" : "kAugLockState_Unlocked",
|
||||
"resultsFromOperationCache" : false,
|
||||
"retryCount" : 1,
|
||||
"info" : {
|
||||
"wlanRSSI" : -54,
|
||||
"lockType" : "lock_version_1001",
|
||||
"lockStatusChanged" : false,
|
||||
"serialNumber" : "ABC",
|
||||
"serial" : "123",
|
||||
"action" : "lock",
|
||||
"context" : {
|
||||
"startDate" : "2020-02-19T01:59:39.516Z",
|
||||
"retryCount" : 1,
|
||||
"transactionID" : "mock"
|
||||
},
|
||||
"bridgeID" : "mock",
|
||||
"wlanSNR" : 41,
|
||||
"startTime" : "2020-02-19T01:59:39.517Z",
|
||||
"duration" : 5149,
|
||||
"lockID" : "ABC",
|
||||
"rssi" : -77
|
||||
},
|
||||
"totalTime" : 5162,
|
||||
"doorState" : "kAugDoorState_Closed"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue