From 23cd401ca51ce51893c307a35a79ae7c6f89d69e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:07:09 -0500 Subject: [PATCH 01/31] Reduce duplicate code in unifiprotect --- .../components/unifiprotect/binary_sensor.py | 11 ++++------- homeassistant/components/unifiprotect/entity.py | 12 ++++++++++++ homeassistant/components/unifiprotect/switch.py | 17 +++++++++-------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index c4e1aa87df25a..93fa9b5e54193 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -32,6 +32,7 @@ BaseProtectEntity, EventEntityMixin, ProtectDeviceEntity, + ProtectIsOnMixin, ProtectNVREntity, async_all_device_entities, ) @@ -623,17 +624,13 @@ class ProtectBinaryEventEntityDescription( } -class ProtectDeviceBinarySensor(ProtectDeviceEntity, BinarySensorEntity): +class ProtectDeviceBinarySensor( + ProtectIsOnMixin, ProtectDeviceEntity, BinarySensorEntity +): """A UniFi Protect Device Binary Sensor.""" device: Camera | Light | Sensor entity_description: ProtectBinaryEntityDescription - _state_attrs: tuple[str, ...] = ("_attr_available", "_attr_is_on") - - @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: - super()._async_update_device_from_protect(device) - self._attr_is_on = self.entity_description.get_ufp_value(self.device) class MountableProtectDeviceBinarySensor(ProtectDeviceBinarySensor): diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 7eceb86195533..877b1199870c1 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -266,6 +266,18 @@ async def async_added_to_hass(self) -> None: ) +class ProtectIsOnMixin(BaseProtectEntity): + """Base class for entities with is_on property.""" + + _state_attrs: tuple[str, ...] = ("_attr_available", "_attr_is_on") + _attr_is_on: bool | None + entity_description: ProtectEntityDescription + + def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + super()._async_update_device_from_protect(device) + self._attr_is_on = self.entity_description.get_ufp_value(self.device) is True + + class ProtectDeviceEntity(BaseProtectEntity): """Base class for UniFi protect entities.""" diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index ca56a602209f5..68f45a332f950 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -27,6 +27,7 @@ from .entity import ( BaseProtectEntity, ProtectDeviceEntity, + ProtectIsOnMixin, ProtectNVREntity, async_all_device_entities, ) @@ -472,15 +473,10 @@ async def _set_highfps(obj: Camera, value: bool) -> None: } -class ProtectBaseSwitch(BaseProtectEntity, SwitchEntity): +class ProtectBaseSwitch(ProtectIsOnMixin): """Base class for UniFi Protect Switch.""" entity_description: ProtectSwitchEntityDescription - _state_attrs = ("_attr_available", "_attr_is_on") - - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: - super()._async_update_device_from_protect(device) - self._attr_is_on = self.entity_description.get_ufp_value(self.device) is True async def async_turn_on(self, **kwargs: Any) -> None: """Turn the device on.""" @@ -491,18 +487,23 @@ async def async_turn_off(self, **kwargs: Any) -> None: await self.entity_description.ufp_set(self.device, False) -class ProtectSwitch(ProtectBaseSwitch, ProtectDeviceEntity): +class ProtectSwitch(ProtectDeviceEntity, ProtectBaseSwitch, SwitchEntity): """A UniFi Protect Switch.""" + entity_description: ProtectSwitchEntityDescription + -class ProtectNVRSwitch(ProtectBaseSwitch, ProtectNVREntity): +class ProtectNVRSwitch(ProtectNVREntity, ProtectBaseSwitch, SwitchEntity): """A UniFi Protect NVR Switch.""" + entity_description: ProtectSwitchEntityDescription + class ProtectPrivacyModeSwitch(RestoreEntity, ProtectSwitch): """A UniFi Protect Switch.""" device: Camera + entity_description: ProtectSwitchEntityDescription def __init__( self, From d642d0e0935d17781856038544062e8ad6d0d802 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:11:46 -0500 Subject: [PATCH 02/31] Reduce duplicate code in unifiprotect --- homeassistant/components/unifiprotect/binary_sensor.py | 3 --- homeassistant/components/unifiprotect/entity.py | 8 ++++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 93fa9b5e54193..58cc63d38afc2 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -8,7 +8,6 @@ from uiprotect.data import ( NVR, Camera, - Light, ModelType, MountType, ProtectAdoptableDeviceModel, @@ -629,7 +628,6 @@ class ProtectDeviceBinarySensor( ): """A UniFi Protect Device Binary Sensor.""" - device: Camera | Light | Sensor entity_description: ProtectBinaryEntityDescription @@ -670,7 +668,6 @@ def __init__( self._disk = disk # backwards compat with old unique IDs index = self._disk.slot - 1 - description = dataclasses.replace( description, key=f"{description.key}_{index}", diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 877b1199870c1..8e7030fb446b8 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -223,7 +223,7 @@ def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: self.device = device async_get_ufp_enabled = self._async_get_ufp_enabled - self._attr_available = ( + available = ( last_update_success and ( device.state is StateType.CONNECTED @@ -231,6 +231,8 @@ def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: ) and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) ) + if self._attr_available != available: + self._attr_available = available @callback def _async_updated_event(self, device: ProtectAdoptableDeviceModel | NVR) -> None: @@ -275,7 +277,9 @@ class ProtectIsOnMixin(BaseProtectEntity): def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) - self._attr_is_on = self.entity_description.get_ufp_value(self.device) is True + is_on = self.entity_description.get_ufp_value(self.device) is True + if self._attr_is_on != is_on: + self._attr_is_on = is_on class ProtectDeviceEntity(BaseProtectEntity): From 5e3a93ba64dfec33912bbb3e99df770a2abc2682 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:14:22 -0500 Subject: [PATCH 03/31] Reduce duplicate code in unifiprotect --- homeassistant/components/unifiprotect/entity.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 8e7030fb446b8..70b887a01fd43 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -222,16 +222,17 @@ def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: if last_update_success := self.data.last_update_success: self.device = device + was_available = self._attr_available async_get_ufp_enabled = self._async_get_ufp_enabled + connected_or_adoptable = device.state is StateType.CONNECTED or ( + not device.is_adopted_by_us and device.can_adopt + ) available = ( last_update_success - and ( - device.state is StateType.CONNECTED - or (not device.is_adopted_by_us and device.can_adopt) - ) + and connected_or_adoptable and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) ) - if self._attr_available != available: + if available != was_available: self._attr_available = available @callback From 49e916676a61e910445cbcf0d5ddd740b1235d97 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:15:35 -0500 Subject: [PATCH 04/31] Reduce duplicate code in unifiprotect --- homeassistant/components/unifiprotect/entity.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 70b887a01fd43..1366942fc33ec 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -278,8 +278,9 @@ class ProtectIsOnMixin(BaseProtectEntity): def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) - is_on = self.entity_description.get_ufp_value(self.device) is True - if self._attr_is_on != is_on: + was_on = self._attr_is_on + is_on = self.entity_description.get_ufp_value(device) is True + if was_on != is_on: self._attr_is_on = is_on From 9c21adb7ddf17023f0b68859ae79d17d76fc9d3b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:15:56 -0500 Subject: [PATCH 05/31] Reduce duplicate code in unifiprotect --- homeassistant/components/unifiprotect/entity.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 1366942fc33ec..0955e9a3b6828 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -279,8 +279,7 @@ class ProtectIsOnMixin(BaseProtectEntity): def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) was_on = self._attr_is_on - is_on = self.entity_description.get_ufp_value(device) is True - if was_on != is_on: + if was_on != (is_on := self.entity_description.get_ufp_value(device) is True): self._attr_is_on = is_on From 7fa912c5ad41e4a68207abc79e7f09212faaef23 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:18:04 -0500 Subject: [PATCH 06/31] Reduce duplicate code in unifiprotect --- .../components/unifiprotect/binary_sensor.py | 4 ++-- homeassistant/components/unifiprotect/entity.py | 2 +- homeassistant/components/unifiprotect/switch.py | 11 +++-------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 58cc63d38afc2..60b93917daf7f 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -31,7 +31,7 @@ BaseProtectEntity, EventEntityMixin, ProtectDeviceEntity, - ProtectIsOnMixin, + ProtectIsOnEntity, ProtectNVREntity, async_all_device_entities, ) @@ -624,7 +624,7 @@ class ProtectBinaryEventEntityDescription( class ProtectDeviceBinarySensor( - ProtectIsOnMixin, ProtectDeviceEntity, BinarySensorEntity + ProtectIsOnEntity, ProtectDeviceEntity, BinarySensorEntity ): """A UniFi Protect Device Binary Sensor.""" diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 0955e9a3b6828..bdc2a4c1105cf 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -269,7 +269,7 @@ async def async_added_to_hass(self) -> None: ) -class ProtectIsOnMixin(BaseProtectEntity): +class ProtectIsOnEntity(BaseProtectEntity): """Base class for entities with is_on property.""" _state_attrs: tuple[str, ...] = ("_attr_available", "_attr_is_on") diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index 68f45a332f950..7ef537046ac4d 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -5,7 +5,6 @@ from collections.abc import Sequence from dataclasses import dataclass from functools import partial -import logging from typing import Any from uiprotect.data import ( @@ -27,13 +26,12 @@ from .entity import ( BaseProtectEntity, ProtectDeviceEntity, - ProtectIsOnMixin, + ProtectIsOnEntity, ProtectNVREntity, async_all_device_entities, ) from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T -_LOGGER = logging.getLogger(__name__) ATTR_PREV_MIC = "prev_mic_level" ATTR_PREV_RECORD = "prev_record_mode" @@ -46,10 +44,7 @@ class ProtectSwitchEntityDescription( async def _set_highfps(obj: Camera, value: bool) -> None: - if value: - await obj.set_video_mode(VideoMode.HIGH_FPS) - else: - await obj.set_video_mode(VideoMode.DEFAULT) + await obj.set_video_mode(VideoMode.HIGH_FPS if value else VideoMode.DEFAULT) CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( @@ -473,7 +468,7 @@ async def _set_highfps(obj: Camera, value: bool) -> None: } -class ProtectBaseSwitch(ProtectIsOnMixin): +class ProtectBaseSwitch(ProtectIsOnEntity): """Base class for UniFi Protect Switch.""" entity_description: ProtectSwitchEntityDescription From 25dfcd7e6fa4bc28bbde41ccfbbbd2e9dba6b9f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:20:04 -0500 Subject: [PATCH 07/31] Reduce duplicate code in unifiprotect --- homeassistant/components/unifiprotect/binary_sensor.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 60b93917daf7f..159dcdb5a1b64 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -635,11 +635,7 @@ class MountableProtectDeviceBinarySensor(ProtectDeviceBinarySensor): """A UniFi Protect Device Binary Sensor that can change device class at runtime.""" device: Sensor - _state_attrs: tuple[str, ...] = ( - "_attr_available", - "_attr_is_on", - "_attr_device_class", - ) + _state_attrs = ("_attr_available", "_attr_is_on", "_attr_device_class") @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: From 128c3989fb3b21fce84791e8f511f95d91d38eaa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:24:47 -0500 Subject: [PATCH 08/31] Reduce duplicate code in unifiprotect --- homeassistant/components/unifiprotect/entity.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index bdc2a4c1105cf..70ce389ddccae 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -308,12 +308,9 @@ def _async_set_device_info(self) -> None: @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: - data = self.data - if last_update_success := data.last_update_success: + if (data := self.data).last_update_success: self.device = data.api.bootstrap.nvr - self._attr_available = last_update_success - class EventEntityMixin(ProtectDeviceEntity): """Adds motion event attributes to sensor.""" From b3dcf9160dfc6e54a0c1baea902787f584b21da5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:33:32 -0500 Subject: [PATCH 09/31] Reduce duplicate code in unifiprotect --- .../components/unifiprotect/entity.py | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 70ce389ddccae..f22ad858c1024 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -216,22 +216,29 @@ def _async_set_device_info(self) -> None: @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: """Update Entity object from Protect device.""" - if TYPE_CHECKING: - assert isinstance(device, ProtectAdoptableDeviceModel) - - if last_update_success := self.data.last_update_success: - self.device = device - was_available = self._attr_available async_get_ufp_enabled = self._async_get_ufp_enabled - connected_or_adoptable = device.state is StateType.CONNECTED or ( - not device.is_adopted_by_us and device.can_adopt - ) - available = ( - last_update_success - and connected_or_adoptable - and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) - ) + available = self.data.last_update_success + if device.model is ModelType.NVR: + if TYPE_CHECKING: + assert isinstance(device, NVR) + connected_or_adoptable = True + if available: + self.device = device + else: + if TYPE_CHECKING: + assert isinstance(device, ProtectAdoptableDeviceModel) + connected_or_adoptable = device.state is StateType.CONNECTED or ( + not device.is_adopted_by_us and device.can_adopt + ) + if available: + self.device = device + available = ( + available + and connected_or_adoptable + and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) + ) + if available != was_available: self._attr_available = available @@ -306,11 +313,6 @@ def _async_set_device_info(self) -> None: configuration_url=self.device.api.base_url, ) - @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: - if (data := self.data).last_update_success: - self.device = data.api.bootstrap.nvr - class EventEntityMixin(ProtectDeviceEntity): """Adds motion event attributes to sensor.""" From 7deee4d03b37f19d2922fb6cb85c33f3ea0ed112 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:34:11 -0500 Subject: [PATCH 10/31] Reduce duplicate code in unifiprotect --- homeassistant/components/unifiprotect/entity.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index f22ad858c1024..4a8a86bc3183e 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -218,23 +218,23 @@ def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: """Update Entity object from Protect device.""" was_available = self._attr_available async_get_ufp_enabled = self._async_get_ufp_enabled - available = self.data.last_update_success + last_updated_success = self.data.last_update_success if device.model is ModelType.NVR: if TYPE_CHECKING: assert isinstance(device, NVR) - connected_or_adoptable = True - if available: + if available := last_updated_success: self.device = device else: if TYPE_CHECKING: assert isinstance(device, ProtectAdoptableDeviceModel) + if last_updated_success: + self.device = device + connected_or_adoptable = device.state is StateType.CONNECTED or ( not device.is_adopted_by_us and device.can_adopt ) - if available: - self.device = device available = ( - available + last_updated_success and connected_or_adoptable and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) ) From b9d461fd598fad812c200b118bdf15f4a13abaae Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:35:46 -0500 Subject: [PATCH 11/31] Reduce duplicate code in unifiprotect --- homeassistant/components/unifiprotect/entity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 4a8a86bc3183e..caa96f87dcdd4 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -219,6 +219,7 @@ def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: was_available = self._attr_available async_get_ufp_enabled = self._async_get_ufp_enabled last_updated_success = self.data.last_update_success + if device.model is ModelType.NVR: if TYPE_CHECKING: assert isinstance(device, NVR) From 0f28fd27c4568354ab7ece8f9a0e800dfb3eb93b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:41:43 -0500 Subject: [PATCH 12/31] Reduce duplicate code in unifiprotect --- .../components/unifiprotect/entity.py | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index caa96f87dcdd4..1289a1a7053a4 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -2,6 +2,7 @@ from __future__ import annotations +from abc import ABC, abstractmethod from collections.abc import Callable, Sequence from datetime import datetime from functools import partial @@ -157,7 +158,7 @@ def async_all_device_entities( ) -class BaseProtectEntity(Entity): +class BaseProtectEntity(Entity, ABC): """Base class for UniFi protect entities.""" device: ProtectAdoptableDeviceModel | NVR @@ -202,46 +203,13 @@ async def async_update(self) -> None: await self.data.async_refresh() @callback + @abstractmethod def _async_set_device_info(self) -> None: - self._attr_device_info = DeviceInfo( - name=self.device.display_name, - manufacturer=DEFAULT_BRAND, - model=self.device.type, - via_device=(DOMAIN, self.data.api.bootstrap.nvr.mac), - sw_version=self.device.firmware_version, - connections={(dr.CONNECTION_NETWORK_MAC, self.device.mac)}, - configuration_url=self.device.protect_url, - ) + """Set device information.""" @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: """Update Entity object from Protect device.""" - was_available = self._attr_available - async_get_ufp_enabled = self._async_get_ufp_enabled - last_updated_success = self.data.last_update_success - - if device.model is ModelType.NVR: - if TYPE_CHECKING: - assert isinstance(device, NVR) - if available := last_updated_success: - self.device = device - else: - if TYPE_CHECKING: - assert isinstance(device, ProtectAdoptableDeviceModel) - if last_updated_success: - self.device = device - - connected_or_adoptable = device.state is StateType.CONNECTED or ( - not device.is_adopted_by_us and device.can_adopt - ) - available = ( - last_updated_success - and connected_or_adoptable - and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) - ) - - if available != was_available: - self._attr_available = available @callback def _async_updated_event(self, device: ProtectAdoptableDeviceModel | NVR) -> None: @@ -296,6 +264,38 @@ class ProtectDeviceEntity(BaseProtectEntity): device: ProtectAdoptableDeviceModel + @callback + def _async_set_device_info(self) -> None: + self._attr_device_info = DeviceInfo( + name=self.device.display_name, + manufacturer=DEFAULT_BRAND, + model=self.device.type, + via_device=(DOMAIN, self.data.api.bootstrap.nvr.mac), + sw_version=self.device.firmware_version, + connections={(dr.CONNECTION_NETWORK_MAC, self.device.mac)}, + configuration_url=self.device.protect_url, + ) + + @callback + def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + """Update Entity object from Protect device.""" + if TYPE_CHECKING: + assert isinstance(device, ProtectAdoptableDeviceModel) + was_available = self._attr_available + async_get_ufp_enabled = self._async_get_ufp_enabled + if last_updated_success := self.data.last_update_success: + self.device = device + connected_or_adoptable = device.state is StateType.CONNECTED or ( + not device.is_adopted_by_us and device.can_adopt + ) + available = ( + last_updated_success + and connected_or_adoptable + and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) + ) + if available != was_available: + self._attr_available = available + class ProtectNVREntity(BaseProtectEntity): """Base class for unifi protect entities.""" @@ -314,6 +314,17 @@ def _async_set_device_info(self) -> None: configuration_url=self.device.api.base_url, ) + @callback + def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + """Update Entity object from Protect device.""" + if TYPE_CHECKING: + assert isinstance(device, NVR) + was_available = self._attr_available + if available := self.data.last_update_success: + self.device = device + if available != was_available: + self._attr_available = available + class EventEntityMixin(ProtectDeviceEntity): """Adds motion event attributes to sensor.""" From 4065a1db67391c7fe9ffabc82bf8c58708393210 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:43:30 -0500 Subject: [PATCH 13/31] Revert "Reduce duplicate code in unifiprotect" This reverts commit 0f28fd27c4568354ab7ece8f9a0e800dfb3eb93b. --- .../components/unifiprotect/entity.py | 83 ++++++++----------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 1289a1a7053a4..caa96f87dcdd4 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -2,7 +2,6 @@ from __future__ import annotations -from abc import ABC, abstractmethod from collections.abc import Callable, Sequence from datetime import datetime from functools import partial @@ -158,7 +157,7 @@ def async_all_device_entities( ) -class BaseProtectEntity(Entity, ABC): +class BaseProtectEntity(Entity): """Base class for UniFi protect entities.""" device: ProtectAdoptableDeviceModel | NVR @@ -203,13 +202,46 @@ async def async_update(self) -> None: await self.data.async_refresh() @callback - @abstractmethod def _async_set_device_info(self) -> None: - """Set device information.""" + self._attr_device_info = DeviceInfo( + name=self.device.display_name, + manufacturer=DEFAULT_BRAND, + model=self.device.type, + via_device=(DOMAIN, self.data.api.bootstrap.nvr.mac), + sw_version=self.device.firmware_version, + connections={(dr.CONNECTION_NETWORK_MAC, self.device.mac)}, + configuration_url=self.device.protect_url, + ) @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: """Update Entity object from Protect device.""" + was_available = self._attr_available + async_get_ufp_enabled = self._async_get_ufp_enabled + last_updated_success = self.data.last_update_success + + if device.model is ModelType.NVR: + if TYPE_CHECKING: + assert isinstance(device, NVR) + if available := last_updated_success: + self.device = device + else: + if TYPE_CHECKING: + assert isinstance(device, ProtectAdoptableDeviceModel) + if last_updated_success: + self.device = device + + connected_or_adoptable = device.state is StateType.CONNECTED or ( + not device.is_adopted_by_us and device.can_adopt + ) + available = ( + last_updated_success + and connected_or_adoptable + and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) + ) + + if available != was_available: + self._attr_available = available @callback def _async_updated_event(self, device: ProtectAdoptableDeviceModel | NVR) -> None: @@ -264,38 +296,6 @@ class ProtectDeviceEntity(BaseProtectEntity): device: ProtectAdoptableDeviceModel - @callback - def _async_set_device_info(self) -> None: - self._attr_device_info = DeviceInfo( - name=self.device.display_name, - manufacturer=DEFAULT_BRAND, - model=self.device.type, - via_device=(DOMAIN, self.data.api.bootstrap.nvr.mac), - sw_version=self.device.firmware_version, - connections={(dr.CONNECTION_NETWORK_MAC, self.device.mac)}, - configuration_url=self.device.protect_url, - ) - - @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: - """Update Entity object from Protect device.""" - if TYPE_CHECKING: - assert isinstance(device, ProtectAdoptableDeviceModel) - was_available = self._attr_available - async_get_ufp_enabled = self._async_get_ufp_enabled - if last_updated_success := self.data.last_update_success: - self.device = device - connected_or_adoptable = device.state is StateType.CONNECTED or ( - not device.is_adopted_by_us and device.can_adopt - ) - available = ( - last_updated_success - and connected_or_adoptable - and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) - ) - if available != was_available: - self._attr_available = available - class ProtectNVREntity(BaseProtectEntity): """Base class for unifi protect entities.""" @@ -314,17 +314,6 @@ def _async_set_device_info(self) -> None: configuration_url=self.device.api.base_url, ) - @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: - """Update Entity object from Protect device.""" - if TYPE_CHECKING: - assert isinstance(device, NVR) - was_available = self._attr_available - if available := self.data.last_update_success: - self.device = device - if available != was_available: - self._attr_available = available - class EventEntityMixin(ProtectDeviceEntity): """Adds motion event attributes to sensor.""" From d8fe6347dba04c397f85eaf3e0e282cbdadfa244 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:45:47 -0500 Subject: [PATCH 14/31] Reduce duplicate code in unifiprotect --- .../components/unifiprotect/entity.py | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index caa96f87dcdd4..cba698e986ca8 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -2,6 +2,7 @@ from __future__ import annotations +from abc import ABC, abstractmethod from collections.abc import Callable, Sequence from datetime import datetime from functools import partial @@ -9,14 +10,7 @@ from operator import attrgetter from typing import TYPE_CHECKING -from uiprotect.data import ( - NVR, - Event, - ModelType, - ProtectAdoptableDeviceModel, - ProtectModelWithId, - StateType, -) +from uiprotect.data import NVR, Event, ModelType, ProtectAdoptableDeviceModel, StateType from homeassistant.core import callback import homeassistant.helpers.device_registry as dr @@ -157,7 +151,7 @@ def async_all_device_entities( ) -class BaseProtectEntity(Entity): +class BaseProtectEntity(Entity, ABC): """Base class for UniFi protect entities.""" device: ProtectAdoptableDeviceModel | NVR @@ -202,19 +196,14 @@ async def async_update(self) -> None: await self.data.async_refresh() @callback + @abstractmethod def _async_set_device_info(self) -> None: - self._attr_device_info = DeviceInfo( - name=self.device.display_name, - manufacturer=DEFAULT_BRAND, - model=self.device.type, - via_device=(DOMAIN, self.data.api.bootstrap.nvr.mac), - sw_version=self.device.firmware_version, - connections={(dr.CONNECTION_NETWORK_MAC, self.device.mac)}, - configuration_url=self.device.protect_url, - ) + """Set device info.""" @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect( + self, device: ProtectAdoptableDeviceModel | NVR + ) -> None: """Update Entity object from Protect device.""" was_available = self._attr_available async_get_ufp_enabled = self._async_get_ufp_enabled @@ -284,7 +273,9 @@ class ProtectIsOnEntity(BaseProtectEntity): _attr_is_on: bool | None entity_description: ProtectEntityDescription - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect( + self, device: ProtectAdoptableDeviceModel | NVR + ) -> None: super()._async_update_device_from_protect(device) was_on = self._attr_is_on if was_on != (is_on := self.entity_description.get_ufp_value(device) is True): @@ -296,6 +287,18 @@ class ProtectDeviceEntity(BaseProtectEntity): device: ProtectAdoptableDeviceModel + @callback + def _async_set_device_info(self) -> None: + self._attr_device_info = DeviceInfo( + name=self.device.display_name, + manufacturer=DEFAULT_BRAND, + model=self.device.type, + via_device=(DOMAIN, self.data.api.bootstrap.nvr.mac), + sw_version=self.device.firmware_version, + connections={(dr.CONNECTION_NETWORK_MAC, self.device.mac)}, + configuration_url=self.device.protect_url, + ) + class ProtectNVREntity(BaseProtectEntity): """Base class for unifi protect entities.""" From 09437986a32e5c729ec6d0e5fe0eb5f3921984a7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:50:50 -0500 Subject: [PATCH 15/31] cleanups --- .../components/unifiprotect/binary_sensor.py | 9 ++++----- homeassistant/components/unifiprotect/button.py | 6 +++--- homeassistant/components/unifiprotect/camera.py | 5 ++--- homeassistant/components/unifiprotect/entity.py | 12 +++++------- homeassistant/components/unifiprotect/event.py | 11 +++-------- homeassistant/components/unifiprotect/light.py | 11 +++-------- homeassistant/components/unifiprotect/lock.py | 5 ++--- .../components/unifiprotect/media_player.py | 11 +++-------- homeassistant/components/unifiprotect/number.py | 5 ++--- homeassistant/components/unifiprotect/select.py | 5 ++--- homeassistant/components/unifiprotect/sensor.py | 7 +++---- homeassistant/components/unifiprotect/switch.py | 5 ++--- homeassistant/components/unifiprotect/text.py | 5 ++--- 13 files changed, 36 insertions(+), 61 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 159dcdb5a1b64..fe2017d2f053b 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -11,7 +11,6 @@ ModelType, MountType, ProtectAdoptableDeviceModel, - ProtectModelWithId, Sensor, SmartDetectObjectType, ) @@ -26,7 +25,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .data import ProtectData, UFPConfigEntry +from .data import ProtectData, ProtectDeviceType, UFPConfigEntry from .entity import ( BaseProtectEntity, EventEntityMixin, @@ -638,7 +637,7 @@ class MountableProtectDeviceBinarySensor(ProtectDeviceBinarySensor): _state_attrs = ("_attr_available", "_attr_is_on", "_attr_device_class") @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) # UP Sense can be any of the 3 contact sensor device classes self._attr_device_class = MOUNT_DEVICE_CLASS_MAP.get( @@ -672,7 +671,7 @@ def __init__( super().__init__(data, device, description) @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) slot = self._disk.slot self._attr_available = False @@ -702,7 +701,7 @@ def _set_event_done(self) -> None: self._attr_extra_state_attributes = {} @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: description = self.entity_description prev_event = self._event diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 6c0ef37e1df10..911c2e97a0f6a 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -7,7 +7,7 @@ import logging from typing import Final -from uiprotect.data import ModelType, ProtectAdoptableDeviceModel, ProtectModelWithId +from uiprotect.data import ModelType, ProtectAdoptableDeviceModel from homeassistant.components.button import ( ButtonDeviceClass, @@ -21,7 +21,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DEVICES_THAT_ADOPT, DOMAIN -from .data import UFPConfigEntry +from .data import ProtectDeviceType, UFPConfigEntry from .entity import ProtectDeviceEntity, async_all_device_entities from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T @@ -171,7 +171,7 @@ class ProtectButton(ProtectDeviceEntity, ButtonEntity): entity_description: ProtectButtonEntityDescription @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) if self.entity_description.key == KEY_ADOPT: device = self.device diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index 73cdb4a2c312e..62c35d00171be 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -9,7 +9,6 @@ Camera as UFPCamera, CameraChannel, ProtectAdoptableDeviceModel, - ProtectModelWithId, StateType, ) @@ -28,7 +27,7 @@ ATTR_WIDTH, DOMAIN, ) -from .data import ProtectData, UFPConfigEntry +from .data import ProtectData, ProtectDeviceType, UFPConfigEntry from .entity import ProtectDeviceEntity from .utils import get_camera_base_name @@ -216,7 +215,7 @@ def _async_set_stream_source(self) -> None: self._attr_supported_features = _EMPTY_CAMERA_FEATURES @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) updated_device = self.device channel = updated_device.channels[self.channel.id] diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index cba698e986ca8..8fbbab5ff2a74 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -24,7 +24,7 @@ DEFAULT_BRAND, DOMAIN, ) -from .data import ProtectData +from .data import ProtectData, ProtectDeviceType from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin _LOGGER = logging.getLogger(__name__) @@ -154,7 +154,7 @@ def async_all_device_entities( class BaseProtectEntity(Entity, ABC): """Base class for UniFi protect entities.""" - device: ProtectAdoptableDeviceModel | NVR + device: ProtectDeviceType _attr_should_poll = False _attr_attribution = DEFAULT_ATTRIBUTION @@ -165,7 +165,7 @@ class BaseProtectEntity(Entity, ABC): def __init__( self, data: ProtectData, - device: ProtectAdoptableDeviceModel | NVR, + device: ProtectDeviceType, description: EntityDescription | None = None, ) -> None: """Initialize the entity.""" @@ -201,9 +201,7 @@ def _async_set_device_info(self) -> None: """Set device info.""" @callback - def _async_update_device_from_protect( - self, device: ProtectAdoptableDeviceModel | NVR - ) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: """Update Entity object from Protect device.""" was_available = self._attr_available async_get_ufp_enabled = self._async_get_ufp_enabled @@ -233,7 +231,7 @@ def _async_update_device_from_protect( self._attr_available = available @callback - def _async_updated_event(self, device: ProtectAdoptableDeviceModel | NVR) -> None: + def _async_updated_event(self, device: ProtectDeviceType) -> None: """When device is updated from Protect.""" previous_attrs = [getter() for getter in self._state_getters] self._async_update_device_from_protect(device) diff --git a/homeassistant/components/unifiprotect/event.py b/homeassistant/components/unifiprotect/event.py index 4e2fd7fce447a..c8269e363267a 100644 --- a/homeassistant/components/unifiprotect/event.py +++ b/homeassistant/components/unifiprotect/event.py @@ -4,12 +4,7 @@ import dataclasses -from uiprotect.data import ( - Camera, - EventType, - ProtectAdoptableDeviceModel, - ProtectModelWithId, -) +from uiprotect.data import Camera, EventType, ProtectAdoptableDeviceModel from homeassistant.components.event import ( EventDeviceClass, @@ -20,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ATTR_EVENT_ID -from .data import ProtectData, UFPConfigEntry +from .data import ProtectData, ProtectDeviceType, UFPConfigEntry from .entity import EventEntityMixin, ProtectDeviceEntity from .models import ProtectEventMixin @@ -50,7 +45,7 @@ class ProtectDeviceEventEntity(EventEntityMixin, ProtectDeviceEntity, EventEntit entity_description: ProtectEventEntityDescription @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: description = self.entity_description prev_event = self._event diff --git a/homeassistant/components/unifiprotect/light.py b/homeassistant/components/unifiprotect/light.py index 651b9c7d3d495..486a8956e0c78 100644 --- a/homeassistant/components/unifiprotect/light.py +++ b/homeassistant/components/unifiprotect/light.py @@ -5,18 +5,13 @@ import logging from typing import Any -from uiprotect.data import ( - Light, - ModelType, - ProtectAdoptableDeviceModel, - ProtectModelWithId, -) +from uiprotect.data import Light, ModelType, ProtectAdoptableDeviceModel from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .data import UFPConfigEntry +from .data import ProtectDeviceType, UFPConfigEntry from .entity import ProtectDeviceEntity _LOGGER = logging.getLogger(__name__) @@ -66,7 +61,7 @@ class ProtectLight(ProtectDeviceEntity, LightEntity): _state_attrs = ("_attr_available", "_attr_is_on", "_attr_brightness") @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) updated_device = self.device self._attr_is_on = updated_device.is_light_on diff --git a/homeassistant/components/unifiprotect/lock.py b/homeassistant/components/unifiprotect/lock.py index b649813135b35..3e9372db0e545 100644 --- a/homeassistant/components/unifiprotect/lock.py +++ b/homeassistant/components/unifiprotect/lock.py @@ -10,14 +10,13 @@ LockStatusType, ModelType, ProtectAdoptableDeviceModel, - ProtectModelWithId, ) from homeassistant.components.lock import LockEntity, LockEntityDescription from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .data import UFPConfigEntry +from .data import ProtectDeviceType, UFPConfigEntry from .entity import ProtectDeviceEntity _LOGGER = logging.getLogger(__name__) @@ -60,7 +59,7 @@ class ProtectLock(ProtectDeviceEntity, LockEntity): ) @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) lock_status = self.device.lock_status diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index d9b2dad722059..5f9991b257ba9 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -5,12 +5,7 @@ import logging from typing import Any -from uiprotect.data import ( - Camera, - ProtectAdoptableDeviceModel, - ProtectModelWithId, - StateType, -) +from uiprotect.data import Camera, ProtectAdoptableDeviceModel, StateType from uiprotect.exceptions import StreamError from homeassistant.components import media_source @@ -28,7 +23,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .data import UFPConfigEntry +from .data import ProtectDeviceType, UFPConfigEntry from .entity import ProtectDeviceEntity _LOGGER = logging.getLogger(__name__) @@ -77,7 +72,7 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity): _state_attrs = ("_attr_available", "_attr_state", "_attr_volume_level") @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) updated_device = self.device self._attr_volume_level = float(updated_device.speaker_settings.volume / 100) diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index a0d360af80bdd..4282746fc563f 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -12,7 +12,6 @@ Light, ModelType, ProtectAdoptableDeviceModel, - ProtectModelWithId, ) from homeassistant.components.number import NumberEntity, NumberEntityDescription @@ -20,7 +19,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .data import ProtectData, UFPConfigEntry +from .data import ProtectData, ProtectDeviceType, UFPConfigEntry from .entity import ProtectDeviceEntity, async_all_device_entities from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T @@ -268,7 +267,7 @@ def __init__( self._attr_native_step = self.entity_description.ufp_step @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) self._attr_native_value = self.entity_description.get_ufp_value(self.device) diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 9e742caa9cebc..e06ae7bfbeca3 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -21,7 +21,6 @@ ModelType, MountType, ProtectAdoptableDeviceModel, - ProtectModelWithId, RecordingMode, Sensor, Viewer, @@ -33,7 +32,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import TYPE_EMPTY_VALUE -from .data import ProtectData, UFPConfigEntry +from .data import ProtectData, ProtectDeviceType, UFPConfigEntry from .entity import ProtectDeviceEntity, async_all_device_entities from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T from .utils import async_get_light_motion_current @@ -371,7 +370,7 @@ def __init__( super().__init__(data, device, description) @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) entity_description = self.entity_description # entities with categories are not exposed for voice diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index 84cac342d00d7..786c5bd66c825 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -16,7 +16,6 @@ ModelType, ProtectAdoptableDeviceModel, ProtectDeviceModel, - ProtectModelWithId, Sensor, SmartDetectObjectType, ) @@ -41,7 +40,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .data import ProtectData, UFPConfigEntry +from .data import ProtectData, ProtectDeviceType, UFPConfigEntry from .entity import ( BaseProtectEntity, EventEntityMixin, @@ -721,7 +720,7 @@ class BaseProtectSensor(BaseProtectEntity, SensorEntity): entity_description: ProtectSensorEntityDescription _state_attrs = ("_attr_available", "_attr_native_value") - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) self._attr_native_value = self.entity_description.get_ufp_value(self.device) @@ -756,7 +755,7 @@ def _set_event_done(self) -> None: self._attr_extra_state_attributes = {} @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: description = self.entity_description prev_event = self._event diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index 7ef537046ac4d..6eac572cc3e23 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -11,7 +11,6 @@ Camera, ModelType, ProtectAdoptableDeviceModel, - ProtectModelWithId, RecordingMode, VideoMode, ) @@ -22,7 +21,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.restore_state import RestoreEntity -from .data import ProtectData, UFPConfigEntry +from .data import ProtectData, ProtectDeviceType, UFPConfigEntry from .entity import ( BaseProtectEntity, ProtectDeviceEntity, @@ -529,7 +528,7 @@ def _update_previous_attr(self) -> None: self._attr_extra_state_attributes = {} @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) # do not add extra state attribute on initialize if self.entity_id: diff --git a/homeassistant/components/unifiprotect/text.py b/homeassistant/components/unifiprotect/text.py index e01a6b31f11e4..9af946a7e116d 100644 --- a/homeassistant/components/unifiprotect/text.py +++ b/homeassistant/components/unifiprotect/text.py @@ -10,7 +10,6 @@ DoorbellMessageType, ModelType, ProtectAdoptableDeviceModel, - ProtectModelWithId, ) from homeassistant.components.text import TextEntity, TextEntityDescription @@ -18,7 +17,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .data import UFPConfigEntry +from .data import ProtectDeviceType, UFPConfigEntry from .entity import ProtectDeviceEntity, async_all_device_entities from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T @@ -89,7 +88,7 @@ class ProtectDeviceText(ProtectDeviceEntity, TextEntity): _state_attrs = ("_attr_available", "_attr_native_value") @callback - def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) self._attr_native_value = self.entity_description.get_ufp_value(self.device) From 86e50a90b71397ee217e755f03de9d328db5324e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:51:49 -0500 Subject: [PATCH 16/31] cleanups --- homeassistant/components/unifiprotect/entity.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 8fbbab5ff2a74..44bba4b5e1787 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -208,8 +208,6 @@ def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: last_updated_success = self.data.last_update_success if device.model is ModelType.NVR: - if TYPE_CHECKING: - assert isinstance(device, NVR) if available := last_updated_success: self.device = device else: From b84d5551b5fff1b81d70818b4c7e87979989b400 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:53:06 -0500 Subject: [PATCH 17/31] cleanups --- .../components/unifiprotect/entity.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 44bba4b5e1787..cd5b1c69c4166 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -204,27 +204,29 @@ def _async_set_device_info(self) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: """Update Entity object from Protect device.""" was_available = self._attr_available - async_get_ufp_enabled = self._async_get_ufp_enabled last_updated_success = self.data.last_update_success if device.model is ModelType.NVR: if available := last_updated_success: self.device = device - else: - if TYPE_CHECKING: - assert isinstance(device, ProtectAdoptableDeviceModel) - if last_updated_success: - self.device = device + if available != was_available: + self._attr_available = available + return - connected_or_adoptable = device.state is StateType.CONNECTED or ( - not device.is_adopted_by_us and device.can_adopt - ) - available = ( - last_updated_success - and connected_or_adoptable - and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) - ) + if TYPE_CHECKING: + assert isinstance(device, ProtectAdoptableDeviceModel) + if last_updated_success: + self.device = device + async_get_ufp_enabled = self._async_get_ufp_enabled + connected_or_adoptable = device.state is StateType.CONNECTED or ( + not device.is_adopted_by_us and device.can_adopt + ) + available = ( + last_updated_success + and connected_or_adoptable + and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) + ) if available != was_available: self._attr_available = available From 2eacdd656f4a0eea28bd9f5605eda8ef5b40531a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:55:07 -0500 Subject: [PATCH 18/31] cleanups --- .../components/unifiprotect/entity.py | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index cd5b1c69c4166..ed2648698347f 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -204,29 +204,25 @@ def _async_set_device_info(self) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: """Update Entity object from Protect device.""" was_available = self._attr_available - last_updated_success = self.data.last_update_success + if last_updated_success := self.data.last_update_success: + self.device = device - if device.model is ModelType.NVR: - if available := last_updated_success: - self.device = device - if available != was_available: - self._attr_available = available - return + if device.model is not ModelType.NVR: + if TYPE_CHECKING: + assert isinstance(device, ProtectAdoptableDeviceModel) - if TYPE_CHECKING: - assert isinstance(device, ProtectAdoptableDeviceModel) - if last_updated_success: - self.device = device + async_get_ufp_enabled = self._async_get_ufp_enabled + connected_or_adoptable = device.state is StateType.CONNECTED or ( + not device.is_adopted_by_us and device.can_adopt + ) + available = ( + last_updated_success + and connected_or_adoptable + and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) + ) + else: + available = last_updated_success - async_get_ufp_enabled = self._async_get_ufp_enabled - connected_or_adoptable = device.state is StateType.CONNECTED or ( - not device.is_adopted_by_us and device.can_adopt - ) - available = ( - last_updated_success - and connected_or_adoptable - and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) - ) if available != was_available: self._attr_available = available From 925655a7e888981ee5ad6146b32030fdc1a24819 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:55:39 -0500 Subject: [PATCH 19/31] cleanups --- homeassistant/components/unifiprotect/entity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index ed2648698347f..680d819a360a2 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -207,7 +207,9 @@ def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: if last_updated_success := self.data.last_update_success: self.device = device - if device.model is not ModelType.NVR: + if device.model is ModelType.NVR: + available = last_updated_success + else: if TYPE_CHECKING: assert isinstance(device, ProtectAdoptableDeviceModel) @@ -220,8 +222,6 @@ def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: and connected_or_adoptable and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) ) - else: - available = last_updated_success if available != was_available: self._attr_available = available From 0c45a6ea0dcfdbb261c0721a5e812100f8512aaa Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 03:58:59 -0500 Subject: [PATCH 20/31] cleanups --- homeassistant/components/unifiprotect/entity.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 680d819a360a2..c13eb76b34edc 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -279,8 +279,6 @@ def _async_update_device_from_protect( class ProtectDeviceEntity(BaseProtectEntity): """Base class for UniFi protect entities.""" - device: ProtectAdoptableDeviceModel - @callback def _async_set_device_info(self) -> None: self._attr_device_info = DeviceInfo( From bf74c10ed8152b2ce1ce2f4dcd2b84810239c0ba Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:05:20 -0500 Subject: [PATCH 21/31] fix storage --- .../components/unifiprotect/binary_sensor.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index fe2017d2f053b..bada2477c35c8 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -674,18 +674,16 @@ def __init__( def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) slot = self._disk.slot - self._attr_available = False - available = self.data.last_update_success - + ustorage = self.device.system_info.ustorage # should not be possible since it would require user to # _downgrade_ to make ustorage disppear - assert self.device.system_info.ustorage is not None - for disk in self.device.system_info.ustorage.disks: - if disk.slot == slot: - self._disk = disk - self._attr_available = available - break - + assert ustorage is not None + disk = next(iter(d for d in ustorage.disks if d.slot == slot), None) + if disk is None: + # disk was removed + self._attr_available = False + return + self._disk = disk self._attr_is_on = not self._disk.is_healthy From d2a61a0cf0b537288350c152166f085c19ec1d62 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:06:48 -0500 Subject: [PATCH 22/31] fix storage --- homeassistant/components/unifiprotect/binary_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index bada2477c35c8..3712ba2524e63 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -678,7 +678,7 @@ def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: # should not be possible since it would require user to # _downgrade_ to make ustorage disppear assert ustorage is not None - disk = next(iter(d for d in ustorage.disks if d.slot == slot), None) + disk = next((d for d in ustorage.disks if d.slot == slot), None) if disk is None: # disk was removed self._attr_available = False From aa0d7bfa782bbdb52e79c728a22e583c6ae91e42 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:11:28 -0500 Subject: [PATCH 23/31] fix storage --- homeassistant/components/unifiprotect/entity.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index c13eb76b34edc..a0fa45e910124 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -204,25 +204,21 @@ def _async_set_device_info(self) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: """Update Entity object from Protect device.""" was_available = self._attr_available + async_get_ufp_enabled = self._async_get_ufp_enabled if last_updated_success := self.data.last_update_success: self.device = device if device.model is ModelType.NVR: - available = last_updated_success + enabled = connected = True else: if TYPE_CHECKING: assert isinstance(device, ProtectAdoptableDeviceModel) - - async_get_ufp_enabled = self._async_get_ufp_enabled - connected_or_adoptable = device.state is StateType.CONNECTED or ( + connected = device.state is StateType.CONNECTED or ( not device.is_adopted_by_us and device.can_adopt ) - available = ( - last_updated_success - and connected_or_adoptable - and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) - ) + enabled = not async_get_ufp_enabled or async_get_ufp_enabled(device) + available = last_updated_success and connected and enabled if available != was_available: self._attr_available = available From 6da07178ea493dc36da895b7649a8377a77b52a5 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:11:42 -0500 Subject: [PATCH 24/31] fix storage --- homeassistant/components/unifiprotect/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index a0fa45e910124..8d41fc03645f1 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -204,7 +204,6 @@ def _async_set_device_info(self) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: """Update Entity object from Protect device.""" was_available = self._attr_available - async_get_ufp_enabled = self._async_get_ufp_enabled if last_updated_success := self.data.last_update_success: self.device = device @@ -216,6 +215,7 @@ def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: connected = device.state is StateType.CONNECTED or ( not device.is_adopted_by_us and device.can_adopt ) + async_get_ufp_enabled = self._async_get_ufp_enabled enabled = not async_get_ufp_enabled or async_get_ufp_enabled(device) available = last_updated_success and connected and enabled From e0a8f770952a188862b3dc18a7b529958c56e47c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:12:24 -0500 Subject: [PATCH 25/31] fix storage --- homeassistant/components/unifiprotect/entity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 8d41fc03645f1..307fb75097677 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -208,7 +208,7 @@ def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: self.device = device if device.model is ModelType.NVR: - enabled = connected = True + available = last_updated_success else: if TYPE_CHECKING: assert isinstance(device, ProtectAdoptableDeviceModel) @@ -217,8 +217,8 @@ def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: ) async_get_ufp_enabled = self._async_get_ufp_enabled enabled = not async_get_ufp_enabled or async_get_ufp_enabled(device) + available = last_updated_success and connected and enabled - available = last_updated_success and connected and enabled if available != was_available: self._attr_available = available From b0b7361dc04c350a44cf85d375d18e534c150d4e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:14:26 -0500 Subject: [PATCH 26/31] fix storage --- homeassistant/components/unifiprotect/entity.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 307fb75097677..924a2838714a2 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -346,9 +346,8 @@ def _event_already_ended( event object so we need to check the datetime object that was saved from the last time the entity was updated. """ - event = self._event return bool( - event + (event := self._event) and event.end and prev_event and prev_event_end From 90898bb99243538983d3c016f75505ddde388678 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:16:14 -0500 Subject: [PATCH 27/31] fix storage --- homeassistant/components/unifiprotect/entity.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 924a2838714a2..f29d18ce35b96 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -2,7 +2,6 @@ from __future__ import annotations -from abc import ABC, abstractmethod from collections.abc import Callable, Sequence from datetime import datetime from functools import partial @@ -151,7 +150,7 @@ def async_all_device_entities( ) -class BaseProtectEntity(Entity, ABC): +class BaseProtectEntity(Entity): """Base class for UniFi protect entities.""" device: ProtectDeviceType @@ -196,7 +195,6 @@ async def async_update(self) -> None: await self.data.async_refresh() @callback - @abstractmethod def _async_set_device_info(self) -> None: """Set device info.""" From 2c133923d49bbac8a4559844827794c5ac5cf247 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:19:54 -0500 Subject: [PATCH 28/31] typing --- homeassistant/components/unifiprotect/button.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 911c2e97a0f6a..6e6286ce508a8 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -5,7 +5,7 @@ from collections.abc import Sequence from dataclasses import dataclass import logging -from typing import Final +from typing import TYPE_CHECKING, Final from uiprotect.data import ModelType, ProtectAdoptableDeviceModel @@ -174,7 +174,8 @@ class ProtectButton(ProtectDeviceEntity, ButtonEntity): def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) if self.entity_description.key == KEY_ADOPT: - device = self.device + if TYPE_CHECKING: + assert isinstance(device, ProtectAdoptableDeviceModel) self._attr_available = device.can_adopt and device.can_create( self.data.api.bootstrap.auth_user ) From 07079f0448426e1483d66d56819c0e61e235cec0 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:29:13 -0500 Subject: [PATCH 29/31] clean --- .../components/unifiprotect/button.py | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 6e6286ce508a8..e590f43a9ed8e 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -4,6 +4,7 @@ from collections.abc import Sequence from dataclasses import dataclass +from functools import partial import logging from typing import TYPE_CHECKING, Final @@ -119,17 +120,25 @@ async def async_setup_entry( """Discover devices on a UniFi Protect NVR.""" data = entry.runtime_data + adopt_entities = partial( + async_all_device_entities, + data, + ProtectAdoptButton, + unadopted_descs=[ADOPT_BUTTON], + ) + base_entities = partial( + async_all_device_entities, + data, + ProtectButton, + all_descs=ALL_DEVICE_BUTTONS, + model_descriptions=_MODEL_DESCRIPTIONS, + ) + @callback def _add_new_device(device: ProtectAdoptableDeviceModel) -> None: - entities = async_all_device_entities( - data, - ProtectButton, - all_descs=ALL_DEVICE_BUTTONS, - unadopted_descs=[ADOPT_BUTTON], - model_descriptions=_MODEL_DESCRIPTIONS, - ufp_device=device, + async_add_entities( + [*base_entities(ufp_device=device), *adopt_entities(ufp_device=device)] ) - async_add_entities(entities) _async_remove_adopt_button(hass, device) @callback @@ -137,29 +146,13 @@ def _async_add_unadopted_device(device: ProtectAdoptableDeviceModel) -> None: if not device.can_adopt or not device.can_create(data.api.bootstrap.auth_user): _LOGGER.debug("Device is not adoptable: %s", device.id) return - async_add_entities( - async_all_device_entities( - data, - ProtectButton, - unadopted_descs=[ADOPT_BUTTON], - ufp_device=device, - ) - ) + async_add_entities(adopt_entities(ufp_device=device)) data.async_subscribe_adopt(_add_new_device) entry.async_on_unload( async_dispatcher_connect(hass, data.add_signal, _async_add_unadopted_device) ) - - async_add_entities( - async_all_device_entities( - data, - ProtectButton, - all_descs=ALL_DEVICE_BUTTONS, - unadopted_descs=[ADOPT_BUTTON], - model_descriptions=_MODEL_DESCRIPTIONS, - ) - ) + async_add_entities([*base_entities(), *adopt_entities()]) for device in data.get_by_types(DEVICES_THAT_ADOPT): _async_remove_adopt_button(hass, device) @@ -170,17 +163,20 @@ class ProtectButton(ProtectDeviceEntity, ButtonEntity): entity_description: ProtectButtonEntityDescription - @callback - def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: - super()._async_update_device_from_protect(device) - if self.entity_description.key == KEY_ADOPT: - if TYPE_CHECKING: - assert isinstance(device, ProtectAdoptableDeviceModel) - self._attr_available = device.can_adopt and device.can_create( - self.data.api.bootstrap.auth_user - ) - async def async_press(self) -> None: """Press the button.""" if self.entity_description.ufp_press is not None: await getattr(self.device, self.entity_description.ufp_press)() + + +class ProtectAdoptButton(ProtectButton): + """A Ubiquiti UniFi Protect Adopt button.""" + + @callback + def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: + super()._async_update_device_from_protect(device) + if TYPE_CHECKING: + assert isinstance(device, ProtectAdoptableDeviceModel) + self._attr_available = device.can_adopt and device.can_create( + self.data.api.bootstrap.auth_user + ) From fcb8750378bf181815da553fede92faa530e7797 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:30:42 -0500 Subject: [PATCH 30/31] clean --- homeassistant/components/unifiprotect/button.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index e590f43a9ed8e..79985b9c7b250 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -39,7 +39,6 @@ class ProtectButtonEntityDescription( DEVICE_CLASS_CHIME_BUTTON: Final = "unifiprotect__chime_button" -KEY_ADOPT = "adopt" ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = ( @@ -62,7 +61,7 @@ class ProtectButtonEntityDescription( ) ADOPT_BUTTON = ProtectButtonEntityDescription[ProtectAdoptableDeviceModel]( - key=KEY_ADOPT, + key="adopt", name="Adopt device", icon="mdi:plus-circle", ufp_press="adopt", From 31ee74b2e0460ae5848a051592d408927b8f380f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 4 Jul 2024 04:40:01 -0500 Subject: [PATCH 31/31] drop bugfix --- .../components/unifiprotect/binary_sensor.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 3712ba2524e63..fe2017d2f053b 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -674,16 +674,18 @@ def __init__( def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None: super()._async_update_device_from_protect(device) slot = self._disk.slot - ustorage = self.device.system_info.ustorage + self._attr_available = False + available = self.data.last_update_success + # should not be possible since it would require user to # _downgrade_ to make ustorage disppear - assert ustorage is not None - disk = next((d for d in ustorage.disks if d.slot == slot), None) - if disk is None: - # disk was removed - self._attr_available = False - return - self._disk = disk + assert self.device.system_info.ustorage is not None + for disk in self.device.system_info.ustorage.disks: + if disk.slot == slot: + self._disk = disk + self._attr_available = available + break + self._attr_is_on = not self._disk.is_healthy