Update august to support locking, unlocking, jammed (#52814)
This commit is contained in:
parent
0ce071e0a4
commit
bfe3ef0980
8 changed files with 245 additions and 14 deletions
|
@ -1,6 +1,7 @@
|
||||||
"""Support for August lock."""
|
"""Support for August lock."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from aiohttp import ClientResponseError
|
||||||
from yalexs.activity import SOURCE_PUBNUB, ActivityType
|
from yalexs.activity import SOURCE_PUBNUB, ActivityType
|
||||||
from yalexs.lock import LockStatus
|
from yalexs.lock import LockStatus
|
||||||
from yalexs.util import update_lock_detail_from_activity
|
from yalexs.util import update_lock_detail_from_activity
|
||||||
|
@ -9,12 +10,15 @@ from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntity
|
||||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import DATA_AUGUST, DOMAIN
|
from .const import DATA_AUGUST, DOMAIN
|
||||||
from .entity import AugustEntityMixin
|
from .entity import AugustEntityMixin
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
LOCK_JAMMED_ERR = 531
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up August locks."""
|
"""Set up August locks."""
|
||||||
|
@ -44,7 +48,15 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
|
||||||
await self._call_lock_operation(self._data.async_unlock)
|
await self._call_lock_operation(self._data.async_unlock)
|
||||||
|
|
||||||
async def _call_lock_operation(self, lock_operation):
|
async def _call_lock_operation(self, lock_operation):
|
||||||
|
try:
|
||||||
activities = await lock_operation(self._device_id)
|
activities = await lock_operation(self._device_id)
|
||||||
|
except ClientResponseError as err:
|
||||||
|
if err.status == LOCK_JAMMED_ERR:
|
||||||
|
self._detail.lock_status = LockStatus.JAMMED
|
||||||
|
self._detail.lock_status_datetime = dt_util.utcnow()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
for lock_activity in activities:
|
for lock_activity in activities:
|
||||||
update_lock_detail_from_activity(self._detail, lock_activity)
|
update_lock_detail_from_activity(self._detail, lock_activity)
|
||||||
|
|
||||||
|
@ -91,6 +103,10 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
|
||||||
else:
|
else:
|
||||||
self._attr_is_locked = self._lock_status is LockStatus.LOCKED
|
self._attr_is_locked = self._lock_status is LockStatus.LOCKED
|
||||||
|
|
||||||
|
self._attr_is_jammed = self._lock_status is LockStatus.JAMMED
|
||||||
|
self._attr_is_locking = self._lock_status is LockStatus.LOCKING
|
||||||
|
self._attr_is_unlocking = self._lock_status is LockStatus.UNLOCKING
|
||||||
|
|
||||||
self._attr_extra_state_attributes = {
|
self._attr_extra_state_attributes = {
|
||||||
ATTR_BATTERY_LEVEL: self._detail.battery_level
|
ATTR_BATTERY_LEVEL: self._detail.battery_level
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"domain": "august",
|
"domain": "august",
|
||||||
"name": "August",
|
"name": "August",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||||
"requirements": ["yalexs==1.1.11"],
|
"requirements": ["yalexs==1.1.12"],
|
||||||
"codeowners": ["@bdraco"],
|
"codeowners": ["@bdraco"],
|
||||||
"dhcp": [
|
"dhcp": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -2401,7 +2401,7 @@ xs1-api-client==3.0.0
|
||||||
yalesmartalarmclient==0.3.3
|
yalesmartalarmclient==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.1.11
|
yalexs==1.1.12
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.6.3
|
yeelight==0.6.3
|
||||||
|
|
|
@ -1313,7 +1313,7 @@ xknx==0.18.8
|
||||||
xmltodict==0.12.0
|
xmltodict==0.12.0
|
||||||
|
|
||||||
# homeassistant.components.august
|
# homeassistant.components.august
|
||||||
yalexs==1.1.11
|
yalexs==1.1.12
|
||||||
|
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
yeelight==0.6.3
|
yeelight==0.6.3
|
||||||
|
|
|
@ -2,9 +2,16 @@
|
||||||
import datetime
|
import datetime
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from aiohttp import ClientResponseError
|
||||||
|
import pytest
|
||||||
from yalexs.pubnub_async import AugustPubNub
|
from yalexs.pubnub_async import AugustPubNub
|
||||||
|
|
||||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
from homeassistant.components.lock import (
|
||||||
|
DOMAIN as LOCK_DOMAIN,
|
||||||
|
STATE_JAMMED,
|
||||||
|
STATE_LOCKING,
|
||||||
|
STATE_UNLOCKING,
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
SERVICE_LOCK,
|
SERVICE_LOCK,
|
||||||
|
@ -59,6 +66,44 @@ async def test_lock_changed_by(hass):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_state_locking(hass):
|
||||||
|
"""Test creation of a lock with doorsense and bridge that is locking."""
|
||||||
|
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
||||||
|
|
||||||
|
activities = await _mock_activities_from_fixture(hass, "get_activity.locking.json")
|
||||||
|
await _create_august_with_devices(hass, [lock_one], activities=activities)
|
||||||
|
|
||||||
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
|
|
||||||
|
assert lock_online_with_doorsense_name.state == STATE_LOCKING
|
||||||
|
|
||||||
|
|
||||||
|
async def test_state_unlocking(hass):
|
||||||
|
"""Test creation of a lock with doorsense and bridge that is unlocking."""
|
||||||
|
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
||||||
|
|
||||||
|
activities = await _mock_activities_from_fixture(
|
||||||
|
hass, "get_activity.unlocking.json"
|
||||||
|
)
|
||||||
|
await _create_august_with_devices(hass, [lock_one], activities=activities)
|
||||||
|
|
||||||
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
|
|
||||||
|
assert lock_online_with_doorsense_name.state == STATE_UNLOCKING
|
||||||
|
|
||||||
|
|
||||||
|
async def test_state_jammed(hass):
|
||||||
|
"""Test creation of a lock with doorsense and bridge that is jammed."""
|
||||||
|
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
||||||
|
|
||||||
|
activities = await _mock_activities_from_fixture(hass, "get_activity.jammed.json")
|
||||||
|
await _create_august_with_devices(hass, [lock_one], activities=activities)
|
||||||
|
|
||||||
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
|
|
||||||
|
assert lock_online_with_doorsense_name.state == STATE_JAMMED
|
||||||
|
|
||||||
|
|
||||||
async def test_one_lock_operation(hass):
|
async def test_one_lock_operation(hass):
|
||||||
"""Test creation of a lock with doorsense and bridge."""
|
"""Test creation of a lock with doorsense and bridge."""
|
||||||
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
||||||
|
@ -109,6 +154,74 @@ async def test_one_lock_operation(hass):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_lock_jammed(hass):
|
||||||
|
"""Test lock gets jammed on unlock."""
|
||||||
|
|
||||||
|
def _unlock_return_activities_side_effect(access_token, device_id):
|
||||||
|
raise ClientResponseError(None, None, status=531)
|
||||||
|
|
||||||
|
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
||||||
|
await _create_august_with_devices(
|
||||||
|
hass,
|
||||||
|
[lock_one],
|
||||||
|
api_call_side_effects={
|
||||||
|
"unlock_return_activities": _unlock_return_activities_side_effect
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
|
|
||||||
|
assert lock_online_with_doorsense_name.state == STATE_LOCKED
|
||||||
|
|
||||||
|
assert lock_online_with_doorsense_name.attributes.get("battery_level") == 92
|
||||||
|
assert (
|
||||||
|
lock_online_with_doorsense_name.attributes.get("friendly_name")
|
||||||
|
== "online_with_doorsense Name"
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"}
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
|
assert lock_online_with_doorsense_name.state == STATE_JAMMED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_lock_throws_exception_on_unknown_status_code(hass):
|
||||||
|
"""Test lock throws exception."""
|
||||||
|
|
||||||
|
def _unlock_return_activities_side_effect(access_token, device_id):
|
||||||
|
raise ClientResponseError(None, None, status=500)
|
||||||
|
|
||||||
|
lock_one = await _mock_doorsense_enabled_august_lock_detail(hass)
|
||||||
|
await _create_august_with_devices(
|
||||||
|
hass,
|
||||||
|
[lock_one],
|
||||||
|
api_call_side_effects={
|
||||||
|
"unlock_return_activities": _unlock_return_activities_side_effect
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
|
|
||||||
|
assert lock_online_with_doorsense_name.state == STATE_LOCKED
|
||||||
|
|
||||||
|
assert lock_online_with_doorsense_name.attributes.get("battery_level") == 92
|
||||||
|
assert (
|
||||||
|
lock_online_with_doorsense_name.attributes.get("friendly_name")
|
||||||
|
== "online_with_doorsense Name"
|
||||||
|
)
|
||||||
|
|
||||||
|
data = {ATTR_ENTITY_ID: "lock.online_with_doorsense_name"}
|
||||||
|
with pytest.raises(ClientResponseError):
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
LOCK_DOMAIN, SERVICE_UNLOCK, data, blocking=True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
async def test_one_lock_unknown_state(hass):
|
async def test_one_lock_unknown_state(hass):
|
||||||
"""Test creation of a lock with doorsense and bridge."""
|
"""Test creation of a lock with doorsense and bridge."""
|
||||||
lock_one = await _mock_lock_from_fixture(
|
lock_one = await _mock_lock_from_fixture(
|
||||||
|
@ -178,7 +291,7 @@ async def test_lock_update_via_pubnub(hass):
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
assert lock_online_with_doorsense_name.state == STATE_UNLOCKED
|
assert lock_online_with_doorsense_name.state == STATE_UNLOCKING
|
||||||
|
|
||||||
pubnub.message(
|
pubnub.message(
|
||||||
pubnub,
|
pubnub,
|
||||||
|
@ -193,24 +306,24 @@ async def test_lock_update_via_pubnub(hass):
|
||||||
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
assert lock_online_with_doorsense_name.state == STATE_LOCKED
|
assert lock_online_with_doorsense_name.state == STATE_LOCKING
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30))
|
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
assert lock_online_with_doorsense_name.state == STATE_LOCKED
|
assert lock_online_with_doorsense_name.state == STATE_LOCKING
|
||||||
|
|
||||||
pubnub.connected = True
|
pubnub.connected = True
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30))
|
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(seconds=30))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
assert lock_online_with_doorsense_name.state == STATE_LOCKED
|
assert lock_online_with_doorsense_name.state == STATE_LOCKING
|
||||||
|
|
||||||
# Ensure pubnub status is always preserved
|
# Ensure pubnub status is always preserved
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=2))
|
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=2))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
assert lock_online_with_doorsense_name.state == STATE_LOCKED
|
assert lock_online_with_doorsense_name.state == STATE_LOCKING
|
||||||
|
|
||||||
pubnub.message(
|
pubnub.message(
|
||||||
pubnub,
|
pubnub,
|
||||||
|
@ -224,12 +337,12 @@ async def test_lock_update_via_pubnub(hass):
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
assert lock_online_with_doorsense_name.state == STATE_UNLOCKED
|
assert lock_online_with_doorsense_name.state == STATE_UNLOCKING
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=4))
|
async_fire_time_changed(hass, dt_util.utcnow() + datetime.timedelta(hours=4))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
lock_online_with_doorsense_name = hass.states.get("lock.online_with_doorsense_name")
|
||||||
assert lock_online_with_doorsense_name.state == STATE_UNLOCKED
|
assert lock_online_with_doorsense_name.state == STATE_UNLOCKING
|
||||||
|
|
||||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
34
tests/fixtures/august/get_activity.jammed.json
vendored
Normal file
34
tests/fixtures/august/get_activity.jammed.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[{
|
||||||
|
"entities" : {
|
||||||
|
"activity" : "mockActivity2",
|
||||||
|
"house" : "123",
|
||||||
|
"device" : "online_with_doorsense",
|
||||||
|
"callingUser" : "mockUserId2",
|
||||||
|
"otherUser" : "deleted"
|
||||||
|
},
|
||||||
|
"callingUser" : {
|
||||||
|
"LastName" : "elven princess",
|
||||||
|
"UserID" : "mockUserId2",
|
||||||
|
"FirstName" : "Your favorite"
|
||||||
|
},
|
||||||
|
"otherUser" : {
|
||||||
|
"LastName" : "User",
|
||||||
|
"UserName" : "deleteduser",
|
||||||
|
"FirstName" : "Unknown",
|
||||||
|
"UserID" : "deleted",
|
||||||
|
"PhoneNo" : "deleted"
|
||||||
|
},
|
||||||
|
"deviceType" : "lock",
|
||||||
|
"deviceName" : "MockHouseTDoor",
|
||||||
|
"action" : "jammed",
|
||||||
|
"dateTime" : 1582007218000,
|
||||||
|
"info" : {
|
||||||
|
"remote" : true,
|
||||||
|
"DateLogActionID" : "ABC+Time"
|
||||||
|
},
|
||||||
|
"deviceID" : "online_with_doorsense",
|
||||||
|
"house" : {
|
||||||
|
"houseName" : "MockHouse",
|
||||||
|
"houseID" : "123"
|
||||||
|
}
|
||||||
|
}]
|
34
tests/fixtures/august/get_activity.locking.json
vendored
Normal file
34
tests/fixtures/august/get_activity.locking.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[{
|
||||||
|
"entities" : {
|
||||||
|
"activity" : "mockActivity2",
|
||||||
|
"house" : "123",
|
||||||
|
"device" : "online_with_doorsense",
|
||||||
|
"callingUser" : "mockUserId2",
|
||||||
|
"otherUser" : "deleted"
|
||||||
|
},
|
||||||
|
"callingUser" : {
|
||||||
|
"LastName" : "elven princess",
|
||||||
|
"UserID" : "mockUserId2",
|
||||||
|
"FirstName" : "Your favorite"
|
||||||
|
},
|
||||||
|
"otherUser" : {
|
||||||
|
"LastName" : "User",
|
||||||
|
"UserName" : "deleteduser",
|
||||||
|
"FirstName" : "Unknown",
|
||||||
|
"UserID" : "deleted",
|
||||||
|
"PhoneNo" : "deleted"
|
||||||
|
},
|
||||||
|
"deviceType" : "lock",
|
||||||
|
"deviceName" : "MockHouseTDoor",
|
||||||
|
"action" : "locking",
|
||||||
|
"dateTime" : 1582007218000,
|
||||||
|
"info" : {
|
||||||
|
"remote" : true,
|
||||||
|
"DateLogActionID" : "ABC+Time"
|
||||||
|
},
|
||||||
|
"deviceID" : "online_with_doorsense",
|
||||||
|
"house" : {
|
||||||
|
"houseName" : "MockHouse",
|
||||||
|
"houseID" : "123"
|
||||||
|
}
|
||||||
|
}]
|
34
tests/fixtures/august/get_activity.unlocking.json
vendored
Normal file
34
tests/fixtures/august/get_activity.unlocking.json
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[{
|
||||||
|
"entities" : {
|
||||||
|
"activity" : "mockActivity2",
|
||||||
|
"house" : "123",
|
||||||
|
"device" : "online_with_doorsense",
|
||||||
|
"callingUser" : "mockUserId2",
|
||||||
|
"otherUser" : "deleted"
|
||||||
|
},
|
||||||
|
"callingUser" : {
|
||||||
|
"LastName" : "elven princess",
|
||||||
|
"UserID" : "mockUserId2",
|
||||||
|
"FirstName" : "Your favorite"
|
||||||
|
},
|
||||||
|
"otherUser" : {
|
||||||
|
"LastName" : "User",
|
||||||
|
"UserName" : "deleteduser",
|
||||||
|
"FirstName" : "Unknown",
|
||||||
|
"UserID" : "deleted",
|
||||||
|
"PhoneNo" : "deleted"
|
||||||
|
},
|
||||||
|
"deviceType" : "lock",
|
||||||
|
"deviceName" : "MockHouseTDoor",
|
||||||
|
"action" : "unlocking",
|
||||||
|
"dateTime" : 1582007218000,
|
||||||
|
"info" : {
|
||||||
|
"remote" : true,
|
||||||
|
"DateLogActionID" : "ABC+Time"
|
||||||
|
},
|
||||||
|
"deviceID" : "online_with_doorsense",
|
||||||
|
"house" : {
|
||||||
|
"houseName" : "MockHouse",
|
||||||
|
"houseID" : "123"
|
||||||
|
}
|
||||||
|
}]
|
Loading…
Add table
Add a link
Reference in a new issue