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,
),
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,

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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
)
)

View File

@ -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():

View File

@ -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():

View File

@ -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():

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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)