mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 17:27:10 +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,
|
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]:
|
async def async_unlock(self, device_id: str) -> list[ActivityTypes]:
|
||||||
"""Unlock the device."""
|
"""Unlock the device."""
|
||||||
return await self._async_call_api_op_requires_bridge(
|
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.lock import Lock, LockStatus
|
||||||
from yalexs.util import get_latest_activity, update_lock_detail_from_activity
|
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.const import ATTR_BATTERY_LEVEL
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
@ -46,6 +46,8 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
|
|||||||
super().__init__(data, device)
|
super().__init__(data, device)
|
||||||
self._lock_status = None
|
self._lock_status = None
|
||||||
self._attr_unique_id = f"{self._device_id:s}_lock"
|
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()
|
self._update_from_data()
|
||||||
|
|
||||||
async def async_lock(self, **kwargs: Any) -> None:
|
async def async_lock(self, **kwargs: Any) -> None:
|
||||||
@ -56,6 +58,14 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
|
|||||||
return
|
return
|
||||||
await self._call_lock_operation(self._data.async_lock)
|
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:
|
async def async_unlock(self, **kwargs: Any) -> None:
|
||||||
"""Unlock the device."""
|
"""Unlock the device."""
|
||||||
assert self._data.activity_stream is not None
|
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(
|
api_call_side_effects.setdefault(
|
||||||
"unlock_return_activities", unlock_return_activities_side_effect
|
"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(
|
api_instance, entry = await _mock_setup_august_with_api_side_effects(
|
||||||
hass, api_call_side_effects, pubnub, brand
|
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"]
|
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_unlock_async = AsyncMock()
|
||||||
api_instance.async_lock_async = AsyncMock()
|
api_instance.async_lock_async = AsyncMock()
|
||||||
api_instance.async_status_async = AsyncMock()
|
api_instance.async_status_async = AsyncMock()
|
||||||
api_instance.async_get_user = AsyncMock(return_value={"UserID": "abc"})
|
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(
|
return api_instance, await _mock_setup_august(
|
||||||
hass, api_instance, pubnub, brand=brand
|
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")
|
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):
|
def _mock_lock_operation_activity(lock, action, offset):
|
||||||
return LockOperationActivity(
|
return LockOperationActivity(
|
||||||
SOURCE_LOCK_OPERATE,
|
SOURCE_LOCK_OPERATE,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
from aiohttp import ClientResponseError
|
from aiohttp import ClientResponseError
|
||||||
|
import pytest
|
||||||
from yalexs.authenticator_common import AuthenticationState
|
from yalexs.authenticator_common import AuthenticationState
|
||||||
from yalexs.exceptions import AugustApiAIOHTTPError
|
from yalexs.exceptions import AugustApiAIOHTTPError
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ from homeassistant.config_entries import ConfigEntryState
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
SERVICE_LOCK,
|
SERVICE_LOCK,
|
||||||
|
SERVICE_OPEN,
|
||||||
SERVICE_UNLOCK,
|
SERVICE_UNLOCK,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_ON,
|
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:
|
async def test_inoperative_locks_are_filtered_out(hass: HomeAssistant) -> None:
|
||||||
"""Ensure inoperative locks do not get setup."""
|
"""Ensure inoperative locks do not get setup."""
|
||||||
august_operative_lock = await _mock_operative_august_lock_detail(hass)
|
august_operative_lock = await _mock_operative_august_lock_detail(hass)
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.components.lock import (
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
SERVICE_LOCK,
|
SERVICE_LOCK,
|
||||||
|
SERVICE_OPEN,
|
||||||
SERVICE_UNLOCK,
|
SERVICE_UNLOCK,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
@ -25,6 +26,7 @@ from homeassistant.const import (
|
|||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
@ -33,6 +35,8 @@ from .mocks import (
|
|||||||
_mock_activities_from_fixture,
|
_mock_activities_from_fixture,
|
||||||
_mock_doorsense_enabled_august_lock_detail,
|
_mock_doorsense_enabled_august_lock_detail,
|
||||||
_mock_lock_from_fixture,
|
_mock_lock_from_fixture,
|
||||||
|
_mock_lock_with_unlatch,
|
||||||
|
_mock_operative_august_lock_detail,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
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(
|
async def test_one_lock_operation_pubnub_connected(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
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.config_entries.async_unload(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
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