Update offline keys from august cloud for august branded yale locks (#76577)
This commit is contained in:
parent
4c70129427
commit
bf899101ce
10 changed files with 194 additions and 38 deletions
|
@ -13,6 +13,7 @@ from yalexs.lock import Lock, LockDetail
|
|||
from yalexs.pubnub_activity import activities_from_pubnub_message
|
||||
from yalexs.pubnub_async import AugustPubNub, async_create_pubnub
|
||||
|
||||
from homeassistant.components import yalexs_ble
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
@ -93,6 +94,26 @@ async def async_setup_august(
|
|||
return True
|
||||
|
||||
|
||||
@callback
|
||||
def _async_trigger_ble_lock_discovery(
|
||||
hass: HomeAssistant, locks_with_offline_keys: list[LockDetail]
|
||||
):
|
||||
"""Update keys for the yalexs-ble integration if available."""
|
||||
for lock_detail in locks_with_offline_keys:
|
||||
yalexs_ble.async_discovery(
|
||||
hass,
|
||||
yalexs_ble.YaleXSBLEDiscovery(
|
||||
{
|
||||
"name": lock_detail.device_name,
|
||||
"address": lock_detail.mac_address,
|
||||
"serial": lock_detail.serial_number,
|
||||
"key": lock_detail.offline_key,
|
||||
"slot": lock_detail.offline_slot,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class AugustData(AugustSubscriberMixin):
|
||||
"""August data object."""
|
||||
|
||||
|
@ -133,6 +154,19 @@ class AugustData(AugustSubscriberMixin):
|
|||
# detail as we cannot determine if they are usable.
|
||||
# This also allows us to avoid checking for
|
||||
# detail being None all over the place
|
||||
|
||||
# Currently we know how to feed data to yalexe_ble
|
||||
# but we do not know how to send it to homekit_controller
|
||||
# yet
|
||||
_async_trigger_ble_lock_discovery(
|
||||
self._hass,
|
||||
[
|
||||
lock_detail
|
||||
for lock_detail in self._device_detail_by_id.values()
|
||||
if isinstance(lock_detail, LockDetail) and lock_detail.offline_key
|
||||
],
|
||||
)
|
||||
|
||||
self._remove_inoperative_locks()
|
||||
self._remove_inoperative_doorbells()
|
||||
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
],
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"]
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"after_dependencies": ["yalexs_ble"]
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import TypedDict
|
||||
|
||||
import async_timeout
|
||||
from yalexs_ble import PushLock, local_name_is_unique
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry
|
||||
from homeassistant.const import CONF_ADDRESS, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
@ -19,6 +20,28 @@ from .util import async_find_existing_service_info, bluetooth_callback_matcher
|
|||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.LOCK]
|
||||
|
||||
|
||||
class YaleXSBLEDiscovery(TypedDict):
|
||||
"""A validated discovery of a Yale XS BLE device."""
|
||||
|
||||
name: str
|
||||
address: str
|
||||
serial: str
|
||||
key: str
|
||||
slot: int
|
||||
|
||||
|
||||
@callback
|
||||
def async_discovery(hass: HomeAssistant, discovery: YaleXSBLEDiscovery) -> None:
|
||||
"""Update keys for the yalexs-ble integration if available."""
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
"yalexs_ble",
|
||||
context={"source": SOURCE_INTEGRATION_DISCOVERY},
|
||||
data=discovery,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Yale Access Bluetooth from a config entry."""
|
||||
local_name = entry.data[CONF_LOCAL_NAME]
|
||||
|
|
|
@ -40,15 +40,7 @@
|
|||
},
|
||||
"OfflineKeys": {
|
||||
"created": [],
|
||||
"loaded": [
|
||||
{
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"slot": 1,
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941111",
|
||||
"created": "2017-12-10T03:12:09.215Z",
|
||||
"loaded": "2017-12-10T03:12:54.391Z"
|
||||
}
|
||||
],
|
||||
"loaded": [],
|
||||
"deleted": [],
|
||||
"loadedhk": [
|
||||
{
|
||||
|
|
|
@ -40,15 +40,7 @@
|
|||
},
|
||||
"OfflineKeys": {
|
||||
"created": [],
|
||||
"loaded": [
|
||||
{
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"slot": 1,
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941111",
|
||||
"created": "2017-12-10T03:12:09.215Z",
|
||||
"loaded": "2017-12-10T03:12:54.391Z"
|
||||
}
|
||||
],
|
||||
"loaded": [],
|
||||
"deleted": [],
|
||||
"loadedhk": [
|
||||
{
|
||||
|
|
|
@ -19,15 +19,7 @@
|
|||
}
|
||||
],
|
||||
"deleted": [],
|
||||
"loaded": [
|
||||
{
|
||||
"UserID": "userid",
|
||||
"created": "2000-00-00T00:00:00.447Z",
|
||||
"key": "key",
|
||||
"loaded": "2000-00-00T00:00:00.447Z",
|
||||
"slot": 1
|
||||
}
|
||||
]
|
||||
"loaded": []
|
||||
},
|
||||
"SerialNumber": "ABC",
|
||||
"Type": 3,
|
||||
|
|
|
@ -40,15 +40,7 @@
|
|||
},
|
||||
"OfflineKeys": {
|
||||
"created": [],
|
||||
"loaded": [
|
||||
{
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"slot": 1,
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941111",
|
||||
"created": "2017-12-10T03:12:09.215Z",
|
||||
"loaded": "2017-12-10T03:12:54.391Z"
|
||||
}
|
||||
],
|
||||
"loaded": [],
|
||||
"deleted": [],
|
||||
"loadedhk": [
|
||||
{
|
||||
|
|
100
tests/components/august/fixtures/get_lock.online_with_keys.json
Normal file
100
tests/components/august/fixtures/get_lock.online_with_keys.json
Normal file
|
@ -0,0 +1,100 @@
|
|||
{
|
||||
"LockName": "Front Door Lock",
|
||||
"Type": 2,
|
||||
"Created": "2017-12-10T03:12:09.210Z",
|
||||
"Updated": "2017-12-10T03:12:09.210Z",
|
||||
"LockID": "A6697750D607098BAE8D6BAA11EF8063",
|
||||
"HouseID": "000000000000",
|
||||
"HouseName": "My House",
|
||||
"Calibrated": false,
|
||||
"skuNumber": "AUG-SL02-M02-S02",
|
||||
"timeZone": "America/Vancouver",
|
||||
"battery": 0.88,
|
||||
"SerialNumber": "X2FSW05DGA",
|
||||
"LockStatus": {
|
||||
"status": "locked",
|
||||
"doorState": "closed",
|
||||
"dateTime": "2017-12-10T04:48:30.272Z",
|
||||
"isLockStatusChanged": true,
|
||||
"valid": true
|
||||
},
|
||||
"currentFirmwareVersion": "109717e9-3.0.44-3.0.30",
|
||||
"homeKitEnabled": false,
|
||||
"zWaveEnabled": false,
|
||||
"isGalileo": false,
|
||||
"Bridge": {
|
||||
"_id": "aaacab87f7efxa0015884999",
|
||||
"mfgBridgeID": "AAGPP102XX",
|
||||
"deviceModel": "august-doorbell",
|
||||
"firmwareVersion": "2.3.0-RC153+201711151527",
|
||||
"operative": true
|
||||
},
|
||||
"keypad": {
|
||||
"_id": "5bc65c24e6ef2a263e1450a8",
|
||||
"serialNumber": "K1GXB0054Z",
|
||||
"lockID": "92412D1B44004595B5DEB134E151A8D3",
|
||||
"currentFirmwareVersion": "2.27.0",
|
||||
"battery": {},
|
||||
"batteryLevel": "Medium",
|
||||
"batteryRaw": 170
|
||||
},
|
||||
"OfflineKeys": {
|
||||
"created": [],
|
||||
"loaded": [
|
||||
{
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"slot": 1,
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941111",
|
||||
"created": "2017-12-10T03:12:09.215Z",
|
||||
"loaded": "2017-12-10T03:12:54.391Z"
|
||||
}
|
||||
],
|
||||
"deleted": [],
|
||||
"loadedhk": [
|
||||
{
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941222",
|
||||
"slot": 256,
|
||||
"UserID": "cccca94e-373e-aaaa-bbbb-333396827777",
|
||||
"created": "2017-12-10T03:12:09.218Z",
|
||||
"loaded": "2017-12-10T03:12:55.563Z"
|
||||
}
|
||||
]
|
||||
},
|
||||
"parametersToSet": {},
|
||||
"users": {
|
||||
"cccca94e-373e-aaaa-bbbb-333396827777": {
|
||||
"UserType": "superuser",
|
||||
"FirstName": "Foo",
|
||||
"LastName": "Bar",
|
||||
"identifiers": ["email:foo@bar.com", "phone:+177777777777"],
|
||||
"imageInfo": {
|
||||
"original": {
|
||||
"width": 948,
|
||||
"height": 949,
|
||||
"format": "jpg",
|
||||
"url": "http://www.image.com/foo.jpeg",
|
||||
"secure_url": "https://www.image.com/foo.jpeg"
|
||||
},
|
||||
"thumbnail": {
|
||||
"width": 128,
|
||||
"height": 128,
|
||||
"format": "jpg",
|
||||
"url": "http://www.image.com/foo.jpeg",
|
||||
"secure_url": "https://www.image.com/foo.jpeg"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pubsubChannel": "3333a674-ffff-aaaa-b351-b3a4473f3333",
|
||||
"ruleHash": {},
|
||||
"cameras": [],
|
||||
"geofenceLimits": {
|
||||
"ios": {
|
||||
"debounceInterval": 90,
|
||||
"gpsAccuracyMultiplier": 2.5,
|
||||
"maximumGeofence": 5000,
|
||||
"minimumGeofence": 100,
|
||||
"minGPSAccuracyRequired": 80
|
||||
}
|
||||
}
|
||||
}
|
|
@ -302,6 +302,10 @@ async def _mock_operative_august_lock_detail(hass):
|
|||
return await _mock_lock_from_fixture(hass, "get_lock.online.json")
|
||||
|
||||
|
||||
async def _mock_lock_with_offline_key(hass):
|
||||
return await _mock_lock_from_fixture(hass, "get_lock.online_with_keys.json")
|
||||
|
||||
|
||||
async def _mock_inoperative_august_lock_detail(hass):
|
||||
return await _mock_lock_from_fixture(hass, "get_lock.offline.json")
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ from tests.components.august.mocks import (
|
|||
_mock_doorsense_missing_august_lock_detail,
|
||||
_mock_get_config,
|
||||
_mock_inoperative_august_lock_detail,
|
||||
_mock_lock_with_offline_key,
|
||||
_mock_operative_august_lock_detail,
|
||||
)
|
||||
|
||||
|
@ -323,6 +324,31 @@ async def test_load_unload(hass):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_load_triggers_ble_discovery(hass):
|
||||
"""Test that loading a lock that supports offline ble operation passes the keys to yalexe_ble."""
|
||||
|
||||
august_lock_with_key = await _mock_lock_with_offline_key(hass)
|
||||
august_lock_without_key = await _mock_operative_august_lock_detail(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.august.yalexs_ble.async_discovery"
|
||||
) as mock_discovery:
|
||||
config_entry = await _create_august_with_devices(
|
||||
hass, [august_lock_with_key, august_lock_without_key]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
assert len(mock_discovery.mock_calls) == 1
|
||||
assert mock_discovery.mock_calls[0][1][1] == {
|
||||
"name": "Front Door Lock",
|
||||
"address": None,
|
||||
"serial": "X2FSW05DGA",
|
||||
"key": "kkk01d4300c1dcxxx1c330f794941111",
|
||||
"slot": 1,
|
||||
}
|
||||
|
||||
|
||||
async def remove_device(ws_client, device_id, config_entry_id):
|
||||
"""Remove config entry from a device."""
|
||||
await ws_client.send_json(
|
||||
|
|
Loading…
Add table
Reference in a new issue