mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
UniFi Protect bugfixes (#74156)
This commit is contained in:
parent
309cf030b0
commit
54320ff134
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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():
|
||||||
|
@ -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():
|
||||||
|
@ -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():
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user