Reduce August doorbell detail updates (#32193)
* Reduce August doorbell detail updates * Doorbell images now get updates from the activity feed * Tests for activity updates * py-august now provides bridge_is_online for available state * py-august now provides is_standby for available state * py-august now provides get_doorbell_image (eliminate requests) * remove debug * black after merge conflictpull/32205/head
parent
b5c1afcb84
commit
638a3025df
|
@ -30,8 +30,7 @@ from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
LOGIN_METHODS,
|
LOGIN_METHODS,
|
||||||
MIN_TIME_BETWEEN_ACTIVITY_UPDATES,
|
MIN_TIME_BETWEEN_ACTIVITY_UPDATES,
|
||||||
MIN_TIME_BETWEEN_DOORBELL_DETAIL_UPDATES,
|
MIN_TIME_BETWEEN_DETAIL_UPDATES,
|
||||||
MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES,
|
|
||||||
VERIFICATION_CODE_KEY,
|
VERIFICATION_CODE_KEY,
|
||||||
)
|
)
|
||||||
from .exceptions import InvalidAuth, RequireValidation
|
from .exceptions import InvalidAuth, RequireValidation
|
||||||
|
@ -296,7 +295,7 @@ class AugustData:
|
||||||
await self._async_update_doorbells_detail()
|
await self._async_update_doorbells_detail()
|
||||||
return self._doorbell_detail_by_id.get(device_id)
|
return self._doorbell_detail_by_id.get(device_id)
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_DOORBELL_DETAIL_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_DETAIL_UPDATES)
|
||||||
async def _async_update_doorbells_detail(self):
|
async def _async_update_doorbells_detail(self):
|
||||||
await self._hass.async_add_executor_job(self._update_doorbells_detail)
|
await self._hass.async_add_executor_job(self._update_doorbells_detail)
|
||||||
|
|
||||||
|
@ -324,7 +323,7 @@ class AugustData:
|
||||||
if lock.device_id == device_id:
|
if lock.device_id == device_id:
|
||||||
return lock.device_name
|
return lock.device_name
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES)
|
@Throttle(MIN_TIME_BETWEEN_DETAIL_UPDATES)
|
||||||
async def _async_update_locks_detail(self):
|
async def _async_update_locks_detail(self):
|
||||||
await self._hass.async_add_executor_job(self._update_locks_detail)
|
await self._hass.async_add_executor_job(self._update_locks_detail)
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ SCAN_INTERVAL = timedelta(seconds=5)
|
||||||
|
|
||||||
async def _async_retrieve_online_state(data, detail):
|
async def _async_retrieve_online_state(data, detail):
|
||||||
"""Get the latest state of the sensor."""
|
"""Get the latest state of the sensor."""
|
||||||
return detail.is_online or detail.status == "standby"
|
return detail.is_online or detail.is_standby
|
||||||
|
|
||||||
|
|
||||||
async def _async_retrieve_motion_state(data, detail):
|
async def _async_retrieve_motion_state(data, detail):
|
||||||
|
@ -137,12 +137,13 @@ class AugustDoorBinarySensor(BinarySensorDevice):
|
||||||
update_lock_detail_from_activity(detail, door_activity)
|
update_lock_detail_from_activity(detail, door_activity)
|
||||||
|
|
||||||
lock_door_state = None
|
lock_door_state = None
|
||||||
|
self._available = False
|
||||||
if detail is not None:
|
if detail is not None:
|
||||||
lock_door_state = detail.door_state
|
lock_door_state = detail.door_state
|
||||||
|
self._available = detail.bridge_is_online
|
||||||
self._firmware_version = detail.firmware_version
|
self._firmware_version = detail.firmware_version
|
||||||
self._model = detail.model
|
self._model = detail.model
|
||||||
|
|
||||||
self._available = lock_door_state != LockDoorStatus.UNKNOWN
|
|
||||||
self._state = lock_door_state == LockDoorStatus.OPEN
|
self._state = lock_door_state == LockDoorStatus.OPEN
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -208,7 +209,7 @@ class AugustDoorbellBinarySensor(BinarySensorDevice):
|
||||||
self._available = True
|
self._available = True
|
||||||
else:
|
else:
|
||||||
self._available = detail is not None and (
|
self._available = detail is not None and (
|
||||||
detail.is_online or detail.status == "standby"
|
detail.is_online or detail.is_standby
|
||||||
)
|
)
|
||||||
|
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
"""Support for August camera."""
|
"""Support for August camera."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import requests
|
from august.activity import ActivityType
|
||||||
|
from august.util import update_doorbell_image_from_activity
|
||||||
|
|
||||||
from homeassistant.components.camera import Camera
|
from homeassistant.components.camera import Camera
|
||||||
|
|
||||||
|
@ -66,6 +67,15 @@ class AugustCamera(Camera):
|
||||||
self._doorbell_detail = await self._data.async_get_doorbell_detail(
|
self._doorbell_detail = await self._data.async_get_doorbell_detail(
|
||||||
self._doorbell.device_id
|
self._doorbell.device_id
|
||||||
)
|
)
|
||||||
|
doorbell_activity = await self._data.async_get_latest_device_activity(
|
||||||
|
self._doorbell.device_id, ActivityType.DOORBELL_MOTION
|
||||||
|
)
|
||||||
|
|
||||||
|
if doorbell_activity is not None:
|
||||||
|
update_doorbell_image_from_activity(
|
||||||
|
self._doorbell_detail, doorbell_activity
|
||||||
|
)
|
||||||
|
|
||||||
if self._doorbell_detail is None:
|
if self._doorbell_detail is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -89,9 +99,8 @@ class AugustCamera(Camera):
|
||||||
self._model = self._doorbell_detail.model
|
self._model = self._doorbell_detail.model
|
||||||
|
|
||||||
def _camera_image(self):
|
def _camera_image(self):
|
||||||
"""Return bytes of camera image via http get."""
|
"""Return bytes of camera image."""
|
||||||
# Move this to py-august: see issue#32048
|
return self._doorbell_detail.get_doorbell_image(timeout=self._timeout)
|
||||||
return requests.get(self._image_url, timeout=self._timeout).content
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
|
|
|
@ -23,13 +23,7 @@ DOMAIN = "august"
|
||||||
# Limit battery, online, 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
|
# in order to reduce the number of api requests and
|
||||||
# avoid hitting rate limits
|
# avoid hitting rate limits
|
||||||
MIN_TIME_BETWEEN_LOCK_DETAIL_UPDATES = timedelta(seconds=1800)
|
MIN_TIME_BETWEEN_DETAIL_UPDATES = timedelta(seconds=1800)
|
||||||
|
|
||||||
# Doorbells need to update more frequently than locks
|
|
||||||
# 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
|
# Activity needs to be checked more frequently as the
|
||||||
# doorbell motion and rings are included here
|
# doorbell motion and rings are included here
|
||||||
|
|
|
@ -67,9 +67,7 @@ class AugustLock(LockDevice):
|
||||||
|
|
||||||
if detail is not None:
|
if detail is not None:
|
||||||
lock_status = detail.lock_status
|
lock_status = detail.lock_status
|
||||||
self._available = (
|
self._available = detail.bridge_is_online
|
||||||
lock_status is not None and lock_status != LockStatus.UNKNOWN
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._lock_status != lock_status:
|
if self._lock_status != lock_status:
|
||||||
self._lock_status = lock_status
|
self._lock_status = lock_status
|
||||||
|
|
|
@ -5,7 +5,18 @@ import time
|
||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
from asynctest import mock
|
from asynctest import mock
|
||||||
from august.activity import DoorOperationActivity, LockOperationActivity
|
from august.activity import (
|
||||||
|
ACTIVITY_ACTIONS_DOOR_OPERATION,
|
||||||
|
ACTIVITY_ACTIONS_DOORBELL_DING,
|
||||||
|
ACTIVITY_ACTIONS_DOORBELL_MOTION,
|
||||||
|
ACTIVITY_ACTIONS_DOORBELL_VIEW,
|
||||||
|
ACTIVITY_ACTIONS_LOCK_OPERATION,
|
||||||
|
DoorbellDingActivity,
|
||||||
|
DoorbellMotionActivity,
|
||||||
|
DoorbellViewActivity,
|
||||||
|
DoorOperationActivity,
|
||||||
|
LockOperationActivity,
|
||||||
|
)
|
||||||
from august.authenticator import AuthenticationState
|
from august.authenticator import AuthenticationState
|
||||||
from august.doorbell import Doorbell, DoorbellDetail
|
from august.doorbell import Doorbell, DoorbellDetail
|
||||||
from august.lock import Lock, LockDetail
|
from august.lock import Lock, LockDetail
|
||||||
|
@ -45,9 +56,12 @@ async def _mock_setup_august(hass, api_instance, authenticate_mock, api_mock):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def _create_august_with_devices(hass, devices, api_call_side_effects=None):
|
async def _create_august_with_devices(
|
||||||
|
hass, devices, api_call_side_effects=None, activities=None
|
||||||
|
):
|
||||||
if api_call_side_effects is None:
|
if api_call_side_effects is None:
|
||||||
api_call_side_effects = {}
|
api_call_side_effects = {}
|
||||||
|
|
||||||
device_data = {
|
device_data = {
|
||||||
"doorbells": [],
|
"doorbells": [],
|
||||||
"locks": [],
|
"locks": [],
|
||||||
|
@ -89,6 +103,8 @@ async def _create_august_with_devices(hass, devices, api_call_side_effects=None)
|
||||||
return _get_base_devices("doorbells")
|
return _get_base_devices("doorbells")
|
||||||
|
|
||||||
def get_house_activities_side_effect(access_token, house_id, limit=10):
|
def get_house_activities_side_effect(access_token, house_id, limit=10):
|
||||||
|
if activities is not None:
|
||||||
|
return activities
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def lock_return_activities_side_effect(access_token, device_id):
|
def lock_return_activities_side_effect(access_token, device_id):
|
||||||
|
@ -234,6 +250,17 @@ async def _mock_inoperative_august_lock_detail(hass):
|
||||||
return await _mock_lock_from_fixture(hass, "get_lock.offline.json")
|
return await _mock_lock_from_fixture(hass, "get_lock.offline.json")
|
||||||
|
|
||||||
|
|
||||||
|
async def _mock_activities_from_fixture(hass, path):
|
||||||
|
json_dict = await _load_json_fixture(hass, path)
|
||||||
|
activities = []
|
||||||
|
for activity_json in json_dict:
|
||||||
|
activity = _activity_from_dict(activity_json)
|
||||||
|
if activity:
|
||||||
|
activities.append(activity)
|
||||||
|
|
||||||
|
return activities
|
||||||
|
|
||||||
|
|
||||||
async def _mock_lock_from_fixture(hass, path):
|
async def _mock_lock_from_fixture(hass, path):
|
||||||
json_dict = await _load_json_fixture(hass, path)
|
json_dict = await _load_json_fixture(hass, path)
|
||||||
return LockDetail(json_dict)
|
return LockDetail(json_dict)
|
||||||
|
@ -279,3 +306,21 @@ def _mock_door_operation_activity(lock, action):
|
||||||
"action": action,
|
"action": action,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _activity_from_dict(activity_dict):
|
||||||
|
action = activity_dict.get("action")
|
||||||
|
|
||||||
|
activity_dict["dateTime"] = time.time() * 1000
|
||||||
|
|
||||||
|
if action in ACTIVITY_ACTIONS_DOORBELL_DING:
|
||||||
|
return DoorbellDingActivity(activity_dict)
|
||||||
|
if action in ACTIVITY_ACTIONS_DOORBELL_MOTION:
|
||||||
|
return DoorbellMotionActivity(activity_dict)
|
||||||
|
if action in ACTIVITY_ACTIONS_DOORBELL_VIEW:
|
||||||
|
return DoorbellViewActivity(activity_dict)
|
||||||
|
if action in ACTIVITY_ACTIONS_LOCK_OPERATION:
|
||||||
|
return LockOperationActivity(activity_dict)
|
||||||
|
if action in ACTIVITY_ACTIONS_DOOR_OPERATION:
|
||||||
|
return DoorOperationActivity(activity_dict)
|
||||||
|
return None
|
||||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.const import (
|
||||||
|
|
||||||
from tests.components.august.mocks import (
|
from tests.components.august.mocks import (
|
||||||
_create_august_with_devices,
|
_create_august_with_devices,
|
||||||
|
_mock_activities_from_fixture,
|
||||||
_mock_doorbell_from_fixture,
|
_mock_doorbell_from_fixture,
|
||||||
_mock_lock_from_fixture,
|
_mock_lock_from_fixture,
|
||||||
)
|
)
|
||||||
|
@ -70,6 +71,10 @@ async def test_create_doorbell(hass):
|
||||||
"binary_sensor.k98gidt45gul_name_ding"
|
"binary_sensor.k98gidt45gul_name_ding"
|
||||||
)
|
)
|
||||||
assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
|
assert binary_sensor_k98gidt45gul_name_ding.state == STATE_OFF
|
||||||
|
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
|
||||||
|
"binary_sensor.k98gidt45gul_name_motion"
|
||||||
|
)
|
||||||
|
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_OFF
|
||||||
|
|
||||||
|
|
||||||
async def test_create_doorbell_offline(hass):
|
async def test_create_doorbell_offline(hass):
|
||||||
|
@ -90,6 +95,29 @@ async def test_create_doorbell_offline(hass):
|
||||||
assert binary_sensor_tmt100_name_ding.state == STATE_UNAVAILABLE
|
assert binary_sensor_tmt100_name_ding.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_doorbell_with_motion(hass):
|
||||||
|
"""Test creation of a doorbell."""
|
||||||
|
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.json")
|
||||||
|
doorbell_details = [doorbell_one]
|
||||||
|
activities = await _mock_activities_from_fixture(
|
||||||
|
hass, "get_activity.doorbell_motion.json"
|
||||||
|
)
|
||||||
|
await _create_august_with_devices(hass, doorbell_details, activities=activities)
|
||||||
|
|
||||||
|
binary_sensor_k98gidt45gul_name_motion = hass.states.get(
|
||||||
|
"binary_sensor.k98gidt45gul_name_motion"
|
||||||
|
)
|
||||||
|
assert binary_sensor_k98gidt45gul_name_motion.state == STATE_ON
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
async def test_doorbell_device_registry(hass):
|
async def test_doorbell_device_registry(hass):
|
||||||
"""Test creation of a lock with doorsense and bridge ands up in the registry."""
|
"""Test creation of a lock with doorsense and bridge ands up in the registry."""
|
||||||
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.offline.json")
|
doorbell_one = await _mock_doorbell_from_fixture(hass, "get_doorbell.offline.json")
|
||||||
|
|
|
@ -6,7 +6,7 @@ from homeassistant.const import (
|
||||||
SERVICE_LOCK,
|
SERVICE_LOCK,
|
||||||
SERVICE_UNLOCK,
|
SERVICE_UNLOCK,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNKNOWN,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -80,6 +80,5 @@ async def test_one_lock_unknown_state(hass):
|
||||||
await _create_august_with_devices(hass, lock_details)
|
await _create_august_with_devices(hass, lock_details)
|
||||||
|
|
||||||
lock_brokenid_name = hass.states.get("lock.brokenid_name")
|
lock_brokenid_name = hass.states.get("lock.brokenid_name")
|
||||||
# Once we have bridge_is_online support in py-august
|
|
||||||
# this can change to STATE_UNKNOWN
|
assert lock_brokenid_name.state == STATE_UNKNOWN
|
||||||
assert lock_brokenid_name.state == STATE_UNAVAILABLE
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"otherUser" : {
|
||||||
|
"FirstName" : "Unknown",
|
||||||
|
"UserName" : "deleteduser",
|
||||||
|
"LastName" : "User",
|
||||||
|
"UserID" : "deleted",
|
||||||
|
"PhoneNo" : "deleted"
|
||||||
|
},
|
||||||
|
"dateTime" : 1582663119959,
|
||||||
|
"deviceID" : "K98GiDT45GUL",
|
||||||
|
"info" : {
|
||||||
|
"videoUploadProgress" : "in_progress",
|
||||||
|
"image" : {
|
||||||
|
"resource_type" : "image",
|
||||||
|
"etag" : "fdsf",
|
||||||
|
"created_at" : "2020-02-25T20:38:39Z",
|
||||||
|
"type" : "upload",
|
||||||
|
"format" : "jpg",
|
||||||
|
"version" : 1582663119,
|
||||||
|
"secure_url" : "https://res.cloudinary.com/updated_image.jpg",
|
||||||
|
"signature" : "fdfdfd",
|
||||||
|
"url" : "http://res.cloudinary.com/updated_image.jpg",
|
||||||
|
"bytes" : 48545,
|
||||||
|
"placeholder" : false,
|
||||||
|
"original_filename" : "file",
|
||||||
|
"width" : 720,
|
||||||
|
"tags" : [],
|
||||||
|
"public_id" : "xnsj5gphpzij9brifpf4",
|
||||||
|
"height" : 576
|
||||||
|
},
|
||||||
|
"dvrID" : "dvr",
|
||||||
|
"videoAvailable" : false,
|
||||||
|
"hasSubscription" : false
|
||||||
|
},
|
||||||
|
"callingUser" : {
|
||||||
|
"LastName" : "User",
|
||||||
|
"UserName" : "deleteduser",
|
||||||
|
"FirstName" : "Unknown",
|
||||||
|
"UserID" : "deleted",
|
||||||
|
"PhoneNo" : "deleted"
|
||||||
|
},
|
||||||
|
"house" : {
|
||||||
|
"houseName" : "K98GiDT45GUL",
|
||||||
|
"houseID" : "na"
|
||||||
|
},
|
||||||
|
"action" : "doorbell_motion_detected",
|
||||||
|
"deviceType" : "doorbell",
|
||||||
|
"entities" : {
|
||||||
|
"otherUser" : "deleted",
|
||||||
|
"house" : "na",
|
||||||
|
"device" : "K98GiDT45GUL",
|
||||||
|
"activity" : "de5585cfd4eae900bb5ba3dc",
|
||||||
|
"callingUser" : "deleted"
|
||||||
|
},
|
||||||
|
"deviceName" : "Front Door"
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue