diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 34c1119eb60..81832de7d44 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -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 diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index d59ee59b760..c78e8e2f77a 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -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 = ( diff --git a/homeassistant/components/unifiprotect/data.py b/homeassistant/components/unifiprotect/data.py index 2a30e18d586..78a3c5ebac8 100644 --- a/homeassistant/components/unifiprotect/data.py +++ b/homeassistant/components/unifiprotect/data.py @@ -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 diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 6de0a4c57cb..e06d297ef33 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -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 {} diff --git a/homeassistant/components/unifiprotect/light.py b/homeassistant/components/unifiprotect/light.py index 8a6f2f5a371..d84c8406acf 100644 --- a/homeassistant/components/unifiprotect/light.py +++ b/homeassistant/components/unifiprotect/light.py @@ -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 diff --git a/homeassistant/components/unifiprotect/lock.py b/homeassistant/components/unifiprotect/lock.py index c4d56dd1e71..9cef3e19e36 100644 --- a/homeassistant/components/unifiprotect/lock.py +++ b/homeassistant/components/unifiprotect/lock.py @@ -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 diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index 1acd14be130..c4fb1dbe15b 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -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 diff --git a/homeassistant/components/unifiprotect/migrate.py b/homeassistant/components/unifiprotect/migrate.py index 307020caa5c..3273bd80408 100644 --- a/homeassistant/components/unifiprotect/migrate.py +++ b/homeassistant/components/unifiprotect/migrate.py @@ -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 diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index 1cfb4477673..5a3b048e623 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -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: diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 2c6c5fa4cc6..6f5c2cfd0d7 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -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 ( diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index 48337dc416b..7b14a80ed43 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -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: