UniFi Protect bugfixes (#74156)

This commit is contained in:
Christopher Bailey 2022-06-28 23:00:26 -04:00 committed by GitHub
parent 309cf030b0
commit 54320ff134
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 54 additions and 33 deletions

View File

@ -271,7 +271,7 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ufp_perm=PermRequired.NO_WRITE, ufp_perm=PermRequired.NO_WRITE,
), ),
ProtectBinaryEntityDescription( ProtectBinaryEntityDescription(
key="motion", key="motion_enabled",
name="Motion Detection", name="Motion Detection",
icon="mdi:walk", icon="mdi:walk",
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
@ -383,7 +383,9 @@ async def async_setup_entry(
entities += _async_motion_entities(data, ufp_device=device) entities += _async_motion_entities(data, ufp_device=device)
async_add_entities(entities) 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( entities: list[ProtectDeviceEntity] = async_all_device_entities(
data, data,

View File

@ -92,7 +92,9 @@ async def async_setup_entry(
) )
async_add_entities(entities) 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( entities: list[ProtectDeviceEntity] = async_all_device_entities(
data, data,

View File

@ -120,8 +120,12 @@ async def async_setup_entry(
entities = _async_camera_entities(data, ufp_device=device) entities = _async_camera_entities(data, ufp_device=device)
async_add_entities(entities) 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_CHANNELS), _add_new_device) 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) entities = _async_camera_entities(data)
async_add_entities(entities) async_add_entities(entities)

View File

@ -4,17 +4,17 @@ from __future__ import annotations
from collections.abc import Callable, Generator, Iterable from collections.abc import Callable, Generator, Iterable
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any from typing import Any, Union
from pyunifiprotect import ProtectApiClient from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data import ( from pyunifiprotect.data import (
NVR,
Bootstrap, Bootstrap,
Event, Event,
EventType, EventType,
Liveview, Liveview,
ModelType, ModelType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectModelWithId,
WSSubscriptionMessage, WSSubscriptionMessage,
) )
from pyunifiprotect.exceptions import ClientError, NotAuthorized from pyunifiprotect.exceptions import ClientError, NotAuthorized
@ -27,7 +27,6 @@ from homeassistant.helpers.event import async_track_time_interval
from .const import ( from .const import (
CONF_DISABLE_RTSP, CONF_DISABLE_RTSP,
DEVICES_THAT_ADOPT, DEVICES_THAT_ADOPT,
DEVICES_WITH_ENTITIES,
DISPATCH_ADOPT, DISPATCH_ADOPT,
DISPATCH_CHANNELS, DISPATCH_CHANNELS,
DOMAIN, DOMAIN,
@ -39,6 +38,7 @@ from .utils import (
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ProtectDeviceType = Union[ProtectAdoptableDeviceModel, NVR]
@callback @callback
@ -68,7 +68,7 @@ class ProtectData:
self._entry = entry self._entry = entry
self._hass = hass self._hass = hass
self._update_interval = update_interval 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._pending_camera_ids: set[str] = set()
self._unsub_interval: CALLBACK_TYPE | None = None self._unsub_interval: CALLBACK_TYPE | None = None
self._unsub_websocket: CALLBACK_TYPE | None = None self._unsub_websocket: CALLBACK_TYPE | None = None
@ -152,7 +152,7 @@ class ProtectData:
return return
obj = message.new_obj obj = message.new_obj
if obj.model in DEVICES_WITH_ENTITIES: if isinstance(obj, (ProtectAdoptableDeviceModel, NVR)):
self._async_signal_device_update(obj) self._async_signal_device_update(obj)
if ( if (
obj.model == ModelType.CAMERA obj.model == ModelType.CAMERA
@ -211,41 +211,41 @@ class ProtectData:
@callback @callback
def async_subscribe_device_id( def async_subscribe_device_id(
self, device_id: str, update_callback: Callable[[ProtectModelWithId], None] self, mac: str, update_callback: Callable[[ProtectDeviceType], None]
) -> CALLBACK_TYPE: ) -> CALLBACK_TYPE:
"""Add an callback subscriber.""" """Add an callback subscriber."""
if not self._subscriptions: if not self._subscriptions:
self._unsub_interval = async_track_time_interval( self._unsub_interval = async_track_time_interval(
self._hass, self.async_refresh, self._update_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: def _unsubscribe() -> None:
self.async_unsubscribe_device_id(device_id, update_callback) self.async_unsubscribe_device_id(mac, update_callback)
return _unsubscribe return _unsubscribe
@callback @callback
def async_unsubscribe_device_id( def async_unsubscribe_device_id(
self, device_id: str, update_callback: Callable[[ProtectModelWithId], None] self, mac: str, update_callback: Callable[[ProtectDeviceType], None]
) -> None: ) -> None:
"""Remove a callback subscriber.""" """Remove a callback subscriber."""
self._subscriptions[device_id].remove(update_callback) self._subscriptions[mac].remove(update_callback)
if not self._subscriptions[device_id]: if not self._subscriptions[mac]:
del self._subscriptions[device_id] del self._subscriptions[mac]
if not self._subscriptions and self._unsub_interval: if not self._subscriptions and self._unsub_interval:
self._unsub_interval() self._unsub_interval()
self._unsub_interval = None self._unsub_interval = None
@callback @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.""" """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 return
_LOGGER.debug("Updating device: %s", device_id) _LOGGER.debug("Updating device: %s (%s)", device.name, device.mac)
for update_callback in self._subscriptions[device_id]: for update_callback in self._subscriptions[device.mac]:
update_callback(device) update_callback(device)

View File

@ -215,7 +215,7 @@ class ProtectDeviceEntity(Entity):
await super().async_added_to_hass() await super().async_added_to_hass()
self.async_on_remove( self.async_on_remove(
self.data.async_subscribe_device_id( self.data.async_subscribe_device_id(
self.device.id, self._async_updated_event self.device.mac, self._async_updated_event
) )
) )

View File

@ -42,7 +42,9 @@ async def async_setup_entry(
): ):
async_add_entities([ProtectLight(data, device)]) 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 = [] entities = []
for device in data.api.bootstrap.lights.values(): for device in data.api.bootstrap.lights.values():

View File

@ -40,7 +40,9 @@ async def async_setup_entry(
if isinstance(device, Doorlock): if isinstance(device, Doorlock):
async_add_entities([ProtectLock(data, device)]) 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 = [] entities = []
for device in data.api.bootstrap.doorlocks.values(): for device in data.api.bootstrap.doorlocks.values():

View File

@ -49,7 +49,9 @@ async def async_setup_entry(
if isinstance(device, Camera) and device.feature_flags.has_speaker: if isinstance(device, Camera) and device.feature_flags.has_speaker:
async_add_entities([ProtectMediaPlayer(data, device)]) 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 = [] entities = []
for device in data.api.bootstrap.cameras.values(): for device in data.api.bootstrap.cameras.values():

View File

@ -206,7 +206,9 @@ async def async_setup_entry(
) )
async_add_entities(entities) 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( entities: list[ProtectDeviceEntity] = async_all_device_entities(
data, data,

View File

@ -336,7 +336,9 @@ async def async_setup_entry(
) )
async_add_entities(entities) 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( entities: list[ProtectDeviceEntity] = async_all_device_entities(
data, data,

View File

@ -613,7 +613,9 @@ async def async_setup_entry(
entities += _async_motion_entities(data, ufp_device=device) entities += _async_motion_entities(data, ufp_device=device)
async_add_entities(entities) 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( entities: list[ProtectDeviceEntity] = async_all_device_entities(
data, data,

View File

@ -318,7 +318,9 @@ async def async_setup_entry(
) )
async_add_entities(entities) 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( entities: list[ProtectDeviceEntity] = async_all_device_entities(
data, data,

View File

@ -109,11 +109,10 @@ async def test_reload(hass: HomeAssistant, ufp: MockUFPFixture):
assert ufp.api.async_disconnect_ws.called 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.""" """Test unloading of unifiprotect entry."""
await hass.config_entries.async_setup(ufp.entry.entry_id) await init_entry(hass, ufp, [light])
await hass.async_block_till_done()
assert ufp.entry.state == ConfigEntryState.LOADED assert ufp.entry.state == ConfigEntryState.LOADED
await hass.config_entries.async_unload(ufp.entry.entry_id) await hass.config_entries.async_unload(ufp.entry.entry_id)