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 yalexs.manager.gateway import Config as YaleXSConfig
from homeassistant.config_entries import ConfigEntry 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.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr 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) session = async_create_august_clientsession(hass)
august_gateway = AugustGateway(Path(hass.config.config_dir), session) august_gateway = AugustGateway(Path(hass.config.config_dir), session)
try: try:
return await async_setup_august(hass, entry, august_gateway) await async_setup_august(hass, entry, august_gateway)
except (RequireValidation, InvalidAuth) as err: except (RequireValidation, InvalidAuth) as err:
raise ConfigEntryAuthFailed from err raise ConfigEntryAuthFailed from err
except TimeoutError as err: except TimeoutError as err:
raise ConfigEntryNotReady("Timed out connecting to august api") from err raise ConfigEntryNotReady("Timed out connecting to august api") from err
except (AugustApiAIOHTTPError, ClientResponseError, CannotConnect) as err: except (AugustApiAIOHTTPError, ClientResponseError, CannotConnect) as err:
raise ConfigEntryNotReady from 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: 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( async def async_setup_august(
hass: HomeAssistant, entry: AugustConfigEntry, august_gateway: AugustGateway hass: HomeAssistant, entry: AugustConfigEntry, august_gateway: AugustGateway
) -> bool: ) -> None:
"""Set up the August component.""" """Set up the August component."""
config = cast(YaleXSConfig, entry.data) config = cast(YaleXSConfig, entry.data)
await august_gateway.async_setup(config) 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_authenticate()
await august_gateway.async_refresh_access_token_if_needed() await august_gateway.async_refresh_access_token_if_needed()
data = entry.runtime_data = AugustData(hass, august_gateway)
data = entry.runtime_data = AugustData(hass, entry, august_gateway)
entry.async_on_unload( entry.async_on_unload(
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, data.async_stop) hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, data.async_stop)
) )
entry.async_on_unload(data.async_stop) entry.async_on_unload(data.async_stop)
await data.async_setup() await data.async_setup()
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_remove_config_entry_device( async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: AugustConfigEntry, device_entry: dr.DeviceEntry 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 collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import partial
import logging import logging
from yalexs.activity import ( from yalexs.activity import (
@ -51,28 +52,14 @@ def _retrieve_online_state(
return detail.bridge_is_online return detail.bridge_is_online
def _retrieve_motion_state(data: AugustData, detail: DoorbellDetail) -> bool: def _retrieve_time_based_state(
assert data.activity_stream is not None activities: set[ActivityType], data: AugustData, detail: DoorbellDetail
latest = data.activity_stream.get_latest_device_activity( ) -> bool:
detail.device_id, {ActivityType.DOORBELL_MOTION} """Get the latest state of the sensor."""
)
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:
stream = data.activity_stream stream = data.activity_stream
assert stream is not None if latest := stream.get_latest_device_activity(detail.device_id, activities):
latest = stream.get_latest_device_activity(detail.device_id, _IMAGE_ACTIVITIES) return _activity_time_based_state(latest)
if latest is None: return False
return False
return _activity_time_based_state(latest)
_RING_ACTIVITIES = {ActivityType.DOORBELL_DING} _RING_ACTIVITIES = {ActivityType.DOORBELL_DING}
@ -80,7 +67,6 @@ _RING_ACTIVITIES = {ActivityType.DOORBELL_DING}
def _retrieve_ding_state(data: AugustData, detail: DoorbellDetail | LockDetail) -> bool: def _retrieve_ding_state(data: AugustData, detail: DoorbellDetail | LockDetail) -> bool:
stream = data.activity_stream stream = data.activity_stream
assert stream is not None
latest = stream.get_latest_device_activity(detail.device_id, _RING_ACTIVITIES) latest = stream.get_latest_device_activity(detail.device_id, _RING_ACTIVITIES)
if latest is None or ( if latest is None or (
data.push_updates_connected and latest.action == ACTION_DOORBELL_CALL_MISSED data.push_updates_connected and latest.action == ACTION_DOORBELL_CALL_MISSED
@ -118,13 +104,15 @@ SENSOR_TYPES_VIDEO_DOORBELL = (
AugustDoorbellBinarySensorEntityDescription( AugustDoorbellBinarySensorEntityDescription(
key="motion", key="motion",
device_class=BinarySensorDeviceClass.MOTION, device_class=BinarySensorDeviceClass.MOTION,
value_fn=_retrieve_motion_state, value_fn=partial(_retrieve_time_based_state, {ActivityType.DOORBELL_MOTION}),
is_time_based=True, is_time_based=True,
), ),
AugustDoorbellBinarySensorEntityDescription( AugustDoorbellBinarySensorEntityDescription(
key="image capture", key="image capture",
translation_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, is_time_based=True,
), ),
AugustDoorbellBinarySensorEntityDescription( AugustDoorbellBinarySensorEntityDescription(
@ -185,22 +173,13 @@ class AugustDoorBinarySensor(AugustDescriptionEntity, BinarySensorEntity):
@callback @callback
def _update_from_data(self) -> None: def _update_from_data(self) -> None:
"""Get the latest state of the sensor and update activity.""" """Get the latest state of the sensor and update activity."""
assert self._data.activity_stream is not None if door_activity := self._get_latest({ActivityType.DOOR_OPERATION}):
door_activity = self._data.activity_stream.get_latest_device_activity(
self._device_id, {ActivityType.DOOR_OPERATION}
)
if door_activity is not None:
update_lock_detail_from_activity(self._detail, door_activity) 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 the source is pubnub the lock must be online since its a live update
if door_activity.source == SOURCE_PUBNUB: if door_activity.source == SOURCE_PUBNUB:
self._detail.set_online(True) self._detail.set_online(True)
bridge_activity = self._data.activity_stream.get_latest_device_activity( if bridge_activity := self._get_latest({ActivityType.BRIDGE_OPERATION}):
self._device_id, {ActivityType.BRIDGE_OPERATION}
)
if bridge_activity is not None:
update_lock_detail_from_activity(self._detail, bridge_activity) update_lock_detail_from_activity(self._detail, bridge_activity)
self._attr_available = self._detail.bridge_is_online self._attr_available = self._detail.bridge_is_online
self._attr_is_on = self._detail.door_state == LockDoorStatus.OPEN 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") super().__init__(data, device, "camera")
self._timeout = timeout self._timeout = timeout
self._session = session self._session = session
self._attr_model = self._detail.model
@property @property
def is_recording(self) -> bool: def is_recording(self) -> bool:
"""Return true if the device is recording.""" """Return true if the device is recording."""
return self._device.has_subscription return self._device.has_subscription
@property
def model(self) -> str | None:
"""Return the camera model."""
return self._detail.model
async def _async_update(self): async def _async_update(self):
"""Update device.""" """Update device."""
_LOGGER.debug("async_update called %s", self._detail.device_name) _LOGGER.debug("async_update called %s", self._detail.device_name)
@ -74,11 +70,9 @@ class AugustCamera(AugustEntityMixin, Camera):
@callback @callback
def _update_from_data(self) -> None: def _update_from_data(self) -> None:
"""Get the latest state of the sensor.""" """Get the latest state of the sensor."""
doorbell_activity = self._data.activity_stream.get_latest_device_activity( if doorbell_activity := self._get_latest(
self._device_id, {ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE}
{ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_IMAGE_CAPTURE}, ):
)
if doorbell_activity is not None:
update_doorbell_image_from_activity(self._detail, doorbell_activity) update_doorbell_image_from_activity(self._detail, doorbell_activity)
async def async_camera_image( async def async_camera_image(

View File

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

View File

@ -2,6 +2,7 @@
from abc import abstractmethod from abc import abstractmethod
from yalexs.activity import Activity, ActivityType
from yalexs.doorbell import Doorbell, DoorbellDetail from yalexs.doorbell import Doorbell, DoorbellDetail
from yalexs.keypad import KeypadDetail from yalexs.keypad import KeypadDetail
from yalexs.lock import Lock, LockDetail from yalexs.lock import Lock, LockDetail
@ -31,8 +32,10 @@ class AugustEntityMixin(Entity):
"""Initialize an August device.""" """Initialize an August device."""
super().__init__() super().__init__()
self._data = data self._data = data
self._stream = data.activity_stream
self._device = device self._device = device
detail = self._detail detail = self._detail
self._device_id = device.device_id
self._attr_unique_id = f"{device.device_id}_{unique_id}" self._attr_unique_id = f"{device.device_id}_{unique_id}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._device_id)}, identifiers={(DOMAIN, self._device_id)},
@ -46,10 +49,6 @@ class AugustEntityMixin(Entity):
if isinstance(detail, LockDetail) and (mac := detail.mac_address): if isinstance(detail, LockDetail) and (mac := detail.mac_address):
self._attr_device_info[ATTR_CONNECTIONS] = {(dr.CONNECTION_BLUETOOTH, mac)} self._attr_device_info[ATTR_CONNECTIONS] = {(dr.CONNECTION_BLUETOOTH, mac)}
@property
def _device_id(self) -> str:
return self._device.device_id
@property @property
def _detail(self) -> DoorbellDetail | LockDetail: def _detail(self) -> DoorbellDetail | LockDetail:
return self._data.get_device_detail(self._device.device_id) 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.""" """Check if the lock has a paired hyper bridge."""
return bool(self._detail.bridge and self._detail.bridge.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 @callback
def _update_from_data_and_write_state(self) -> None: def _update_from_data_and_write_state(self) -> None:
self._update_from_data() self._update_from_data()
@ -76,7 +80,7 @@ class AugustEntityMixin(Entity):
) )
) )
self.async_on_remove( 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 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: async def async_lock(self, **kwargs: Any) -> None:
"""Lock the device.""" """Lock the device."""
assert self._data.activity_stream is not None
if self._data.push_updates_connected: if self._data.push_updates_connected:
await self._data.async_lock_async(self._device_id, self._hyper_bridge) await self._data.async_lock_async(self._device_id, self._hyper_bridge)
return return
@ -58,7 +57,6 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
async def async_open(self, **kwargs: Any) -> None: async def async_open(self, **kwargs: Any) -> None:
"""Open/unlatch the device.""" """Open/unlatch the device."""
assert self._data.activity_stream is not None
if self._data.push_updates_connected: if self._data.push_updates_connected:
await self._data.async_unlatch_async(self._device_id, self._hyper_bridge) await self._data.async_unlatch_async(self._device_id, self._hyper_bridge)
return return
@ -66,7 +64,6 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
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
if self._data.push_updates_connected: if self._data.push_updates_connected:
await self._data.async_unlock_async(self._device_id, self._hyper_bridge) await self._data.async_unlock_async(self._device_id, self._hyper_bridge)
return return
@ -105,33 +102,22 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
@callback @callback
def _update_from_data(self) -> None: def _update_from_data(self) -> None:
"""Get the latest state of the sensor and update activity.""" """Get the latest state of the sensor and update activity."""
activity_stream = self._data.activity_stream detail = self._detail
device_id = self._device_id if lock_activity := self._get_latest({ActivityType.LOCK_OPERATION}):
if lock_activity := activity_stream.get_latest_device_activity(
device_id,
{ActivityType.LOCK_OPERATION},
):
self._attr_changed_by = lock_activity.operated_by self._attr_changed_by = lock_activity.operated_by
lock_activity_without_operator = self._get_latest(
lock_activity_without_operator = activity_stream.get_latest_device_activity( {ActivityType.LOCK_OPERATION_WITHOUT_OPERATOR}
device_id,
{ActivityType.LOCK_OPERATION_WITHOUT_OPERATOR},
) )
if latest_activity := get_latest_activity( if latest_activity := get_latest_activity(
lock_activity_without_operator, lock_activity lock_activity_without_operator, lock_activity
): ):
if latest_activity.source == SOURCE_PUBNUB: if latest_activity.source == SOURCE_PUBNUB:
# If the source is pubnub the lock must be online since its a live update # If the source is pubnub the lock must be online since its a live update
self._detail.set_online(True) 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( if bridge_activity := self._get_latest({ActivityType.BRIDGE_OPERATION}):
self._device_id, {ActivityType.BRIDGE_OPERATION} update_lock_detail_from_activity(detail, bridge_activity)
)
if bridge_activity is not None:
update_lock_detail_from_activity(self._detail, bridge_activity)
self._update_lock_status_from_detail() self._update_lock_status_from_detail()
lock_status = self._lock_status lock_status = self._lock_status
@ -139,20 +125,16 @@ class AugustLock(AugustEntityMixin, RestoreEntity, LockEntity):
self._attr_is_locked = None self._attr_is_locked = None
else: else:
self._attr_is_locked = lock_status is LockStatus.LOCKED self._attr_is_locked = lock_status is LockStatus.LOCKED
self._attr_is_jammed = lock_status is LockStatus.JAMMED self._attr_is_jammed = lock_status is LockStatus.JAMMED
self._attr_is_locking = lock_status is LockStatus.LOCKING self._attr_is_locking = lock_status is LockStatus.LOCKING
self._attr_is_unlocking = lock_status in ( self._attr_is_unlocking = lock_status in (
LockStatus.UNLOCKING, LockStatus.UNLOCKING,
LockStatus.UNLATCHING, LockStatus.UNLATCHING,
) )
self._attr_extra_state_attributes = {ATTR_BATTERY_LEVEL: detail.battery_level}
self._attr_extra_state_attributes = { if keypad := detail.keypad:
ATTR_BATTERY_LEVEL: self._detail.battery_level
}
if self._detail.keypad is not None:
self._attr_extra_state_attributes["keypad_battery_level"] = ( self._attr_extra_state_attributes["keypad_battery_level"] = (
self._detail.keypad.battery_level keypad.battery_level
) )
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:

View File

@ -127,12 +127,8 @@ class AugustOperatorSensor(AugustEntityMixin, RestoreSensor):
@callback @callback
def _update_from_data(self) -> None: def _update_from_data(self) -> None:
"""Get the latest state of the sensor and update activity.""" """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 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) lock_activity = cast(LockOperationActivity, lock_activity)
self._attr_native_value = lock_activity.operated_by self._attr_native_value = lock_activity.operated_by
self._operated_remote = lock_activity.operated_remote self._operated_remote = lock_activity.operated_remote