1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00

Update offline keys from august cloud for august branded yale locks (#76577)

This commit is contained in:
J. Nick Koston 2022-08-10 16:21:41 -10:00 committed by GitHub
parent 4c70129427
commit bf899101ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 194 additions and 38 deletions

View File

@ -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()

View File

@ -24,5 +24,6 @@
],
"config_flow": true,
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"]
"loggers": ["pubnub", "yalexs"],
"after_dependencies": ["yalexs_ble"]
}

View File

@ -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]

View File

@ -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": [
{

View File

@ -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": [
{

View File

@ -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,

View File

@ -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": [
{

View 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
}
}
}

View File

@ -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")

View File

@ -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(