From 54320ff1340a15bdb4bc6dc9f7c234b6a0252c82 Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Tue, 28 Jun 2022 23:00:26 -0400 Subject: [PATCH] UniFi Protect bugfixes (#74156) --- .../components/unifiprotect/binary_sensor.py | 6 ++-- .../components/unifiprotect/button.py | 4 ++- .../components/unifiprotect/camera.py | 8 +++-- homeassistant/components/unifiprotect/data.py | 34 +++++++++---------- .../components/unifiprotect/entity.py | 2 +- .../components/unifiprotect/light.py | 4 ++- homeassistant/components/unifiprotect/lock.py | 4 ++- .../components/unifiprotect/media_player.py | 4 ++- .../components/unifiprotect/number.py | 4 ++- .../components/unifiprotect/select.py | 4 ++- .../components/unifiprotect/sensor.py | 4 ++- .../components/unifiprotect/switch.py | 4 ++- tests/components/unifiprotect/test_init.py | 5 ++- 13 files changed, 54 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 598e0632fbb..d3bf71a4274 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -271,7 +271,7 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( ufp_perm=PermRequired.NO_WRITE, ), ProtectBinaryEntityDescription( - key="motion", + key="motion_enabled", name="Motion Detection", icon="mdi:walk", entity_category=EntityCategory.DIAGNOSTIC, @@ -383,7 +383,9 @@ async def async_setup_entry( entities += _async_motion_entities(data, ufp_device=device) async_add_entities(entities) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities: list[ProtectDeviceEntity] = async_all_device_entities( data, diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 901139109d3..9440e46b936 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -92,7 +92,9 @@ async def async_setup_entry( ) async_add_entities(entities) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities: list[ProtectDeviceEntity] = async_all_device_entities( data, diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index 336a5ae9187..76bfc72408d 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -120,8 +120,12 @@ async def async_setup_entry( entities = _async_camera_entities(data, ufp_device=device) async_add_entities(entities) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_CHANNELS), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_CHANNELS), _add_new_device) + ) entities = _async_camera_entities(data) async_add_entities(entities) diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index 30887f04235..9e0783a99b1 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -4,17 +4,17 @@ from __future__ import annotations from collections.abc import Callable, Generator, Iterable from datetime import timedelta import logging -from typing import Any +from typing import Any, Union from pyunifiprotect import ProtectApiClient from pyunifiprotect.data import ( + NVR, Bootstrap, Event, EventType, Liveview, ModelType, ProtectAdoptableDeviceModel, - ProtectModelWithId, WSSubscriptionMessage, ) from pyunifiprotect.exceptions import ClientError, NotAuthorized @@ -27,7 +27,6 @@ from homeassistant.helpers.event import async_track_time_interval from .const import ( CONF_DISABLE_RTSP, DEVICES_THAT_ADOPT, - DEVICES_WITH_ENTITIES, DISPATCH_ADOPT, DISPATCH_CHANNELS, DOMAIN, @@ -39,6 +38,7 @@ from .utils import ( ) _LOGGER = logging.getLogger(__name__) +ProtectDeviceType = Union[ProtectAdoptableDeviceModel, NVR] @callback @@ -68,7 +68,7 @@ class ProtectData: self._entry = entry self._hass = hass self._update_interval = update_interval - self._subscriptions: dict[str, list[Callable[[ProtectModelWithId], None]]] = {} + self._subscriptions: dict[str, list[Callable[[ProtectDeviceType], None]]] = {} self._pending_camera_ids: set[str] = set() self._unsub_interval: CALLBACK_TYPE | None = None self._unsub_websocket: CALLBACK_TYPE | None = None @@ -152,7 +152,7 @@ class ProtectData: return obj = message.new_obj - if obj.model in DEVICES_WITH_ENTITIES: + if isinstance(obj, (ProtectAdoptableDeviceModel, NVR)): self._async_signal_device_update(obj) if ( obj.model == ModelType.CAMERA @@ -211,41 +211,41 @@ class ProtectData: @callback def async_subscribe_device_id( - self, device_id: str, update_callback: Callable[[ProtectModelWithId], None] + self, mac: str, update_callback: Callable[[ProtectDeviceType], None] ) -> CALLBACK_TYPE: """Add an callback subscriber.""" if not self._subscriptions: self._unsub_interval = async_track_time_interval( self._hass, self.async_refresh, self._update_interval ) - self._subscriptions.setdefault(device_id, []).append(update_callback) + self._subscriptions.setdefault(mac, []).append(update_callback) def _unsubscribe() -> None: - self.async_unsubscribe_device_id(device_id, update_callback) + self.async_unsubscribe_device_id(mac, update_callback) return _unsubscribe @callback def async_unsubscribe_device_id( - self, device_id: str, update_callback: Callable[[ProtectModelWithId], None] + self, mac: str, update_callback: Callable[[ProtectDeviceType], None] ) -> None: """Remove a callback subscriber.""" - self._subscriptions[device_id].remove(update_callback) - if not self._subscriptions[device_id]: - del self._subscriptions[device_id] + self._subscriptions[mac].remove(update_callback) + if not self._subscriptions[mac]: + del self._subscriptions[mac] if not self._subscriptions and self._unsub_interval: self._unsub_interval() self._unsub_interval = None @callback - def _async_signal_device_update(self, device: ProtectModelWithId) -> None: + def _async_signal_device_update(self, device: ProtectDeviceType) -> None: """Call the callbacks for a device_id.""" - device_id = device.id - if not self._subscriptions.get(device_id): + + if not self._subscriptions.get(device.mac): return - _LOGGER.debug("Updating device: %s", device_id) - for update_callback in self._subscriptions[device_id]: + _LOGGER.debug("Updating device: %s (%s)", device.name, device.mac) + for update_callback in self._subscriptions[device.mac]: update_callback(device) diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index b7419d0a41e..e68e5cfb81d 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -215,7 +215,7 @@ class ProtectDeviceEntity(Entity): await super().async_added_to_hass() self.async_on_remove( self.data.async_subscribe_device_id( - self.device.id, self._async_updated_event + self.device.mac, self._async_updated_event ) ) diff --git a/homeassistant/components/unifiprotect/light.py b/homeassistant/components/unifiprotect/light.py index fdfe41bca3c..588b99b38d7 100644 --- a/homeassistant/components/unifiprotect/light.py +++ b/homeassistant/components/unifiprotect/light.py @@ -42,7 +42,9 @@ async def async_setup_entry( ): async_add_entities([ProtectLight(data, device)]) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities = [] for device in data.api.bootstrap.lights.values(): diff --git a/homeassistant/components/unifiprotect/lock.py b/homeassistant/components/unifiprotect/lock.py index 400d463050e..0a203308d1e 100644 --- a/homeassistant/components/unifiprotect/lock.py +++ b/homeassistant/components/unifiprotect/lock.py @@ -40,7 +40,9 @@ async def async_setup_entry( if isinstance(device, Doorlock): async_add_entities([ProtectLock(data, device)]) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities = [] for device in data.api.bootstrap.doorlocks.values(): diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index 41109c053f6..d4046e4b8b7 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -49,7 +49,9 @@ async def async_setup_entry( if isinstance(device, Camera) and device.feature_flags.has_speaker: async_add_entities([ProtectMediaPlayer(data, device)]) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities = [] for device in data.api.bootstrap.cameras.values(): diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index a017d2330b6..ed9faf4da40 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -206,7 +206,9 @@ async def async_setup_entry( ) async_add_entities(entities) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities: list[ProtectDeviceEntity] = async_all_device_entities( data, diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 5ea956ca603..e398e6692b0 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -336,7 +336,9 @@ async def async_setup_entry( ) async_add_entities(entities) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities: list[ProtectDeviceEntity] = async_all_device_entities( data, diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index a46e4c790b7..7a9f4652a2e 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -613,7 +613,9 @@ async def async_setup_entry( entities += _async_motion_entities(data, ufp_device=device) async_add_entities(entities) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities: list[ProtectDeviceEntity] = async_all_device_entities( data, diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index 71812459b95..5bc4e1f17eb 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -318,7 +318,9 @@ async def async_setup_entry( ) async_add_entities(entities) - async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + entry.async_on_unload( + async_dispatcher_connect(hass, _ufpd(entry, DISPATCH_ADOPT), _add_new_device) + ) entities: list[ProtectDeviceEntity] = async_all_device_entities( data, diff --git a/tests/components/unifiprotect/test_init.py b/tests/components/unifiprotect/test_init.py index c0ad30ad115..f6f0645df18 100644 --- a/tests/components/unifiprotect/test_init.py +++ b/tests/components/unifiprotect/test_init.py @@ -109,11 +109,10 @@ async def test_reload(hass: HomeAssistant, ufp: MockUFPFixture): assert ufp.api.async_disconnect_ws.called -async def test_unload(hass: HomeAssistant, ufp: MockUFPFixture): +async def test_unload(hass: HomeAssistant, ufp: MockUFPFixture, light: Light): """Test unloading of unifiprotect entry.""" - await hass.config_entries.async_setup(ufp.entry.entry_id) - await hass.async_block_till_done() + await init_entry(hass, ufp, [light]) assert ufp.entry.state == ConfigEntryState.LOADED await hass.config_entries.async_unload(ufp.entry.entry_id)