Small cleanups to august (#119950)

This commit is contained in:
J. Nick Koston 2024-06-19 09:21:04 -05:00 committed by GitHub
parent 52bc006a72
commit 87e52bb660
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 46 additions and 107 deletions

View File

@ -11,7 +11,7 @@ from yalexs.manager.exceptions import CannotConnect, InvalidAuth, RequireValidat
from yalexs.manager.gateway import Config as YaleXSConfig
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
@ -29,13 +29,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
session = async_create_august_clientsession(hass)
august_gateway = AugustGateway(Path(hass.config.config_dir), session)
try:
return await async_setup_august(hass, entry, august_gateway)
await async_setup_august(hass, entry, august_gateway)
except (RequireValidation, InvalidAuth) as err:
raise ConfigEntryAuthFailed from err
except TimeoutError as err:
raise ConfigEntryNotReady("Timed out connecting to august api") from err
except (AugustApiAIOHTTPError, ClientResponseError, CannotConnect) as err:
raise ConfigEntryNotReady from err
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> bool:
@ -45,32 +47,19 @@ async def async_unload_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> b
async def async_setup_august(
hass: HomeAssistant, entry: AugustConfigEntry, august_gateway: AugustGateway
) -> bool:
) -> None:
"""Set up the August component."""
config = cast(YaleXSConfig, entry.data)
await august_gateway.async_setup(config)
if CONF_PASSWORD in entry.data:
# We no longer need to store passwords since we do not
# support YAML anymore
config_data = entry.data.copy()
del config_data[CONF_PASSWORD]
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 = entry.runtime_data = AugustData(hass, entry, august_gateway)
data = entry.runtime_data = AugustData(hass, 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(entry, PLATFORMS)
return True
async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: AugustConfigEntry, device_entry: dr.DeviceEntry

View File

@ -5,6 +5,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime, timedelta
from functools import partial
import logging
from yalexs.activity import (
@ -51,28 +52,14 @@ def _retrieve_online_state(
return detail.bridge_is_online
def _retrieve_motion_state(data: AugustData, detail: DoorbellDetail) -> bool:
assert data.activity_stream is not None
latest = data.activity_stream.get_latest_device_activity(
detail.device_id, {ActivityType.DOORBELL_MOTION}
)
if latest is None:
return False
return _activity_time_based_state(latest)
_IMAGE_ACTIVITIES = {ActivityType.DOORBELL_IMAGE_CAPTURE}
def _retrieve_image_capture_state(data: AugustData, detail: DoorbellDetail) -> bool:
def _retrieve_time_based_state(
activities: set[ActivityType], data: AugustData, detail: DoorbellDetail
) -> bool:
"""Get the latest state of the sensor."""
stream = data.activity_stream
assert stream is not None
latest = stream.get_latest_device_activity(detail.device_id, _IMAGE_ACTIVITIES)
if latest is None:
return False
if latest := stream.get_latest_device_activity(detail.device_id, activities):
return _activity_time_based_state(latest)
return False
_RING_ACTIVITIES = {ActivityType.DOORBELL_DING}
@ -80,7 +67,6 @@ _RING_ACTIVITIES = {ActivityType.DOORBELL_DING}
def _retrieve_ding_state(data: AugustData, detail: DoorbellDetail | LockDetail) -> bool:
stream = data.activity_stream
assert stream is not None
latest = stream.get_latest_device_activity(detail.device_id, _RING_ACTIVITIES)
if latest is None or (
data.push_updates_connected and latest.action == ACTION_DOORBELL_CALL_MISSED
@ -118,13 +104,15 @@ SENSOR_TYPES_VIDEO_DOORBELL = (
AugustDoorbellBinarySensorEntityDescription(
key="motion",
device_class=BinarySensorDeviceClass.MOTION,
value_fn=_retrieve_motion_state,
value_fn=partial(_retrieve_time_based_state, {ActivityType.DOORBELL_MOTION}),
is_time_based=True,
),
AugustDoorbellBinarySensorEntityDescription(
key="image capture",
translation_key="image_capture",
value_fn=_retrieve_image_capture_state,
value_fn=partial(
_retrieve_time_based_state, {ActivityType.DOORBELL_IMAGE_CAPTURE}
),
is_time_based=True,
),
AugustDoorbellBinarySensorEntityDescription(
@ -185,22 +173,13 @@ class AugustDoorBinarySensor(AugustDescriptionEntity, BinarySensorEntity):
@callback
def _update_from_data(self) -> None:
"""Get the latest state of the sensor and update activity."""
assert self._data.activity_stream is not None
door_activity = self._data.activity_stream.get_latest_device_activity(
self._device_id, {ActivityType.DOOR_OPERATION}
)
if door_activity is not None:
if door_activity := self._get_latest({ActivityType.DOOR_OPERATION}):
update_lock_detail_from_activity(self._detail, door_activity)
# If the source is pubnub the lock must be online since its a live update
if door_activity.source == SOURCE_PUBNUB:
self._detail.set_online(True)
bridge_activity = self._data.activity_stream.get_latest_device_activity(
self._device_id, {ActivityType.BRIDGE_OPERATION}
)
if bridge_activity is not None:
if bridge_activity := self._get_latest({ActivityType.BRIDGE_OPERATION}):
update_lock_detail_from_activity(self._detail, bridge_activity)
self._attr_available = self._detail.bridge_is_online
self._attr_is_on = self._detail.door_state == LockDoorStatus.OPEN

View File

@ -54,17 +54,13 @@ class AugustCamera(AugustEntityMixin, Camera):
super().__init__(data, device, "camera")
self._timeout = timeout
self._session = session
self._attr_model = self._detail.model
@property
def is_recording(self) -> bool:
"""Return true if the device is recording."""
return self._device.has_subscription
@property
def model(self) -> str | None:
"""Return the camera model."""
return self._detail.model
async def _async_update(self):
"""Update device."""
_LOGGER.debug("async_update called %s", self._detail.device_name)
@ -74,11 +70,9 @@ class AugustCamera(AugustEntityMixin, Camera):
@callback
def _update_from_data(self) -> None:
"""Get the latest state of the sensor."""
doorbell_activity = self._data.activity_stream.get_latest_device_activity(
self._device_id,
{ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE},
)
if doorbell_activity is not None:
if doorbell_activity := self._get_latest(
{ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE}
):
update_doorbell_image_from_activity(self._detail, doorbell_activity)
async def async_camera_image(

View File

@ -6,7 +6,7 @@ from yalexs.lock import LockDetail
from yalexs.manager.data import YaleXSData
from yalexs_ble import YaleXSBLEDiscovery
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import discovery_flow
@ -41,12 +41,7 @@ def _async_trigger_ble_lock_discovery(
class AugustData(YaleXSData):
"""August data object."""
def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
august_gateway: AugustGateway,
) -> None:
def __init__(self, hass: HomeAssistant, august_gateway: AugustGateway) -> None:
"""Init August data object."""
self._hass = hass
super().__init__(august_gateway, HomeAssistantError)

View File

@ -2,6 +2,7 @@
from abc import abstractmethod
from yalexs.activity import Activity, ActivityType
from yalexs.doorbell import Doorbell, DoorbellDetail
from yalexs.keypad import KeypadDetail
from yalexs.lock import Lock, LockDetail
@ -31,8 +32,10 @@ class AugustEntityMixin(Entity):
"""Initialize an August device."""
super().__init__()
self._data = data
self._stream = data.activity_stream
self._device = device
detail = self._detail
self._device_id = device.device_id
self._attr_unique_id = f"{device.device_id}_{unique_id}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_id)},
@ -46,10 +49,6 @@ class AugustEntityMixin(Entity):
if isinstance(detail, LockDetail) and (mac := detail.mac_address):
self._attr_device_info[ATTR_CONNECTIONS] = {(dr.CONNECTION_BLUETOOTH, mac)}
@property
def _device_id(self) -> str:
return self._device.device_id
@property
def _detail(self) -> DoorbellDetail | LockDetail:
return self._data.get_device_detail(self._device.device_id)
@ -59,6 +58,11 @@ class AugustEntityMixin(Entity):
"""Check if the lock has a paired hyper bridge."""
return bool(self._detail.bridge and self._detail.bridge.hyper_bridge)
@callback
def _get_latest(self, activity_types: set[ActivityType]) -> Activity | None:
"""Get the latest activity for the device."""
return self._stream.get_latest_device_activity(self._device_id, activity_types)
@callback
def _update_from_data_and_write_state(self) -> None:
self._update_from_data()
@ -76,7 +80,7 @@ class AugustEntityMixin(Entity):
)
)
self.async_on_remove(
self._data.activity_stream.async_subscribe_device_id(
self._stream.async_subscribe_device_id(
self._device_id, self._update_from_data_and_write_state
)
)

View File

@ -50,7 +50,6 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
async def async_lock(self, **kwargs: Any) -> None:
"""Lock the device."""
assert self._data.activity_stream is not None
if self._data.push_updates_connected:
await self._data.async_lock_async(self._device_id, self._hyper_bridge)
return
@ -58,7 +57,6 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
async def async_open(self, **kwargs: Any) -> None:
"""Open/unlatch the device."""
assert self._data.activity_stream is not None
if self._data.push_updates_connected:
await self._data.async_unlatch_async(self._device_id, self._hyper_bridge)
return
@ -66,7 +64,6 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the device."""
assert self._data.activity_stream is not None
if self._data.push_updates_connected:
await self._data.async_unlock_async(self._device_id, self._hyper_bridge)
return
@ -105,33 +102,22 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
@callback
def _update_from_data(self) -> None:
"""Get the latest state of the sensor and update activity."""
activity_stream = self._data.activity_stream
device_id = self._device_id
if lock_activity := activity_stream.get_latest_device_activity(
device_id,
{ActivityType.LOCK_OPERATION},
):
detail = self._detail
if lock_activity := self._get_latest({ActivityType.LOCK_OPERATION}):
self._attr_changed_by = lock_activity.operated_by
lock_activity_without_operator = activity_stream.get_latest_device_activity(
device_id,
{ActivityType.LOCK_OPERATION_WITHOUT_OPERATOR},
lock_activity_without_operator = self._get_latest(
{ActivityType.LOCK_OPERATION_WITHOUT_OPERATOR}
)
if latest_activity := get_latest_activity(
lock_activity_without_operator, lock_activity
):
if latest_activity.source == SOURCE_PUBNUB:
# If the source is pubnub the lock must be online since its a live update
self._detail.set_online(True)
update_lock_detail_from_activity(self._detail, latest_activity)
update_lock_detail_from_activity(detail, latest_activity)
bridge_activity = self._data.activity_stream.get_latest_device_activity(
self._device_id, {ActivityType.BRIDGE_OPERATION}
)
if bridge_activity is not None:
update_lock_detail_from_activity(self._detail, bridge_activity)
if bridge_activity := self._get_latest({ActivityType.BRIDGE_OPERATION}):
update_lock_detail_from_activity(detail, bridge_activity)
self._update_lock_status_from_detail()
lock_status = self._lock_status
@ -139,20 +125,16 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
self._attr_is_locked = None
else:
self._attr_is_locked = lock_status is LockStatus.LOCKED
self._attr_is_jammed = lock_status is LockStatus.JAMMED
self._attr_is_locking = lock_status is LockStatus.LOCKING
self._attr_is_unlocking = lock_status in (
LockStatus.UNLOCKING,
LockStatus.UNLATCHING,
)
self._attr_extra_state_attributes = {
ATTR_BATTERY_LEVEL: self._detail.battery_level
}
if self._detail.keypad is not None:
self._attr_extra_state_attributes = {ATTR_BATTERY_LEVEL: detail.battery_level}
if keypad := detail.keypad:
self._attr_extra_state_attributes["keypad_battery_level"] = (
self._detail.keypad.battery_level
keypad.battery_level
)
async def async_added_to_hass(self) -> None:

View File

@ -127,12 +127,8 @@ class AugustOperatorSensor(AugustEntityMixin, RestoreSensor):
@callback
def _update_from_data(self) -> None:
"""Get the latest state of the sensor and update activity."""
lock_activity = self._data.activity_stream.get_latest_device_activity(
self._device_id, {ActivityType.LOCK_OPERATION}
)
self._attr_available = True
if lock_activity is not None:
if lock_activity := self._get_latest({ActivityType.LOCK_OPERATION}):
lock_activity = cast(LockOperationActivity, lock_activity)
self._attr_native_value = lock_activity.operated_by
self._operated_remote = lock_activity.operated_remote