mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add august open action (#113795)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
ae6c394b53
commit
05d0174e07
@ -381,6 +381,25 @@ class AugustData(AugustSubscriberMixin):
|
||||
hyper_bridge,
|
||||
)
|
||||
|
||||
async def async_unlatch(self, device_id: str) -> list[ActivityTypes]:
|
||||
"""Open/unlatch the device."""
|
||||
return await self._async_call_api_op_requires_bridge(
|
||||
device_id,
|
||||
self._api.async_unlatch_return_activities,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
)
|
||||
|
||||
async def async_unlatch_async(self, device_id: str, hyper_bridge: bool) -> str:
|
||||
"""Open/unlatch the device but do not wait for a response since it will come via pubnub."""
|
||||
return await self._async_call_api_op_requires_bridge(
|
||||
device_id,
|
||||
self._api.async_unlatch_async,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
hyper_bridge,
|
||||
)
|
||||
|
||||
async def async_unlock(self, device_id: str) -> list[ActivityTypes]:
|
||||
"""Unlock the device."""
|
||||
return await self._async_call_api_op_requires_bridge(
|
||||
|
@ -11,7 +11,7 @@ from yalexs.activity import SOURCE_PUBNUB, ActivityType, ActivityTypes
|
||||
from yalexs.lock import Lock, LockStatus
|
||||
from yalexs.util import get_latest_activity, update_lock_detail_from_activity
|
||||
|
||||
from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntity
|
||||
from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntity, LockEntityFeature
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
@ -46,6 +46,8 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
|
||||
super().__init__(data, device)
|
||||
self._lock_status = None
|
||||
self._attr_unique_id = f"{self._device_id:s}_lock"
|
||||
if self._detail.unlatch_supported:
|
||||
self._attr_supported_features = LockEntityFeature.OPEN
|
||||
self._update_from_data()
|
||||
|
||||
async def async_lock(self, **kwargs: Any) -> None:
|
||||
@ -56,6 +58,14 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
|
||||
return
|
||||
await self._call_lock_operation(self._data.async_lock)
|
||||
|
||||
async def async_open(self, **kwargs: Any) -> None:
|
||||
"""Open/unlatch the device."""
|
||||
assert self._data.activity_stream is not None
|
||||
if self._data.activity_stream.pubnub.connected:
|
||||
await self._data.async_unlatch_async(self._device_id, self._hyper_bridge)
|
||||
return
|
||||
await self._call_lock_operation(self._data.async_unlatch)
|
||||
|
||||
async def async_unlock(self, **kwargs: Any) -> None:
|
||||
"""Unlock the device."""
|
||||
assert self._data.activity_stream is not None
|
||||
|
@ -0,0 +1,94 @@
|
||||
{
|
||||
"LockName": "Lock online with unlatch supported",
|
||||
"Type": 17,
|
||||
"Created": "2024-03-14T18:03:09.003Z",
|
||||
"Updated": "2024-03-14T18:03:09.003Z",
|
||||
"LockID": "online_with_unlatch",
|
||||
"HouseID": "mockhouseid1",
|
||||
"HouseName": "Zuhause",
|
||||
"Calibrated": false,
|
||||
"timeZone": "Europe/Berlin",
|
||||
"battery": 0.61,
|
||||
"batteryInfo": {
|
||||
"level": 0.61,
|
||||
"warningState": "lock_state_battery_warning_none",
|
||||
"infoUpdatedDate": "2024-04-30T17:55:09.045Z",
|
||||
"lastChangeDate": "2024-03-15T07:04:00.000Z",
|
||||
"lastChangeVoltage": 8350,
|
||||
"state": "Mittel",
|
||||
"icon": "https://app-resources.aaecosystem.com/images/lock_battery_state_medium.png"
|
||||
},
|
||||
"hostHardwareID": "xxx",
|
||||
"supportsEntryCodes": true,
|
||||
"remoteOperateSecret": "xxxx",
|
||||
"skuNumber": "NONE",
|
||||
"macAddress": "DE:AD:BE:00:00:00",
|
||||
"SerialNumber": "LPOC000000",
|
||||
"LockStatus": {
|
||||
"status": "locked",
|
||||
"dateTime": "2024-04-30T18:41:25.673Z",
|
||||
"isLockStatusChanged": false,
|
||||
"valid": true,
|
||||
"doorState": "init"
|
||||
},
|
||||
"currentFirmwareVersion": "1.0.4",
|
||||
"homeKitEnabled": false,
|
||||
"zWaveEnabled": false,
|
||||
"isGalileo": false,
|
||||
"Bridge": {
|
||||
"_id": "65f33445529187c78a100000",
|
||||
"mfgBridgeID": "LPOCH0004Y",
|
||||
"deviceModel": "august-lock",
|
||||
"firmwareVersion": "1.0.4",
|
||||
"operative": true,
|
||||
"status": {
|
||||
"current": "online",
|
||||
"lastOnline": "2024-04-30T18:41:27.971Z",
|
||||
"updated": "2024-04-30T18:41:27.971Z",
|
||||
"lastOffline": "2024-04-25T14:41:40.118Z"
|
||||
},
|
||||
"locks": [
|
||||
{
|
||||
"_id": "656858c182e6c7c555faf758",
|
||||
"LockID": "68895DD075A1444FAD4C00B273EEEF28",
|
||||
"macAddress": "DE:AD:BE:EF:0B:BC"
|
||||
}
|
||||
],
|
||||
"hyperBridge": true
|
||||
},
|
||||
"OfflineKeys": {
|
||||
"created": [],
|
||||
"loaded": [
|
||||
{
|
||||
"created": "2024-03-14T18:03:09.034Z",
|
||||
"key": "055281d4aa9bd7b68c7b7bb78e2f34ca",
|
||||
"slot": 1,
|
||||
"UserID": "b4b44424-0000-0000-0000-25c224dad337",
|
||||
"loaded": "2024-03-14T18:03:33.470Z"
|
||||
}
|
||||
],
|
||||
"deleted": []
|
||||
},
|
||||
"parametersToSet": {},
|
||||
"users": {
|
||||
"b4b44424-0000-0000-0000-25c224dad337": {
|
||||
"UserType": "superuser",
|
||||
"FirstName": "m10x",
|
||||
"LastName": "m10x",
|
||||
"identifiers": ["phone:+494444444", "email:m10x@example.com"]
|
||||
}
|
||||
},
|
||||
"pubsubChannel": "pubsub",
|
||||
"ruleHash": {},
|
||||
"cameras": [],
|
||||
"geofenceLimits": {
|
||||
"ios": {
|
||||
"debounceInterval": 90,
|
||||
"gpsAccuracyMultiplier": 2.5,
|
||||
"maximumGeofence": 5000,
|
||||
"minimumGeofence": 100,
|
||||
"minGPSAccuracyRequired": 80
|
||||
}
|
||||
},
|
||||
"accessSchedulesAllowed": true
|
||||
}
|
@ -191,6 +191,9 @@ async def _create_august_api_with_devices(
|
||||
api_call_side_effects.setdefault(
|
||||
"unlock_return_activities", unlock_return_activities_side_effect
|
||||
)
|
||||
api_call_side_effects.setdefault(
|
||||
"async_unlatch_return_activities", unlock_return_activities_side_effect
|
||||
)
|
||||
|
||||
api_instance, entry = await _mock_setup_august_with_api_side_effects(
|
||||
hass, api_call_side_effects, pubnub, brand
|
||||
@ -244,10 +247,17 @@ async def _mock_setup_august_with_api_side_effects(
|
||||
side_effect=api_call_side_effects["unlock_return_activities"]
|
||||
)
|
||||
|
||||
if api_call_side_effects["async_unlatch_return_activities"]:
|
||||
type(api_instance).async_unlatch_return_activities = AsyncMock(
|
||||
side_effect=api_call_side_effects["async_unlatch_return_activities"]
|
||||
)
|
||||
|
||||
api_instance.async_unlock_async = AsyncMock()
|
||||
api_instance.async_lock_async = AsyncMock()
|
||||
api_instance.async_status_async = AsyncMock()
|
||||
api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"})
|
||||
api_instance.async_unlatch_async = AsyncMock()
|
||||
api_instance.async_unlatch = AsyncMock()
|
||||
|
||||
return api_instance, await _mock_setup_august(
|
||||
hass, api_instance, pubnub, brand=brand
|
||||
@ -366,6 +376,10 @@ async def _mock_doorsense_missing_august_lock_detail(hass):
|
||||
return await _mock_lock_from_fixture(hass, "get_lock.online_missing_doorsense.json")
|
||||
|
||||
|
||||
async def _mock_lock_with_unlatch(hass):
|
||||
return await _mock_lock_from_fixture(hass, "get_lock.online_with_unlatch.json")
|
||||
|
||||
|
||||
def _mock_lock_operation_activity(lock, action, offset):
|
||||
return LockOperationActivity(
|
||||
SOURCE_LOCK_OPERATE,
|
||||
|
@ -3,6 +3,7 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
import pytest
|
||||
from yalexs.authenticator_common import AuthenticationState
|
||||
from yalexs.exceptions import AugustApiAIOHTTPError
|
||||
|
||||
@ -12,6 +13,7 @@ from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_LOCK,
|
||||
SERVICE_OPEN,
|
||||
SERVICE_UNLOCK,
|
||||
STATE_LOCKED,
|
||||
STATE_ON,
|
||||
@ -162,6 +164,17 @@ async def test_lock_throws_august_api_http_error(hass: HomeAssistant) -> None:
|
||||
)
|
||||
|
||||
|
||||
async def test_open_throws_hass_service_not_supported_error(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test open throws correct error on entity does not support this service error."""
|
||||
mocked_lock_detail = await _mock_operative_august_lock_detail(hass)
|
||||
await _create_august_with_devices(hass, [mocked_lock_detail])
|
||||
data = {ATTR_ENTITY_ID: "lock.a6697750d607098bae8d6baa11ef8063_name"}
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)
|
||||
|
||||
|
||||
async def test_inoperative_locks_are_filtered_out(hass: HomeAssistant) -> None:
|
||||
"""Ensure inoperative locks do not get setup."""
|
||||
august_operative_lock = await _mock_operative_august_lock_detail(hass)
|
||||
|
@ -18,6 +18,7 @@ from homeassistant.components.lock import (
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_LOCK,
|
||||
SERVICE_OPEN,
|
||||
SERVICE_UNLOCK,
|
||||
STATE_LOCKED,
|
||||
STATE_UNAVAILABLE,
|
||||
@ -25,6 +26,7 @@ from homeassistant.const import (
|
||||
STATE_UNLOCKED,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
@ -33,6 +35,8 @@ from .mocks import (
|
||||
_mock_activities_from_fixture,
|
||||
_mock_doorsense_enabled_august_lock_detail,
|
||||
_mock_lock_from_fixture,
|
||||
_mock_lock_with_unlatch,
|
||||
_mock_operative_august_lock_detail,
|
||||
)
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
@ -156,6 +160,60 @@ async def test_one_lock_operation(
|
||||
)
|
||||
|
||||
|
||||
async def test_open_lock_operation(hass: HomeAssistant) -> None:
|
||||
"""Test open lock operation using the open service."""
|
||||
lock_with_unlatch = await _mock_lock_with_unlatch(hass)
|
||||
await _create_august_with_devices(hass, [lock_with_unlatch])
|
||||
|
||||
lock_online_with_unlatch_name = hass.states.get("lock.online_with_unlatch_name")
|
||||
assert lock_online_with_unlatch_name.state == STATE_LOCKED
|
||||
|
||||
data = {ATTR_ENTITY_ID: "lock.online_with_unlatch_name"}
|
||||
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
lock_online_with_unlatch_name = hass.states.get("lock.online_with_unlatch_name")
|
||||
assert lock_online_with_unlatch_name.state == STATE_UNLOCKED
|
||||
|
||||
|
||||
async def test_open_lock_operation_pubnub_connected(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test open lock operation using the open service when pubnub is connected."""
|
||||
lock_with_unlatch = await _mock_lock_with_unlatch(hass)
|
||||
assert lock_with_unlatch.pubsub_channel == "pubsub"
|
||||
|
||||
pubnub = AugustPubNub()
|
||||
await _create_august_with_devices(hass, [lock_with_unlatch], pubnub=pubnub)
|
||||
pubnub.connected = True
|
||||
|
||||
lock_online_with_unlatch_name = hass.states.get("lock.online_with_unlatch_name")
|
||||
assert lock_online_with_unlatch_name.state == STATE_LOCKED
|
||||
|
||||
data = {ATTR_ENTITY_ID: "lock.online_with_unlatch_name"}
|
||||
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
pubnub.message(
|
||||
pubnub,
|
||||
Mock(
|
||||
channel=lock_with_unlatch.pubsub_channel,
|
||||
timetoken=(dt_util.utcnow().timestamp() + 2) * 10000000,
|
||||
message={
|
||||
"status": "kAugLockState_Unlocked",
|
||||
},
|
||||
),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
lock_online_with_unlatch_name = hass.states.get("lock.online_with_unlatch_name")
|
||||
assert lock_online_with_unlatch_name.state == STATE_UNLOCKED
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_one_lock_operation_pubnub_connected(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
@ -449,3 +507,14 @@ async def test_lock_update_via_pubnub(hass: HomeAssistant) -> None:
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_open_throws_hass_service_not_supported_error(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test open throws correct error on entity does not support this service error."""
|
||||
mocked_lock_detail = await _mock_operative_august_lock_detail(hass)
|
||||
await _create_august_with_devices(hass, [mocked_lock_detail])
|
||||
data = {ATTR_ENTITY_ID: "lock.a6697750d607098bae8d6baa11ef8063_name"}
|
||||
with pytest.raises(HomeAssistantError, match="does not support this service"):
|
||||
await hass.services.async_call(LOCK_DOMAIN, SERVICE_OPEN, data, blocking=True)
|
||||
|
Loading…
x
Reference in New Issue
Block a user