mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Migrate august to use yalexs 6.0.0 (#119321)
This commit is contained in:
parent
def9d5b101
commit
8855289d9c
@ -2,54 +2,25 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable, Coroutine, Iterable, ValuesView
|
||||
from datetime import datetime
|
||||
from itertools import chain
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
from typing import cast
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
from aiohttp import ClientResponseError
|
||||
from path import Path
|
||||
from yalexs.activity import ActivityTypes
|
||||
from yalexs.const import DEFAULT_BRAND
|
||||
from yalexs.doorbell import Doorbell, DoorbellDetail
|
||||
from yalexs.exceptions import AugustApiAIOHTTPError
|
||||
from yalexs.lock import Lock, LockDetail
|
||||
from yalexs.manager.activity import ActivityStream
|
||||
from yalexs.manager.const import CONF_BRAND
|
||||
from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidation
|
||||
from yalexs.manager.gateway import Config as YaleXSConfig
|
||||
from yalexs.manager.subscriber import SubscriberMixin
|
||||
from yalexs.pubnub_activity import activities_from_pubnub_message
|
||||
from yalexs.pubnub_async import AugustPubNub, async_create_pubnub
|
||||
from yalexs_ble import YaleXSBLEDiscovery
|
||||
|
||||
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
HomeAssistantError,
|
||||
)
|
||||
from homeassistant.helpers import device_registry as dr, discovery_flow
|
||||
from homeassistant.util.async_ import create_eager_task
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import DOMAIN, MIN_TIME_BETWEEN_DETAIL_UPDATES, PLATFORMS
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
from .data import AugustData
|
||||
from .gateway import AugustGateway
|
||||
from .util import async_create_august_clientsession
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
API_CACHED_ATTRS = {
|
||||
"door_state",
|
||||
"door_state_datetime",
|
||||
"lock_status",
|
||||
"lock_status_datetime",
|
||||
}
|
||||
YALEXS_BLE_DOMAIN = "yalexs_ble"
|
||||
|
||||
type AugustConfigEntry = ConfigEntry[AugustData]
|
||||
|
||||
|
||||
@ -73,437 +44,34 @@ async def async_unload_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> b
|
||||
|
||||
|
||||
async def async_setup_august(
|
||||
hass: HomeAssistant, config_entry: AugustConfigEntry, august_gateway: AugustGateway
|
||||
hass: HomeAssistant, entry: AugustConfigEntry, august_gateway: AugustGateway
|
||||
) -> bool:
|
||||
"""Set up the August component."""
|
||||
config = cast(YaleXSConfig, config_entry.data)
|
||||
config = cast(YaleXSConfig, entry.data)
|
||||
await august_gateway.async_setup(config)
|
||||
|
||||
if CONF_PASSWORD in config_entry.data:
|
||||
if CONF_PASSWORD in entry.data:
|
||||
# We no longer need to store passwords since we do not
|
||||
# support YAML anymore
|
||||
config_data = config_entry.data.copy()
|
||||
config_data = entry.data.copy()
|
||||
del config_data[CONF_PASSWORD]
|
||||
hass.config_entries.async_update_entry(config_entry, data=config_data)
|
||||
hass.config_entries.async_update_entry(entry, data=config_data)
|
||||
|
||||
await august_gateway.async_authenticate()
|
||||
await august_gateway.async_refresh_access_token_if_needed()
|
||||
|
||||
data = config_entry.runtime_data = AugustData(hass, config_entry, august_gateway)
|
||||
data = entry.runtime_data = AugustData(hass, entry, august_gateway)
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, data.async_stop)
|
||||
)
|
||||
entry.async_on_unload(data.async_stop)
|
||||
await data.async_setup()
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
def _async_trigger_ble_lock_discovery(
|
||||
hass: HomeAssistant, locks_with_offline_keys: list[LockDetail]
|
||||
) -> None:
|
||||
"""Update keys for the yalexs-ble integration if available."""
|
||||
for lock_detail in locks_with_offline_keys:
|
||||
discovery_flow.async_create_flow(
|
||||
hass,
|
||||
YALEXS_BLE_DOMAIN,
|
||||
context={"source": SOURCE_INTEGRATION_DISCOVERY},
|
||||
data=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(SubscriberMixin):
|
||||
"""August data object."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
august_gateway: AugustGateway,
|
||||
) -> None:
|
||||
"""Init August data object."""
|
||||
super().__init__(MIN_TIME_BETWEEN_DETAIL_UPDATES)
|
||||
self._config_entry = config_entry
|
||||
self._hass = hass
|
||||
self._august_gateway = august_gateway
|
||||
self.activity_stream: ActivityStream = None
|
||||
self._api = august_gateway.api
|
||||
self._device_detail_by_id: dict[str, LockDetail | DoorbellDetail] = {}
|
||||
self._doorbells_by_id: dict[str, Doorbell] = {}
|
||||
self._locks_by_id: dict[str, Lock] = {}
|
||||
self._house_ids: set[str] = set()
|
||||
self._pubnub_unsub: Callable[[], Coroutine[Any, Any, None]] | None = None
|
||||
|
||||
@property
|
||||
def brand(self) -> str:
|
||||
"""Brand of the device."""
|
||||
return self._config_entry.data.get(CONF_BRAND, DEFAULT_BRAND)
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
"""Async setup of august device data and activities."""
|
||||
token = self._august_gateway.access_token
|
||||
# This used to be a gather but it was less reliable with august's recent api changes.
|
||||
user_data = await self._api.async_get_user(token)
|
||||
locks: list[Lock] = await self._api.async_get_operable_locks(token) or []
|
||||
doorbells: list[Doorbell] = await self._api.async_get_doorbells(token) or []
|
||||
self._doorbells_by_id = {device.device_id: device for device in doorbells}
|
||||
self._locks_by_id = {device.device_id: device for device in locks}
|
||||
self._house_ids = {device.house_id for device in chain(locks, doorbells)}
|
||||
|
||||
await self._async_refresh_device_detail_by_ids(
|
||||
[device.device_id for device in chain(locks, doorbells)]
|
||||
)
|
||||
|
||||
# We remove all devices that we are missing
|
||||
# detail as we cannot determine if they are usable.
|
||||
# This also allows us to avoid checking for
|
||||
# detail being None all over the place
|
||||
self._remove_inoperative_locks()
|
||||
self._remove_inoperative_doorbells()
|
||||
|
||||
pubnub = AugustPubNub()
|
||||
for device in self._device_detail_by_id.values():
|
||||
pubnub.register_device(device)
|
||||
|
||||
self.activity_stream = ActivityStream(
|
||||
self._api, self._august_gateway, self._house_ids, pubnub
|
||||
)
|
||||
self._config_entry.async_on_unload(
|
||||
self._hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, self.async_stop)
|
||||
)
|
||||
self._config_entry.async_on_unload(self.async_stop)
|
||||
await self.activity_stream.async_setup()
|
||||
|
||||
pubnub.subscribe(self.async_pubnub_message)
|
||||
self._pubnub_unsub = async_create_pubnub(
|
||||
user_data["UserID"],
|
||||
pubnub,
|
||||
self.brand,
|
||||
)
|
||||
|
||||
if self._locks_by_id:
|
||||
# Do not prevent setup as the sync can timeout
|
||||
# but it is not a fatal error as the lock
|
||||
# will recover automatically when it comes back online.
|
||||
self._config_entry.async_create_background_task(
|
||||
self._hass, self._async_initial_sync(), "august-initial-sync"
|
||||
)
|
||||
|
||||
async def _async_initial_sync(self) -> None:
|
||||
"""Attempt to request an initial sync."""
|
||||
# We don't care if this fails because we only want to wake
|
||||
# locks that are actually online anyways and they will be
|
||||
# awake when they come back online
|
||||
for result in await asyncio.gather(
|
||||
*[
|
||||
create_eager_task(
|
||||
self.async_status_async(
|
||||
device_id, bool(detail.bridge and detail.bridge.hyper_bridge)
|
||||
)
|
||||
)
|
||||
for device_id, detail in self._device_detail_by_id.items()
|
||||
if device_id in self._locks_by_id
|
||||
],
|
||||
return_exceptions=True,
|
||||
):
|
||||
if isinstance(result, Exception) and not isinstance(
|
||||
result, (TimeoutError, ClientResponseError, CannotConnect)
|
||||
):
|
||||
_LOGGER.warning(
|
||||
"Unexpected exception during initial sync: %s",
|
||||
result,
|
||||
exc_info=result,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_pubnub_message(
|
||||
self, device_id: str, date_time: datetime, message: dict[str, Any]
|
||||
) -> None:
|
||||
"""Process a pubnub message."""
|
||||
device = self.get_device_detail(device_id)
|
||||
activities = activities_from_pubnub_message(device, date_time, message)
|
||||
activity_stream = self.activity_stream
|
||||
if activities and activity_stream.async_process_newer_device_activities(
|
||||
activities
|
||||
):
|
||||
self.async_signal_device_id_update(device.device_id)
|
||||
activity_stream.async_schedule_house_id_refresh(device.house_id)
|
||||
|
||||
async def async_stop(self, event: Event | None = None) -> None:
|
||||
"""Stop the subscriptions."""
|
||||
if self._pubnub_unsub:
|
||||
await self._pubnub_unsub()
|
||||
self.activity_stream.async_stop()
|
||||
|
||||
@property
|
||||
def doorbells(self) -> ValuesView[Doorbell]:
|
||||
"""Return a list of py-august Doorbell objects."""
|
||||
return self._doorbells_by_id.values()
|
||||
|
||||
@property
|
||||
def locks(self) -> ValuesView[Lock]:
|
||||
"""Return a list of py-august Lock objects."""
|
||||
return self._locks_by_id.values()
|
||||
|
||||
def get_device_detail(self, device_id: str) -> DoorbellDetail | LockDetail:
|
||||
"""Return the py-august LockDetail or DoorbellDetail object for a device."""
|
||||
return self._device_detail_by_id[device_id]
|
||||
|
||||
async def _async_refresh(self, time: datetime) -> None:
|
||||
await self._async_refresh_device_detail_by_ids(self._subscriptions.keys())
|
||||
|
||||
async def _async_refresh_device_detail_by_ids(
|
||||
self, device_ids_list: Iterable[str]
|
||||
) -> None:
|
||||
"""Refresh each device in sequence.
|
||||
|
||||
This used to be a gather but it was less reliable with august's
|
||||
recent api changes.
|
||||
|
||||
The august api has been timing out for some devices so
|
||||
we want the ones that it isn't timing out for to keep working.
|
||||
"""
|
||||
for device_id in device_ids_list:
|
||||
try:
|
||||
await self._async_refresh_device_detail_by_id(device_id)
|
||||
except TimeoutError:
|
||||
_LOGGER.warning(
|
||||
"Timed out calling august api during refresh of device: %s",
|
||||
device_id,
|
||||
)
|
||||
except (ClientResponseError, CannotConnect) as err:
|
||||
_LOGGER.warning(
|
||||
"Error from august api during refresh of device: %s",
|
||||
device_id,
|
||||
exc_info=err,
|
||||
)
|
||||
|
||||
async def refresh_camera_by_id(self, device_id: str) -> None:
|
||||
"""Re-fetch doorbell/camera data from API."""
|
||||
await self._async_update_device_detail(
|
||||
self._doorbells_by_id[device_id],
|
||||
self._api.async_get_doorbell_detail,
|
||||
)
|
||||
|
||||
async def _async_refresh_device_detail_by_id(self, device_id: str) -> None:
|
||||
if device_id in self._locks_by_id:
|
||||
if self.activity_stream and self.activity_stream.pubnub.connected:
|
||||
saved_attrs = _save_live_attrs(self._device_detail_by_id[device_id])
|
||||
await self._async_update_device_detail(
|
||||
self._locks_by_id[device_id], self._api.async_get_lock_detail
|
||||
)
|
||||
if self.activity_stream and self.activity_stream.pubnub.connected:
|
||||
_restore_live_attrs(self._device_detail_by_id[device_id], saved_attrs)
|
||||
# keypads are always attached to locks
|
||||
if (
|
||||
device_id in self._device_detail_by_id
|
||||
and self._device_detail_by_id[device_id].keypad is not None
|
||||
):
|
||||
keypad = self._device_detail_by_id[device_id].keypad
|
||||
self._device_detail_by_id[keypad.device_id] = keypad
|
||||
elif device_id in self._doorbells_by_id:
|
||||
await self._async_update_device_detail(
|
||||
self._doorbells_by_id[device_id],
|
||||
self._api.async_get_doorbell_detail,
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"async_signal_device_id_update (from detail updates): %s", device_id
|
||||
)
|
||||
self.async_signal_device_id_update(device_id)
|
||||
|
||||
async def _async_update_device_detail(
|
||||
self,
|
||||
device: Doorbell | Lock,
|
||||
api_call: Callable[
|
||||
[str, str], Coroutine[Any, Any, DoorbellDetail | LockDetail]
|
||||
],
|
||||
) -> None:
|
||||
device_id = device.device_id
|
||||
device_name = device.device_name
|
||||
_LOGGER.debug("Started retrieving detail for %s (%s)", device_name, device_id)
|
||||
|
||||
try:
|
||||
detail = await api_call(self._august_gateway.access_token, device_id)
|
||||
except ClientError as ex:
|
||||
_LOGGER.error(
|
||||
"Request error trying to retrieve %s details for %s. %s",
|
||||
device_id,
|
||||
device_name,
|
||||
ex,
|
||||
)
|
||||
_LOGGER.debug("Completed retrieving detail for %s (%s)", device_name, device_id)
|
||||
# If the key changes after startup we need to trigger a
|
||||
# discovery to keep it up to date
|
||||
if isinstance(detail, LockDetail) and detail.offline_key:
|
||||
_async_trigger_ble_lock_discovery(self._hass, [detail])
|
||||
|
||||
self._device_detail_by_id[device_id] = detail
|
||||
|
||||
def get_device(self, device_id: str) -> Doorbell | Lock | None:
|
||||
"""Get a device by id."""
|
||||
return self._locks_by_id.get(device_id) or self._doorbells_by_id.get(device_id)
|
||||
|
||||
def _get_device_name(self, device_id: str) -> str | None:
|
||||
"""Return doorbell or lock name as August has it stored."""
|
||||
if device := self.get_device(device_id):
|
||||
return device.device_name
|
||||
return None
|
||||
|
||||
async def async_lock(self, device_id: str) -> list[ActivityTypes]:
|
||||
"""Lock the device."""
|
||||
return await self._async_call_api_op_requires_bridge(
|
||||
device_id,
|
||||
self._api.async_lock_return_activities,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
)
|
||||
|
||||
async def async_status_async(self, device_id: str, hyper_bridge: bool) -> str:
|
||||
"""Request status of 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_status_async,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
hyper_bridge,
|
||||
)
|
||||
|
||||
async def async_lock_async(self, device_id: str, hyper_bridge: bool) -> str:
|
||||
"""Lock 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_lock_async,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
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(
|
||||
device_id,
|
||||
self._api.async_unlock_return_activities,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
)
|
||||
|
||||
async def async_unlock_async(self, device_id: str, hyper_bridge: bool) -> str:
|
||||
"""Unlock 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_unlock_async,
|
||||
self._august_gateway.access_token,
|
||||
device_id,
|
||||
hyper_bridge,
|
||||
)
|
||||
|
||||
async def _async_call_api_op_requires_bridge[**_P, _R](
|
||||
self,
|
||||
device_id: str,
|
||||
func: Callable[_P, Coroutine[Any, Any, _R]],
|
||||
*args: _P.args,
|
||||
**kwargs: _P.kwargs,
|
||||
) -> _R:
|
||||
"""Call an API that requires the bridge to be online and will change the device state."""
|
||||
try:
|
||||
ret = await func(*args, **kwargs)
|
||||
except AugustApiAIOHTTPError as err:
|
||||
device_name = self._get_device_name(device_id)
|
||||
if device_name is None:
|
||||
device_name = f"DeviceID: {device_id}"
|
||||
raise HomeAssistantError(f"{device_name}: {err}") from err
|
||||
|
||||
return ret
|
||||
|
||||
def _remove_inoperative_doorbells(self) -> None:
|
||||
for doorbell in list(self.doorbells):
|
||||
device_id = doorbell.device_id
|
||||
if self._device_detail_by_id.get(device_id):
|
||||
continue
|
||||
_LOGGER.info(
|
||||
(
|
||||
"The doorbell %s could not be setup because the system could not"
|
||||
" fetch details about the doorbell"
|
||||
),
|
||||
doorbell.device_name,
|
||||
)
|
||||
del self._doorbells_by_id[device_id]
|
||||
|
||||
def _remove_inoperative_locks(self) -> None:
|
||||
# Remove non-operative locks as there must
|
||||
# be a bridge (August Connect) for them to
|
||||
# be usable
|
||||
for lock in list(self.locks):
|
||||
device_id = lock.device_id
|
||||
lock_detail = self._device_detail_by_id.get(device_id)
|
||||
if lock_detail is None:
|
||||
_LOGGER.info(
|
||||
(
|
||||
"The lock %s could not be setup because the system could not"
|
||||
" fetch details about the lock"
|
||||
),
|
||||
lock.device_name,
|
||||
)
|
||||
elif lock_detail.bridge is None:
|
||||
_LOGGER.info(
|
||||
(
|
||||
"The lock %s could not be setup because it does not have a"
|
||||
" bridge (Connect)"
|
||||
),
|
||||
lock.device_name,
|
||||
)
|
||||
del self._device_detail_by_id[device_id]
|
||||
# Bridge may come back online later so we still add the device since we will
|
||||
# have a pubnub subscription to tell use when it recovers
|
||||
else:
|
||||
continue
|
||||
del self._locks_by_id[device_id]
|
||||
|
||||
|
||||
def _save_live_attrs(lock_detail: DoorbellDetail | LockDetail) -> dict[str, Any]:
|
||||
"""Store the attributes that the lock detail api may have an invalid cache for.
|
||||
|
||||
Since we are connected to pubnub we may have more current data
|
||||
then the api so we want to restore the most current data after
|
||||
updating battery state etc.
|
||||
"""
|
||||
return {attr: getattr(lock_detail, attr) for attr in API_CACHED_ATTRS}
|
||||
|
||||
|
||||
def _restore_live_attrs(
|
||||
lock_detail: DoorbellDetail | LockDetail, attrs: dict[str, Any]
|
||||
) -> None:
|
||||
"""Restore the non-cache attributes after a cached update."""
|
||||
for attr, value in attrs.items():
|
||||
setattr(lock_detail, attr, value)
|
||||
|
||||
|
||||
async def async_remove_config_entry_device(
|
||||
hass: HomeAssistant, config_entry: AugustConfigEntry, device_entry: dr.DeviceEntry
|
||||
) -> bool:
|
||||
|
@ -15,6 +15,7 @@ from yalexs.activity import (
|
||||
)
|
||||
from yalexs.doorbell import Doorbell, DoorbellDetail
|
||||
from yalexs.lock import Lock, LockDetail, LockDoorStatus
|
||||
from yalexs.manager.const import ACTIVITY_UPDATE_INTERVAL
|
||||
from yalexs.util import update_lock_detail_from_activity
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
@ -28,7 +29,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from . import AugustConfigEntry, AugustData
|
||||
from .const import ACTIVITY_UPDATE_INTERVAL
|
||||
from .entity import AugustEntityMixin
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -1,7 +1,5 @@
|
||||
"""Constants for August devices."""
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DEFAULT_TIMEOUT = 25
|
||||
@ -37,15 +35,6 @@ ATTR_OPERATION_KEYPAD = "keypad"
|
||||
ATTR_OPERATION_MANUAL = "manual"
|
||||
ATTR_OPERATION_TAG = "tag"
|
||||
|
||||
# Limit battery, online, and hardware updates to hourly
|
||||
# in order to reduce the number of api requests and
|
||||
# avoid hitting rate limits
|
||||
MIN_TIME_BETWEEN_DETAIL_UPDATES = timedelta(hours=24)
|
||||
|
||||
# Activity needs to be checked more frequently as the
|
||||
# doorbell motion and rings are included here
|
||||
ACTIVITY_UPDATE_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
LOGIN_METHODS = ["phone", "email"]
|
||||
DEFAULT_LOGIN_METHOD = "email"
|
||||
|
||||
|
65
homeassistant/components/august/data.py
Normal file
65
homeassistant/components/august/data.py
Normal file
@ -0,0 +1,65 @@
|
||||
"""Support for August devices."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from yalexs.const import DEFAULT_BRAND
|
||||
from yalexs.lock import LockDetail
|
||||
from yalexs.manager.const import CONF_BRAND
|
||||
from yalexs.manager.data import YaleXSData
|
||||
from yalexs_ble import YaleXSBLEDiscovery
|
||||
|
||||
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import discovery_flow
|
||||
|
||||
from .gateway import AugustGateway
|
||||
|
||||
YALEXS_BLE_DOMAIN = "yalexs_ble"
|
||||
|
||||
|
||||
@callback
|
||||
def _async_trigger_ble_lock_discovery(
|
||||
hass: HomeAssistant, locks_with_offline_keys: list[LockDetail]
|
||||
) -> None:
|
||||
"""Update keys for the yalexs-ble integration if available."""
|
||||
for lock_detail in locks_with_offline_keys:
|
||||
discovery_flow.async_create_flow(
|
||||
hass,
|
||||
YALEXS_BLE_DOMAIN,
|
||||
context={"source": SOURCE_INTEGRATION_DISCOVERY},
|
||||
data=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(YaleXSData):
|
||||
"""August data object."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
august_gateway: AugustGateway,
|
||||
) -> None:
|
||||
"""Init August data object."""
|
||||
self._hass = hass
|
||||
self._config_entry = config_entry
|
||||
super().__init__(august_gateway, HomeAssistantError)
|
||||
|
||||
@property
|
||||
def brand(self) -> str:
|
||||
"""Brand of the device."""
|
||||
return self._config_entry.data.get(CONF_BRAND, DEFAULT_BRAND)
|
||||
|
||||
@callback
|
||||
def async_offline_key_discovered(self, detail: LockDetail) -> None:
|
||||
"""Handle offline key discovery."""
|
||||
_async_trigger_ble_lock_discovery(self._hass, [detail])
|
@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==5.2.0", "yalexs-ble==2.4.2"]
|
||||
"requirements": ["yalexs==6.0.0", "yalexs-ble==2.4.2"]
|
||||
}
|
||||
|
@ -2933,7 +2933,7 @@ yalesmartalarmclient==0.3.9
|
||||
yalexs-ble==2.4.2
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs==5.2.0
|
||||
yalexs==6.0.0
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.14
|
||||
|
@ -2292,7 +2292,7 @@ yalesmartalarmclient==0.3.9
|
||||
yalexs-ble==2.4.2
|
||||
|
||||
# homeassistant.components.august
|
||||
yalexs==5.2.0
|
||||
yalexs==6.0.0
|
||||
|
||||
# homeassistant.components.yeelight
|
||||
yeelight==0.7.14
|
||||
|
@ -9,6 +9,6 @@ import pytest
|
||||
def mock_discovery_fixture():
|
||||
"""Mock discovery to avoid loading the whole bluetooth stack."""
|
||||
with patch(
|
||||
"homeassistant.components.august.discovery_flow.async_create_flow"
|
||||
"homeassistant.components.august.data.discovery_flow.async_create_flow"
|
||||
) as mock_discovery:
|
||||
yield mock_discovery
|
||||
|
@ -78,10 +78,10 @@ async def _mock_setup_august(
|
||||
entry.add_to_hass(hass)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.august.async_create_pubnub",
|
||||
"yalexs.manager.data.async_create_pubnub",
|
||||
return_value=AsyncMock(),
|
||||
),
|
||||
patch("homeassistant.components.august.AugustPubNub", return_value=pubnub_mock),
|
||||
patch("yalexs.manager.data.AugustPubNub", return_value=pubnub_mock),
|
||||
):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
Loading…
x
Reference in New Issue
Block a user