From 693441e56f81345d71a0341b5c354ef162c19943 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 23 Feb 2020 11:54:35 -1000 Subject: [PATCH] Deduplicate code in the august integration (#32101) * Deduplicate code in the august integration * Add additional tests for august (more coming) * Door state is now updated when a lock or unlock call returns as the state is contained in the response which avoids the confusing out of sync state * revert * document known issue with doorsense and lock getting out of sync (pre-existing) * Address review comments * Additional review comments --- homeassistant/components/august/__init__.py | 118 ++++------- homeassistant/components/august/lock.py | 38 ++-- tests/components/august/mocks.py | 184 +++++++++++++----- tests/components/august/test_binary_sensor.py | 70 +++++++ tests/components/august/test_camera.py | 18 ++ tests/components/august/test_lock.py | 14 +- tests/fixtures/august/get_doorbell.json | 83 ++++++++ 7 files changed, 379 insertions(+), 146 deletions(-) create mode 100644 tests/components/august/test_camera.py create mode 100644 tests/fixtures/august/get_doorbell.json diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index f49cae7b107..95206b5bee1 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -40,18 +40,20 @@ DATA_AUGUST = "august" DOMAIN = "august" DEFAULT_ENTITY_NAMESPACE = "august" -# Limit battery and hardware updates to 1800 seconds +# Limit battery, online, 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) # 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) +# since we get an image from the doorbell api. Once +# py-august 0.18.0 is released doorbell status updates +# can be reduced in the same was as locks have been +MIN_TIME_BETWEEN_DOORBELL_DETAIL_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) +MIN_TIME_BETWEEN_ACTIVITY_UPDATES = timedelta(seconds=10) DEFAULT_SCAN_INTERVAL = timedelta(seconds=10) @@ -265,7 +267,7 @@ class AugustData: activities = await self.async_get_device_activities(device_id, *activity_types) return next(iter(activities or []), None) - @Throttle(MIN_TIME_BETWEEN_UPDATES) + @Throttle(MIN_TIME_BETWEEN_ACTIVITY_UPDATES) async def _async_update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT): """Update data object with latest from August API.""" @@ -292,77 +294,32 @@ class AugustData: _LOGGER.debug("Completed retrieving device activities") - async def async_get_doorbell_detail(self, doorbell_id): + async def async_get_doorbell_detail(self, device_id): """Return doorbell detail.""" - await self._async_update_doorbells() - return self._doorbell_detail_by_id.get(doorbell_id) + await self._async_update_doorbells_detail() + return self._doorbell_detail_by_id.get(device_id) - @Throttle(MIN_TIME_BETWEEN_DOORBELL_STATUS_UPDATES) - async def _async_update_doorbells(self): - await self._hass.async_add_executor_job(self._update_doorbells) + @Throttle(MIN_TIME_BETWEEN_DOORBELL_DETAIL_UPDATES) + async def _async_update_doorbells_detail(self): + await self._hass.async_add_executor_job(self._update_doorbells_detail) - def _update_doorbells(self): - detail_by_id = {} + def _update_doorbells_detail(self): + self._doorbell_detail_by_id = self._update_device_detail( + "doorbell", self._doorbells, self._api.get_doorbell_detail + ) - _LOGGER.debug("Start retrieving doorbell details") - for doorbell in self._doorbells: - _LOGGER.debug("Updating doorbell status for %s", doorbell.device_name) - try: - detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail( - self._access_token, doorbell.device_id - ) - except RequestException as ex: - _LOGGER.error( - "Request error trying to retrieve doorbell status for %s. %s", - doorbell.device_name, - ex, - ) - detail_by_id[doorbell.device_id] = None - except Exception: - detail_by_id[doorbell.device_id] = None - raise - - _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 - """ - # 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): - """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_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): + def lock_has_doorsense(self, device_id): """Determine if a lock has doorsense installed and can tell when the door is open or closed.""" # We do not update here since this is not expected # to change until restart - if self._lock_detail_by_id[lock_id] is None: + if self._lock_detail_by_id[device_id] is None: return False - return self._lock_detail_by_id[lock_id].doorsense + return self._lock_detail_by_id[device_id].doorsense - async def async_get_lock_detail(self, lock_id): + async def async_get_lock_detail(self, device_id): """Return lock detail.""" await self._async_update_locks_detail() - return self._lock_detail_by_id[lock_id] + return self._lock_detail_by_id[device_id] def get_lock_name(self, device_id): """Return lock name as August has it stored.""" @@ -375,34 +332,39 @@ class AugustData: await self._hass.async_add_executor_job(self._update_locks_detail) def _update_locks_detail(self): + self._lock_detail_by_id = self._update_device_detail( + "lock", self._locks, self._api.get_lock_detail + ) + + def _update_device_detail(self, device_type, devices, api_call): detail_by_id = {} - _LOGGER.debug("Start retrieving locks detail") - for lock in self._locks: + _LOGGER.debug("Start retrieving %s detail", device_type) + for device in devices: + device_id = device.device_id try: - detail_by_id[lock.device_id] = self._api.get_lock_detail( - self._access_token, lock.device_id - ) + detail_by_id[device_id] = api_call(self._access_token, device_id) except RequestException as ex: _LOGGER.error( - "Request error trying to retrieve door details for %s. %s", - lock.device_name, + "Request error trying to retrieve %s details for %s. %s", + device_type, + device.device_name, ex, ) - detail_by_id[lock.device_id] = None + detail_by_id[device_id] = None except Exception: - detail_by_id[lock.device_id] = None + detail_by_id[device_id] = None raise - _LOGGER.debug("Completed retrieving locks detail") - self._lock_detail_by_id = detail_by_id + _LOGGER.debug("Completed retrieving %s detail", device_type) + return detail_by_id def lock(self, device_id): """Lock the device.""" return _call_api_operation_that_requires_bridge( self.get_lock_name(device_id), "lock", - self._api.lock, + self._api.lock_return_activities, self._access_token, device_id, ) @@ -412,7 +374,7 @@ class AugustData: return _call_api_operation_that_requires_bridge( self.get_lock_name(device_id), "unlock", - self._api.unlock, + self._api.unlock_return_activities, self._access_token, device_id, ) diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 0097b6029a0..a805fa2657a 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -8,7 +8,6 @@ from august.util import update_lock_detail_from_activity from homeassistant.components.lock import LockDevice from homeassistant.const import ATTR_BATTERY_LEVEL -from homeassistant.util import dt from . import DATA_AUGUST @@ -43,27 +42,31 @@ class AugustLock(LockDevice): async def async_lock(self, **kwargs): """Lock the device.""" - update_start_time_utc = dt.utcnow() - lock_status = await self.hass.async_add_executor_job( - self._data.lock, self._lock.device_id - ) - self._update_lock_status(lock_status, update_start_time_utc) + await self._call_lock_operation(self._data.lock) async def async_unlock(self, **kwargs): """Unlock the device.""" - update_start_time_utc = dt.utcnow() - lock_status = await self.hass.async_add_executor_job( - self._data.unlock, self._lock.device_id - ) - self._update_lock_status(lock_status, update_start_time_utc) + await self._call_lock_operation(self._data.unlock) - def _update_lock_status(self, lock_status, update_start_time_utc): + async def _call_lock_operation(self, lock_operation): + activities = await self.hass.async_add_executor_job( + lock_operation, self._lock.device_id + ) + for lock_activity in activities: + update_lock_detail_from_activity(self._lock_detail, lock_activity) + + if self._update_lock_status_from_detail(): + self.schedule_update_ha_state() + + def _update_lock_status_from_detail(self): + lock_status = self._lock_detail.lock_status 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._available = ( + lock_status is not None and lock_status != LockStatus.UNKNOWN ) - self.schedule_update_ha_state() + return True + return False async def async_update(self): """Get the latest state of the sensor and update activity.""" @@ -76,10 +79,7 @@ class AugustLock(LockDevice): self._changed_by = lock_activity.operated_by update_lock_detail_from_activity(self._lock_detail, lock_activity) - self._lock_status = self._lock_detail.lock_status - self._available = ( - self._lock_status is not None and self._lock_status != LockStatus.UNKNOWN - ) + self._update_lock_status_from_detail() @property def name(self): diff --git a/tests/components/august/mocks.py b/tests/components/august/mocks.py index 30269bec11e..d1c3efb00e8 100644 --- a/tests/components/august/mocks.py +++ b/tests/components/august/mocks.py @@ -2,15 +2,16 @@ import datetime import json import os +import time from unittest.mock import MagicMock, PropertyMock from asynctest import mock -from august.activity import Activity +from august.activity import Activity, DoorOperationActivity, LockOperationActivity 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, LockStatus +from august.lock import Lock, LockDetail from homeassistant.components.august import ( CONF_LOGIN_METHOD, @@ -19,7 +20,6 @@ from homeassistant.components.august import ( DOMAIN, AugustData, ) -from homeassistant.components.august.binary_sensor import AugustDoorBinarySensor from homeassistant.setup import async_setup_component from homeassistant.util import dt @@ -39,44 +39,125 @@ def _mock_get_config(): @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): +async def _mock_setup_august(hass, api_instance, 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) + api_mock.return_value = api_instance 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)) +async def _create_august_with_devices(hass, devices, api_call_side_effects=None): + if api_call_side_effects is None: + api_call_side_effects = {} + device_data = { + "doorbells": [], + "locks": [], + } + for device in devices: + if isinstance(device, LockDetail): + device_data["locks"].append( + {"base": _mock_august_lock(device.device_id), "detail": device} + ) + elif isinstance(device, DoorbellDetail): + device_data["doorbells"].append( + {"base": _mock_august_doorbell(device.device_id), "detail": device} + ) + else: + raise ValueError - 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 + def _get_device_detail(device_type, device_id): + for device in device_data[device_type]: + if device["detail"].device_id == device_id: + return device["detail"] + raise ValueError - 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 + def _get_base_devices(device_type): + base_devices = [] + for device in device_data[device_type]: + base_devices.append(device["base"]) + return base_devices - await _mock_setup_august(hass, api_mocks_callback) + def get_lock_detail_side_effect(access_token, device_id): + return _get_device_detail("locks", device_id) - return True + def get_operable_locks_side_effect(access_token): + return _get_base_devices("locks") + + def get_doorbells_side_effect(access_token): + return _get_base_devices("doorbells") + + def get_house_activities_side_effect(access_token, house_id, limit=10): + return [] + + def lock_return_activities_side_effect(access_token, device_id): + lock = _get_device_detail("locks", device_id) + return [ + _mock_lock_operation_activity(lock, "lock"), + _mock_door_operation_activity(lock, "doorclosed"), + ] + + def unlock_return_activities_side_effect(access_token, device_id): + lock = _get_device_detail("locks", device_id) + return [ + _mock_lock_operation_activity(lock, "unlock"), + _mock_door_operation_activity(lock, "dooropen"), + ] + + if "get_lock_detail" not in api_call_side_effects: + api_call_side_effects["get_lock_detail"] = get_lock_detail_side_effect + if "get_operable_locks" not in api_call_side_effects: + api_call_side_effects["get_operable_locks"] = get_operable_locks_side_effect + if "get_doorbells" not in api_call_side_effects: + api_call_side_effects["get_doorbells"] = get_doorbells_side_effect + if "get_house_activities" not in api_call_side_effects: + api_call_side_effects["get_house_activities"] = get_house_activities_side_effect + if "lock_return_activities" not in api_call_side_effects: + api_call_side_effects[ + "lock_return_activities" + ] = lock_return_activities_side_effect + if "unlock_return_activities" not in api_call_side_effects: + api_call_side_effects[ + "unlock_return_activities" + ] = unlock_return_activities_side_effect + + return await _mock_setup_august_with_api_side_effects(hass, api_call_side_effects) + + +async def _mock_setup_august_with_api_side_effects(hass, api_call_side_effects): + api_instance = MagicMock(name="Api") + + if api_call_side_effects["get_lock_detail"]: + api_instance.get_lock_detail.side_effect = api_call_side_effects[ + "get_lock_detail" + ] + + if api_call_side_effects["get_operable_locks"]: + api_instance.get_operable_locks.side_effect = api_call_side_effects[ + "get_operable_locks" + ] + + if api_call_side_effects["get_doorbells"]: + api_instance.get_doorbells.side_effect = api_call_side_effects["get_doorbells"] + + if api_call_side_effects["get_house_activities"]: + api_instance.get_house_activities.side_effect = api_call_side_effects[ + "get_house_activities" + ] + + if api_call_side_effects["lock_return_activities"]: + api_instance.lock_return_activities.side_effect = api_call_side_effects[ + "lock_return_activities" + ] + + if api_call_side_effects["unlock_return_activities"]: + api_instance.unlock_return_activities.side_effect = api_call_side_effects[ + "unlock_return_activities" + ] + return await _mock_setup_august(hass, api_instance) class MockAugustApiFailing(Api): @@ -114,19 +195,6 @@ class MockActivity(Activity): return self._action -class MockAugustComponentDoorBinarySensor(AugustDoorBinarySensor): - """A mock for august component AugustDoorBinarySensor class.""" - - def _update_door_state(self, door_state, activity_start_time_utc): - """Mock updating the lock status.""" - self._data.set_last_door_state_update_time_utc( - self._door.device_id, activity_start_time_utc - ) - self.last_update_door_state = {} - self.last_update_door_state["door_state"] = door_state - self.last_update_door_state["activity_start_time_utc"] = activity_start_time_utc - - class MockAugustComponentData(AugustData): """A wrapper to mock AugustData.""" @@ -210,7 +278,7 @@ def _mock_august_lock(lockid="mocklockid1", houseid="mockhouseid1"): def _mock_august_doorbell(deviceid="mockdeviceid1", houseid="mockhouseid1"): return Doorbell( - deviceid, _mock_august_doorbell_data(device=deviceid, houseid=houseid) + deviceid, _mock_august_doorbell_data(deviceid=deviceid, houseid=houseid) ) @@ -218,11 +286,12 @@ def _mock_august_doorbell_data(deviceid="mockdeviceid1", houseid="mockhouseid1") return { "_id": deviceid, "DeviceID": deviceid, - "DeviceName": deviceid + " Name", + "name": deviceid + " Name", "HouseID": houseid, "UserType": "owner", - "SerialNumber": "mockserial", + "serialNumber": "mockserial", "battery": 90, + "status": "standby", "currentFirmwareVersion": "mockfirmware", "Bridge": { "_id": "bridgeid1", @@ -273,6 +342,11 @@ async def _mock_lock_from_fixture(hass, path): return LockDetail(json_dict) +async def _mock_doorbell_from_fixture(hass, path): + json_dict = await _load_json_fixture(hass, path) + return DoorbellDetail(json_dict) + + async def _load_json_fixture(hass, path): fixture = await hass.async_add_executor_job( load_fixture, os.path.join("august", path) @@ -284,3 +358,25 @@ 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"] return LockDetail(doorsense_lock_detail_data) + + +def _mock_lock_operation_activity(lock, action): + return LockOperationActivity( + { + "dateTime": time.time() * 1000, + "deviceID": lock.device_id, + "deviceType": "lock", + "action": action, + } + ) + + +def _mock_door_operation_activity(lock, action): + return DoorOperationActivity( + { + "dateTime": time.time() * 1000, + "deviceID": lock.device_id, + "deviceType": "lock", + "action": action, + } + ) diff --git a/tests/components/august/test_binary_sensor.py b/tests/components/august/test_binary_sensor.py index 5988e21ebac..47acfb59c72 100644 --- a/tests/components/august/test_binary_sensor.py +++ b/tests/components/august/test_binary_sensor.py @@ -1 +1,71 @@ """The binary_sensor tests for the august platform.""" + +import pytest + +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN +from homeassistant.const import ( + ATTR_ENTITY_ID, + SERVICE_LOCK, + SERVICE_UNLOCK, + STATE_OFF, + STATE_ON, +) + +from tests.components.august.mocks import ( + _create_august_with_devices, + _mock_doorbell_from_fixture, + _mock_lock_from_fixture, +) + + +@pytest.mark.skip( + reason="The lock and doorsense can get out of sync due to update intervals, " + + "this is an existing bug which will be fixed with dispatcher events to tell " + + "all linked devices to update." +) +async def test_doorsense(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_details = [lock_one] + await _create_august_with_devices(hass, lock_details) + + binary_sensor_abc_name = hass.states.get("binary_sensor.abc_name_open") + assert binary_sensor_abc_name.state == STATE_ON + + data = {} + data[ATTR_ENTITY_ID] = "lock.abc_name" + assert await hass.services.async_call( + LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True + ) + + binary_sensor_abc_name = hass.states.get("binary_sensor.abc_name_open") + assert binary_sensor_abc_name.state == STATE_ON + + assert await hass.services.async_call( + LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True + ) + + binary_sensor_abc_name = hass.states.get("binary_sensor.abc_name_open") + assert binary_sensor_abc_name.state == STATE_OFF + + +async def test_create_doorbell(hass): + """Test creation of a doorbell.""" + doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") + doorbell_details = [doorbell_one] + await _create_august_with_devices(hass, doorbell_details) + + binary_sensor_k98gidt45gul_name_motion = hass.states.get( + "binary_sensor.k98gidt45gul_name_motion" + ) + assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF + binary_sensor_k98gidt45gul_name_online = hass.states.get( + "binary_sensor.k98gidt45gul_name_online" + ) + assert binary_sensor_k98gidt45gul_name_online.state == STATE_ON + binary_sensor_k98gidt45gul_name_ding = hass.states.get( + "binary_sensor.k98gidt45gul_name_ding" + ) + assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF diff --git a/tests/components/august/test_camera.py b/tests/components/august/test_camera.py new file mode 100644 index 00000000000..9ed97ecbc29 --- /dev/null +++ b/tests/components/august/test_camera.py @@ -0,0 +1,18 @@ +"""The camera tests for the august platform.""" + +from homeassistant.const import STATE_IDLE + +from tests.components.august.mocks import ( + _create_august_with_devices, + _mock_doorbell_from_fixture, +) + + +async def test_create_doorbell(hass): + """Test creation of a doorbell.""" + doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json") + doorbell_details = [doorbell_one] + await _create_august_with_devices(hass, doorbell_details) + + camera_k98gidt45gul_name = hass.states.get("camera.k98gidt45gul_name") + assert camera_k98gidt45gul_name.state == STATE_IDLE diff --git a/tests/components/august/test_lock.py b/tests/components/august/test_lock.py index 518cf22b5ba..b0b298690a5 100644 --- a/tests/components/august/test_lock.py +++ b/tests/components/august/test_lock.py @@ -3,9 +3,9 @@ from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, + SERVICE_LOCK, SERVICE_UNLOCK, STATE_LOCKED, - STATE_ON, STATE_UNLOCKED, ) @@ -15,13 +15,13 @@ from tests.components.august.mocks import ( ) -async def test_one_lock_unlock_happy_path(hass): +async def test_one_lock_operation(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_details = [lock_one] - await _create_august_with_devices(hass, lock_details=lock_details) + await _create_august_with_devices(hass, lock_details) lock_abc_name = hass.states.get("lock.abc_name") @@ -42,5 +42,9 @@ async def test_one_lock_unlock_happy_path(hass): assert lock_abc_name.attributes.get("battery_level") == 92 assert lock_abc_name.attributes.get("friendly_name") == "ABC Name" - binary_sensor_abc_name = hass.states.get("binary_sensor.abc_name_open") - assert binary_sensor_abc_name.state == STATE_ON + assert await hass.services.async_call( + LOCK_DOMAIN, SERVICE_LOCK, data, blocking=True + ) + + lock_abc_name = hass.states.get("lock.abc_name") + assert lock_abc_name.state == STATE_LOCKED diff --git a/tests/fixtures/august/get_doorbell.json b/tests/fixtures/august/get_doorbell.json new file mode 100644 index 00000000000..abe6e37b1e3 --- /dev/null +++ b/tests/fixtures/august/get_doorbell.json @@ -0,0 +1,83 @@ +{ + "status_timestamp" : 1512811834532, + "appID" : "august-iphone", + "LockID" : "BBBB1F5F11114C24CCCC97571DD6AAAA", + "recentImage" : { + "original_filename" : "file", + "placeholder" : false, + "bytes" : 24476, + "height" : 640, + "format" : "jpg", + "width" : 480, + "version" : 1512892814, + "resource_type" : "image", + "etag" : "54966926be2e93f77d498a55f247661f", + "tags" : [], + "public_id" : "qqqqt4ctmxwsysylaaaa", + "url" : "http://image.com/vmk16naaaa7ibuey7sar.jpg", + "created_at" : "2017-12-10T08:01:35Z", + "signature" : "75z47ca21b5e8ffda21d2134e478a2307c4625da", + "secure_url" : "https://image.com/vmk16naaaa7ibuey7sar.jpg", + "type" : "upload" + }, + "settings" : { + "keepEncoderRunning" : true, + "videoResolution" : "640x480", + "minACNoScaling" : 40, + "irConfiguration" : 8448272, + "directLink" : true, + "overlayEnabled" : true, + "notify_when_offline" : true, + "micVolume" : 100, + "bitrateCeiling" : 512000, + "initialBitrate" : 384000, + "IVAEnabled" : false, + "turnOffCamera" : false, + "ringSoundEnabled" : true, + "JPGQuality" : 70, + "motion_notifications" : true, + "speakerVolume" : 92, + "buttonpush_notifications" : true, + "ABREnabled" : true, + "debug" : false, + "batteryLowThreshold" : 3.1, + "batteryRun" : false, + "IREnabled" : true, + "batteryUseThreshold" : 3.4 + }, + "doorbellServerURL" : "https://doorbells.august.com", + "name" : "Front Door", + "createdAt" : "2016-11-26T22:27:11.176Z", + "installDate" : "2016-11-26T22:27:11.176Z", + "serialNumber" : "tBXZR0Z35E", + "dvrSubscriptionSetupDone" : true, + "caps" : [ + "reconnect" + ], + "doorbellID" : "K98GiDT45GUL", + "HouseID" : "3dd2accaea08", + "telemetry" : { + "signal_level" : -56, + "date" : "2017-12-10 08:05:12", + "battery_soc" : 96, + "battery" : 4.061763, + "steady_ac_in" : 22.196405, + "BSSID" : "88:ee:00:dd:aa:11", + "SSID" : "foo_ssid", + "updated_at" : "2017-12-10T08:05:13.650Z", + "temperature" : 28.25, + "wifi_freq" : 5745, + "load_average" : "0.50 0.47 0.35 1/154 9345", + "link_quality" : 54, + "battery_soh" : 95, + "uptime" : "16168.75 13830.49", + "ip_addr" : "10.0.1.11", + "doorbell_low_battery" : false, + "ac_in" : 23.856874 + }, + "installUserID" : "c3b2a94e-373e-aaaa-bbbb-36e996827777", + "status" : "doorbell_call_status_online", + "firmwareVersion" : "2.3.0-RC153+201711151527", + "pubsubChannel" : "7c7a6672-59c8-3333-ffff-dcd98705cccc", + "updatedAt" : "2017-12-10T08:05:13.650Z" +}