From 87e52bb6603a541eed813d9621826754642d9f01 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 19 Jun 2024 09:21:04 -0500 Subject: [PATCH] Small cleanups to august (#119950) --- homeassistant/components/august/__init__.py | 23 +++------ .../components/august/binary_sensor.py | 49 ++++++------------- homeassistant/components/august/camera.py | 14 ++---- homeassistant/components/august/data.py | 9 +--- homeassistant/components/august/entity.py | 14 ++++-- homeassistant/components/august/lock.py | 38 ++++---------- homeassistant/components/august/sensor.py | 6 +-- 7 files changed, 46 insertions(+), 107 deletions(-) diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index cc4070c0d53..eec794896f6 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -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 diff --git a/homeassistant/components/august/binary_sensor.py b/homeassistant/components/august/binary_sensor.py index beb899a174b..aeeaf9f690c 100644 --- a/homeassistant/components/august/binary_sensor.py +++ b/homeassistant/components/august/binary_sensor.py @@ -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 - return _activity_time_based_state(latest) + 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 diff --git a/homeassistant/components/august/camera.py b/homeassistant/components/august/camera.py index ba29b2905d3..4e569e2a91e 100644 --- a/homeassistant/components/august/camera.py +++ b/homeassistant/components/august/camera.py @@ -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( diff --git a/homeassistant/components/august/data.py b/homeassistant/components/august/data.py index 55c7c2bfa03..66ddfeedfde 100644 --- a/homeassistant/components/august/data.py +++ b/homeassistant/components/august/data.py @@ -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) diff --git a/homeassistant/components/august/entity.py b/homeassistant/components/august/entity.py index 960dddbc005..babf5c587fb 100644 --- a/homeassistant/components/august/entity.py +++ b/homeassistant/components/august/entity.py @@ -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 ) ) diff --git a/homeassistant/components/august/lock.py b/homeassistant/components/august/lock.py index 10d32ebd323..7aee612aa41 100644 --- a/homeassistant/components/august/lock.py +++ b/homeassistant/components/august/lock.py @@ -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: diff --git a/homeassistant/components/august/sensor.py b/homeassistant/components/august/sensor.py index 847d7f32a5a..7a4c1a92358 100644 --- a/homeassistant/components/august/sensor.py +++ b/homeassistant/components/august/sensor.py @@ -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