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 commentspull/32094/head
parent
d2d788631e
commit
693441e56f
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
Loading…
Reference in New Issue