Cleanup device callbacks in unifiprotect (#73463)
parent
0007178d63
commit
9d13252142
|
@ -5,7 +5,15 @@ from copy import copy
|
|||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from pyunifiprotect.data import NVR, Camera, Event, Light, MountType, Sensor
|
||||
from pyunifiprotect.data import (
|
||||
NVR,
|
||||
Camera,
|
||||
Event,
|
||||
Light,
|
||||
MountType,
|
||||
ProtectModelWithId,
|
||||
Sensor,
|
||||
)
|
||||
from pyunifiprotect.data.nvr import UOSDisk
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
|
@ -205,8 +213,8 @@ class ProtectDeviceBinarySensor(ProtectDeviceEntity, BinarySensorEntity):
|
|||
entity_description: ProtectBinaryEntityDescription
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
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)
|
||||
# UP Sense can be any of the 3 contact sensor device classes
|
||||
|
@ -240,8 +248,8 @@ class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity):
|
|||
super().__init__(data, device, description)
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
|
||||
slot = self._disk.slot
|
||||
self._attr_available = False
|
||||
|
|
|
@ -5,7 +5,12 @@ from collections.abc import Generator
|
|||
import logging
|
||||
|
||||
from pyunifiprotect.api import ProtectApiClient
|
||||
from pyunifiprotect.data import Camera as UFPCamera, CameraChannel, StateType
|
||||
from pyunifiprotect.data import (
|
||||
Camera as UFPCamera,
|
||||
CameraChannel,
|
||||
ProtectModelWithId,
|
||||
StateType,
|
||||
)
|
||||
|
||||
from homeassistant.components.camera import Camera, CameraEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -137,8 +142,8 @@ class ProtectCamera(ProtectDeviceEntity, Camera):
|
|||
)
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
self.channel = self.device.channels[self.channel.id]
|
||||
motion_enabled = self.device.recording_settings.enable_motion_detection
|
||||
self._attr_motion_detection_enabled = (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Base class for protect data."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator, Iterable
|
||||
from collections.abc import Callable, Generator, Iterable
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
@ -12,9 +12,10 @@ from pyunifiprotect.data import (
|
|||
Event,
|
||||
Liveview,
|
||||
ModelType,
|
||||
ProtectAdoptableDeviceModel,
|
||||
ProtectModelWithId,
|
||||
WSSubscriptionMessage,
|
||||
)
|
||||
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
|
||||
from pyunifiprotect.exceptions import ClientError, NotAuthorized
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -54,7 +55,7 @@ class ProtectData:
|
|||
self._entry = entry
|
||||
self._hass = hass
|
||||
self._update_interval = update_interval
|
||||
self._subscriptions: dict[str, list[CALLBACK_TYPE]] = {}
|
||||
self._subscriptions: dict[str, list[Callable[[ProtectModelWithId], None]]] = {}
|
||||
self._unsub_interval: CALLBACK_TYPE | None = None
|
||||
self._unsub_websocket: CALLBACK_TYPE | None = None
|
||||
|
||||
|
@ -123,7 +124,7 @@ class ProtectData:
|
|||
return
|
||||
|
||||
if message.new_obj.model in DEVICES_WITH_ENTITIES:
|
||||
self.async_signal_device_id_update(message.new_obj.id)
|
||||
self._async_signal_device_update(message.new_obj)
|
||||
# trigger update for all Cameras with LCD screens when NVR Doorbell settings updates
|
||||
if "doorbell_settings" in message.changed_data:
|
||||
_LOGGER.debug(
|
||||
|
@ -132,15 +133,15 @@ class ProtectData:
|
|||
self.api.bootstrap.nvr.update_all_messages()
|
||||
for camera in self.api.bootstrap.cameras.values():
|
||||
if camera.feature_flags.has_lcd_screen:
|
||||
self.async_signal_device_id_update(camera.id)
|
||||
self._async_signal_device_update(camera)
|
||||
# trigger updates for camera that the event references
|
||||
elif isinstance(message.new_obj, Event):
|
||||
if message.new_obj.camera is not None:
|
||||
self.async_signal_device_id_update(message.new_obj.camera.id)
|
||||
self._async_signal_device_update(message.new_obj.camera)
|
||||
elif message.new_obj.light is not None:
|
||||
self.async_signal_device_id_update(message.new_obj.light.id)
|
||||
self._async_signal_device_update(message.new_obj.light)
|
||||
elif message.new_obj.sensor is not None:
|
||||
self.async_signal_device_id_update(message.new_obj.sensor.id)
|
||||
self._async_signal_device_update(message.new_obj.sensor)
|
||||
# alert user viewport needs restart so voice clients can get new options
|
||||
elif len(self.api.bootstrap.viewers) > 0 and isinstance(
|
||||
message.new_obj, Liveview
|
||||
|
@ -157,13 +158,13 @@ class ProtectData:
|
|||
if updates is None:
|
||||
return
|
||||
|
||||
self.async_signal_device_id_update(self.api.bootstrap.nvr.id)
|
||||
self._async_signal_device_update(self.api.bootstrap.nvr)
|
||||
for device in async_get_devices(self.api.bootstrap, DEVICES_THAT_ADOPT):
|
||||
self.async_signal_device_id_update(device.id)
|
||||
self._async_signal_device_update(device)
|
||||
|
||||
@callback
|
||||
def async_subscribe_device_id(
|
||||
self, device_id: str, update_callback: CALLBACK_TYPE
|
||||
self, device_id: str, update_callback: Callable[[ProtectModelWithId], None]
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Add an callback subscriber."""
|
||||
if not self._subscriptions:
|
||||
|
@ -179,7 +180,7 @@ class ProtectData:
|
|||
|
||||
@callback
|
||||
def async_unsubscribe_device_id(
|
||||
self, device_id: str, update_callback: CALLBACK_TYPE
|
||||
self, device_id: str, update_callback: Callable[[ProtectModelWithId], None]
|
||||
) -> None:
|
||||
"""Remove a callback subscriber."""
|
||||
self._subscriptions[device_id].remove(update_callback)
|
||||
|
@ -190,14 +191,15 @@ class ProtectData:
|
|||
self._unsub_interval = None
|
||||
|
||||
@callback
|
||||
def async_signal_device_id_update(self, device_id: str) -> None:
|
||||
def _async_signal_device_update(self, device: ProtectModelWithId) -> None:
|
||||
"""Call the callbacks for a device_id."""
|
||||
device_id = device.id
|
||||
if not self._subscriptions.get(device_id):
|
||||
return
|
||||
|
||||
_LOGGER.debug("Updating device: %s", device_id)
|
||||
for update_callback in self._subscriptions[device_id]:
|
||||
update_callback()
|
||||
update_callback(device)
|
||||
|
||||
|
||||
@callback
|
||||
|
|
|
@ -14,6 +14,7 @@ from pyunifiprotect.data import (
|
|||
Light,
|
||||
ModelType,
|
||||
ProtectAdoptableDeviceModel,
|
||||
ProtectModelWithId,
|
||||
Sensor,
|
||||
StateType,
|
||||
Viewer,
|
||||
|
@ -26,7 +27,7 @@ from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
|||
from .const import ATTR_EVENT_SCORE, DEFAULT_ATTRIBUTION, DEFAULT_BRAND, DOMAIN
|
||||
from .data import ProtectData
|
||||
from .models import ProtectRequiredKeysMixin
|
||||
from .utils import async_device_by_id, get_nested_attr
|
||||
from .utils import get_nested_attr
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -127,7 +128,7 @@ class ProtectDeviceEntity(Entity):
|
|||
|
||||
self._attr_attribution = DEFAULT_ATTRIBUTION
|
||||
self._async_set_device_info()
|
||||
self._async_update_device_from_protect()
|
||||
self._async_update_device_from_protect(device)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the entity.
|
||||
|
@ -149,14 +150,10 @@ class ProtectDeviceEntity(Entity):
|
|||
)
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
"""Update Entity object from Protect device."""
|
||||
if self.data.last_update_success:
|
||||
assert self.device.model
|
||||
device = async_device_by_id(
|
||||
self.data.api.bootstrap, self.device.id, device_type=self.device.model
|
||||
)
|
||||
assert device is not None
|
||||
assert isinstance(device, ProtectAdoptableDeviceModel)
|
||||
self.device = device
|
||||
|
||||
is_connected = (
|
||||
|
@ -174,9 +171,9 @@ class ProtectDeviceEntity(Entity):
|
|||
self._attr_available = is_connected
|
||||
|
||||
@callback
|
||||
def _async_updated_event(self) -> None:
|
||||
def _async_updated_event(self, device: ProtectModelWithId) -> None:
|
||||
"""Call back for incoming data."""
|
||||
self._async_update_device_from_protect()
|
||||
self._async_update_device_from_protect(device)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
@ -217,7 +214,7 @@ class ProtectNVREntity(ProtectDeviceEntity):
|
|||
)
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
if self.data.last_update_success:
|
||||
self.device = self.data.api.bootstrap.nvr
|
||||
|
||||
|
@ -254,8 +251,8 @@ class EventThumbnailMixin(ProtectDeviceEntity):
|
|||
return attrs
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
self._event = self._async_get_event()
|
||||
|
||||
attrs = self.extra_state_attributes or {}
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyunifiprotect.data import Light
|
||||
from pyunifiprotect.data import Light, ProtectModelWithId
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -59,8 +59,8 @@ class ProtectLight(ProtectDeviceEntity, LightEntity):
|
|||
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
self._attr_is_on = self.device.is_light_on
|
||||
self._attr_brightness = unifi_brightness_to_hass(
|
||||
self.device.light_device_settings.led_level
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyunifiprotect.data import Doorlock, LockStatusType
|
||||
from pyunifiprotect.data import Doorlock, LockStatusType, ProtectModelWithId
|
||||
|
||||
from homeassistant.components.lock import LockEntity, LockEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -56,8 +56,8 @@ class ProtectLock(ProtectDeviceEntity, LockEntity):
|
|||
self._attr_name = f"{self.device.name} Lock"
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
|
||||
self._attr_is_locked = False
|
||||
self._attr_is_locking = False
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyunifiprotect.data import Camera
|
||||
from pyunifiprotect.data import Camera, ProtectModelWithId
|
||||
from pyunifiprotect.exceptions import StreamError
|
||||
|
||||
from homeassistant.components import media_source
|
||||
|
@ -83,8 +83,8 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
|
|||
self._attr_media_content_type = MEDIA_TYPE_MUSIC
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
self._attr_volume_level = float(self.device.speaker_settings.volume / 100)
|
||||
|
||||
if (
|
||||
|
@ -110,7 +110,7 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
|
|||
):
|
||||
_LOGGER.debug("Stopping playback for %s Speaker", self.device.name)
|
||||
await self.device.stop_audio()
|
||||
self._async_updated_event()
|
||||
self._async_updated_event(self.device)
|
||||
|
||||
async def async_play_media(
|
||||
self, media_type: str, media_id: str, **kwargs: Any
|
||||
|
@ -134,11 +134,11 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
|
|||
raise HomeAssistantError(err) from err
|
||||
else:
|
||||
# update state after starting player
|
||||
self._async_updated_event()
|
||||
self._async_updated_event(self.device)
|
||||
# wait until player finishes to update state again
|
||||
await self.device.wait_until_audio_completes()
|
||||
|
||||
self._async_updated_event()
|
||||
self._async_updated_event(self.device)
|
||||
|
||||
async def async_browse_media(
|
||||
self, media_content_type: str | None = None, media_content_id: str | None = None
|
||||
|
|
|
@ -147,12 +147,12 @@ async def async_migrate_device_ids(
|
|||
try:
|
||||
registry.async_update_entity(entity.entity_id, new_unique_id=new_unique_id)
|
||||
except ValueError as err:
|
||||
print(err)
|
||||
_LOGGER.warning(
|
||||
"Could not migrate entity %s (old unique_id: %s, new unique_id: %s)",
|
||||
"Could not migrate entity %s (old unique_id: %s, new unique_id: %s): %s",
|
||||
entity.entity_id,
|
||||
entity.unique_id,
|
||||
new_unique_id,
|
||||
err,
|
||||
)
|
||||
else:
|
||||
count += 1
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
from pyunifiprotect.data import Camera, Doorlock, Light
|
||||
from pyunifiprotect.data import Camera, Doorlock, Light, ProtectModelWithId
|
||||
|
||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -203,8 +203,8 @@ class ProtectNumbers(ProtectDeviceEntity, NumberEntity):
|
|||
self._attr_native_step = self.entity_description.ufp_step
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
self._attr_native_value = self.entity_description.get_ufp_value(self.device)
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
|
|
|
@ -19,6 +19,7 @@ from pyunifiprotect.data import (
|
|||
LightModeEnableType,
|
||||
LightModeType,
|
||||
MountType,
|
||||
ProtectModelWithId,
|
||||
RecordingMode,
|
||||
Sensor,
|
||||
Viewer,
|
||||
|
@ -355,8 +356,8 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity):
|
|||
self._async_set_options()
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
|
||||
# entities with categories are not exposed for voice and safe to update dynamically
|
||||
if (
|
||||
|
|
|
@ -12,6 +12,7 @@ from pyunifiprotect.data import (
|
|||
Event,
|
||||
ProtectAdoptableDeviceModel,
|
||||
ProtectDeviceModel,
|
||||
ProtectModelWithId,
|
||||
Sensor,
|
||||
)
|
||||
|
||||
|
@ -540,8 +541,8 @@ class ProtectDeviceSensor(ProtectDeviceEntity, SensorEntity):
|
|||
super().__init__(data, device, description)
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
self._attr_native_value = self.entity_description.get_ufp_value(self.device)
|
||||
|
||||
|
||||
|
@ -560,8 +561,8 @@ class ProtectNVRSensor(ProtectNVREntity, SensorEntity):
|
|||
super().__init__(data, device, description)
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
super()._async_update_device_from_protect()
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
super()._async_update_device_from_protect(device)
|
||||
self._attr_native_value = self.entity_description.get_ufp_value(self.device)
|
||||
|
||||
|
||||
|
@ -585,9 +586,9 @@ class ProtectEventSensor(ProtectDeviceSensor, EventThumbnailMixin):
|
|||
return event
|
||||
|
||||
@callback
|
||||
def _async_update_device_from_protect(self) -> None:
|
||||
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
|
||||
# do not call ProtectDeviceSensor method since we want event to get value here
|
||||
EventThumbnailMixin._async_update_device_from_protect(self)
|
||||
EventThumbnailMixin._async_update_device_from_protect(self, device)
|
||||
if self._event is None:
|
||||
self._attr_native_value = OBJECT_TYPE_NONE
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue