1
mirror of https://github.com/home-assistant/core synced 2024-09-03 08:14:07 +02:00

Cleanup device callbacks in unifiprotect (#73463)

This commit is contained in:
J. Nick Koston 2022-06-20 22:52:41 -05:00 committed by GitHub
parent 0007178d63
commit 9d13252142
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 74 additions and 60 deletions

View File

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

View File

@ -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 = (

View File

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

View File

@ -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 {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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