diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index fe4399c4c6d..668fe479e1f 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -556,12 +556,13 @@ class ProtectDeviceBinarySensor(ProtectDeviceEntity, BinarySensorEntity): @callback 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) + entity_description = self.entity_description + updated_device = self.device + self._attr_is_on = entity_description.get_ufp_value(updated_device) # UP Sense can be any of the 3 contact sensor device classes - if self.entity_description.key == _KEY_DOOR and isinstance(self.device, Sensor): - self.entity_description.device_class = MOUNT_DEVICE_CLASS_MAP.get( - self.device.mount_type, BinarySensorDeviceClass.DOOR + if entity_description.key == _KEY_DOOR and isinstance(updated_device, Sensor): + entity_description.device_class = MOUNT_DEVICE_CLASS_MAP.get( + updated_device.mount_type, BinarySensorDeviceClass.DOOR ) @@ -615,7 +616,7 @@ class ProtectEventBinarySensor(EventEntityMixin, BinarySensorEntity): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) - is_on = self.entity_description.get_is_on(device) + is_on = self.entity_description.get_is_on(self._event) self._attr_is_on: bool | None = is_on if not is_on: self._event = None diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 8c620402e77..3306743b707 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -183,7 +183,8 @@ class ProtectButton(ProtectDeviceEntity, ButtonEntity): super()._async_update_device_from_protect(device) if self.entity_description.key == KEY_ADOPT: - self._attr_available = self.device.can_adopt and self.device.can_create( + device = self.device + self._attr_available = device.can_adopt and device.can_create( self.data.api.bootstrap.auth_user ) diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index 019ff7b7863..481d51ec529 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -151,23 +151,25 @@ class ProtectCamera(ProtectDeviceEntity, Camera): self._disable_stream = disable_stream self._last_image: bytes | None = None super().__init__(data, camera) + device = self.device if self._secure: - self._attr_unique_id = f"{self.device.mac}_{self.channel.id}" - self._attr_name = f"{self.device.display_name} {self.channel.name}" + self._attr_unique_id = f"{device.mac}_{channel.id}" + self._attr_name = f"{device.display_name} {channel.name}" else: - self._attr_unique_id = f"{self.device.mac}_{self.channel.id}_insecure" - self._attr_name = f"{self.device.display_name} {self.channel.name} Insecure" + self._attr_unique_id = f"{device.mac}_{channel.id}_insecure" + self._attr_name = f"{device.display_name} {channel.name} Insecure" # only the default (first) channel is enabled by default self._attr_entity_registry_enabled_default = is_default and secure @callback def _async_set_stream_source(self) -> None: disable_stream = self._disable_stream - if not self.channel.is_rtsp_enabled: + channel = self.channel + + if not channel.is_rtsp_enabled: disable_stream = False - channel = self.channel rtsp_url = channel.rtsps_url if self._secure else channel.rtsp_url # _async_set_stream_source called by __init__ @@ -182,27 +184,30 @@ class ProtectCamera(ProtectDeviceEntity, Camera): @callback 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 + updated_device = self.device + channel = updated_device.channels[self.channel.id] + self.channel = channel + motion_enabled = updated_device.recording_settings.enable_motion_detection self._attr_motion_detection_enabled = ( motion_enabled if motion_enabled is not None else True ) self._attr_is_recording = ( - self.device.state == StateType.CONNECTED and self.device.is_recording + updated_device.state == StateType.CONNECTED and updated_device.is_recording ) is_connected = ( - self.data.last_update_success and self.device.state == StateType.CONNECTED + self.data.last_update_success + and updated_device.state == StateType.CONNECTED ) # some cameras have detachable lens that could cause the camera to be offline - self._attr_available = is_connected and self.device.is_video_ready + self._attr_available = is_connected and updated_device.is_video_ready self._async_set_stream_source() self._attr_extra_state_attributes = { - ATTR_WIDTH: self.channel.width, - ATTR_HEIGHT: self.channel.height, - ATTR_FPS: self.channel.fps, - ATTR_BITRATE: self.channel.bitrate, - ATTR_CHANNEL_ID: self.channel.id, + ATTR_WIDTH: channel.width, + ATTR_HEIGHT: channel.height, + ATTR_FPS: channel.fps, + ATTR_BITRATE: channel.bitrate, + ATTR_CHANNEL_ID: channel.id, } async def async_camera_image( diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 79ee483dd8d..fa85a0629cb 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -297,10 +297,12 @@ class ProtectNVREntity(ProtectDeviceEntity): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: - if self.data.last_update_success: - self.device = self.data.api.bootstrap.nvr + data = self.data + last_update_success = data.last_update_success + if last_update_success: + self.device = data.api.bootstrap.nvr - self._attr_available = self.data.last_update_success + self._attr_available = last_update_success class EventEntityMixin(ProtectDeviceEntity): @@ -317,24 +319,15 @@ class EventEntityMixin(ProtectDeviceEntity): super().__init__(*args, **kwarg) self._event: Event | None = None - @callback - def _async_event_extra_attrs(self) -> dict[str, Any]: - attrs: dict[str, Any] = {} - - if self._event is None: - return attrs - - attrs[ATTR_EVENT_ID] = self._event.id - attrs[ATTR_EVENT_SCORE] = self._event.score - return attrs - @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: + event = self.entity_description.get_event_obj(device) + if event is not None: + self._attr_extra_state_attributes = { + ATTR_EVENT_ID: event.id, + ATTR_EVENT_SCORE: event.score, + } + else: + self._attr_extra_state_attributes = {} + self._event = event super()._async_update_device_from_protect(device) - self._event = self.entity_description.get_event_obj(device) - - attrs = self.extra_state_attributes or {} - self._attr_extra_state_attributes = { - **attrs, - **self._async_event_extra_attrs(), - } diff --git a/homeassistant/components/unifiprotect/light.py b/homeassistant/components/unifiprotect/light.py index 500b4b4703e..38ce73828c2 100644 --- a/homeassistant/components/unifiprotect/light.py +++ b/homeassistant/components/unifiprotect/light.py @@ -73,9 +73,10 @@ class ProtectLight(ProtectDeviceEntity, LightEntity): @callback 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 + updated_device = self.device + self._attr_is_on = updated_device.is_light_on self._attr_brightness = unifi_brightness_to_hass( - self.device.light_device_settings.led_level + updated_device.light_device_settings.led_level ) async def async_turn_on(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/unifiprotect/lock.py b/homeassistant/components/unifiprotect/lock.py index 4fa9ebf4001..791a5e958ea 100644 --- a/homeassistant/components/unifiprotect/lock.py +++ b/homeassistant/components/unifiprotect/lock.py @@ -73,18 +73,19 @@ class ProtectLock(ProtectDeviceEntity, LockEntity): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) + lock_status = self.device.lock_status self._attr_is_locked = False self._attr_is_locking = False self._attr_is_unlocking = False self._attr_is_jammed = False - if self.device.lock_status == LockStatusType.CLOSED: + if lock_status == LockStatusType.CLOSED: self._attr_is_locked = True - elif self.device.lock_status == LockStatusType.CLOSING: + elif lock_status == LockStatusType.CLOSING: self._attr_is_locking = True - elif self.device.lock_status == LockStatusType.OPENING: + elif lock_status == LockStatusType.OPENING: self._attr_is_unlocking = True - elif self.device.lock_status in ( + elif lock_status in ( LockStatusType.FAILED_WHILE_CLOSING, LockStatusType.FAILED_WHILE_OPENING, LockStatusType.JAMMED_WHILE_CLOSING, @@ -92,7 +93,7 @@ class ProtectLock(ProtectDeviceEntity, LockEntity): ): self._attr_is_jammed = True # lock is not fully initialized yet - elif self.device.lock_status != LockStatusType.OPEN: + elif lock_status != LockStatusType.OPEN: self._attr_available = False async def async_unlock(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/unifiprotect/media_player.py b/homeassistant/components/unifiprotect/media_player.py index 4704c42762e..c3f4e58e247 100644 --- a/homeassistant/components/unifiprotect/media_player.py +++ b/homeassistant/components/unifiprotect/media_player.py @@ -98,21 +98,22 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity): @callback 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) + updated_device = self.device + self._attr_volume_level = float(updated_device.speaker_settings.volume / 100) if ( - self.device.talkback_stream is not None - and self.device.talkback_stream.is_running + updated_device.talkback_stream is not None + and updated_device.talkback_stream.is_running ): self._attr_state = MediaPlayerState.PLAYING else: self._attr_state = MediaPlayerState.IDLE is_connected = self.data.last_update_success and ( - self.device.state == StateType.CONNECTED - or (not self.device.is_adopted_by_us and self.device.can_adopt) + updated_device.state == StateType.CONNECTED + or (not updated_device.is_adopted_by_us and updated_device.can_adopt) ) - self._attr_available = is_connected and self.device.feature_flags.has_speaker + self._attr_available = is_connected and updated_device.feature_flags.has_speaker async def async_set_volume_level(self, volume: float) -> None: """Set volume level, range 0..1.""" diff --git a/homeassistant/components/unifiprotect/models.py b/homeassistant/components/unifiprotect/models.py index 8c688231628..375784d0323 100644 --- a/homeassistant/components/unifiprotect/models.py +++ b/homeassistant/components/unifiprotect/models.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections.abc import Callable, Coroutine from dataclasses import dataclass -from datetime import timedelta from enum import Enum import logging from typing import Any, Generic, TypeVar, cast @@ -77,10 +76,8 @@ class ProtectEventMixin(ProtectRequiredKeysMixin[T]): return cast(Event, get_nested_attr(obj, self.ufp_event_obj)) return None - def get_is_on(self, obj: T) -> bool: + def get_is_on(self, event: Event | None) -> bool: """Return value if event is active.""" - - event = self.get_event_obj(obj) if event is None: return False @@ -88,17 +85,7 @@ class ProtectEventMixin(ProtectRequiredKeysMixin[T]): value = now > event.start if value and event.end is not None and now > event.end: value = False - # only log if the recent ended recently - if event.end + timedelta(seconds=10) < now: - _LOGGER.debug( - "%s (%s): end ended at %s", - self.name, - obj.mac, - event.end.isoformat(), - ) - if value: - _LOGGER.debug("%s (%s): value is on", self.name, obj.mac) return value diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 753563023f4..26a03fb7967 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -356,15 +356,15 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) - + entity_description = self.entity_description # entities with categories are not exposed for voice # and safe to update dynamically if ( - self.entity_description.entity_category is not None - and self.entity_description.ufp_options_fn is not None + entity_description.entity_category is not None + and entity_description.ufp_options_fn is not None ): _LOGGER.debug( - "Updating dynamic select options for %s", self.entity_description.name + "Updating dynamic select options for %s", entity_description.name ) self._async_set_options() diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index dec6f10a57f..d842b13b015 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -710,15 +710,6 @@ class ProtectDeviceSensor(ProtectDeviceEntity, SensorEntity): entity_description: ProtectSensorEntityDescription - def __init__( - self, - data: ProtectData, - device: ProtectAdoptableDeviceModel, - description: ProtectSensorEntityDescription, - ) -> None: - """Initialize an UniFi Protect sensor.""" - super().__init__(data, device, description) - @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) @@ -730,15 +721,6 @@ class ProtectNVRSensor(ProtectNVREntity, SensorEntity): entity_description: ProtectSensorEntityDescription - def __init__( - self, - data: ProtectData, - device: NVR, - description: ProtectSensorEntityDescription, - ) -> None: - """Initialize an UniFi Protect sensor.""" - super().__init__(data, device, description) - @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) @@ -750,32 +732,22 @@ class ProtectEventSensor(EventEntityMixin, SensorEntity): entity_description: ProtectSensorEventEntityDescription - def __init__( - self, - data: ProtectData, - device: ProtectAdoptableDeviceModel, - description: ProtectSensorEventEntityDescription, - ) -> None: - """Initialize an UniFi Protect sensor.""" - super().__init__(data, device, description) - @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: # do not call ProtectDeviceSensor method since we want event to get value here EventEntityMixin._async_update_device_from_protect(self, device) - is_on = self.entity_description.get_is_on(device) + event = self._event + entity_description = self.entity_description + is_on = entity_description.get_is_on(event) is_license_plate = ( - self.entity_description.ufp_event_obj == "last_license_plate_detect_event" + entity_description.ufp_event_obj == "last_license_plate_detect_event" ) if ( not is_on - or self._event is None + or event is None or ( is_license_plate - and ( - self._event.metadata is None - or self._event.metadata.license_plate is None - ) + and (event.metadata is None or event.metadata.license_plate is None) ) ): self._attr_native_value = OBJECT_TYPE_NONE @@ -785,6 +757,6 @@ class ProtectEventSensor(EventEntityMixin, SensorEntity): if is_license_plate: # type verified above - self._attr_native_value = self._event.metadata.license_plate.name # type: ignore[union-attr] + self._attr_native_value = event.metadata.license_plate.name # type: ignore[union-attr] else: - self._attr_native_value = self._event.smart_detect_types[0].value + self._attr_native_value = event.smart_detect_types[0].value