From b842e26d36b3179fcd1e86e5d955a1c8c644408b Mon Sep 17 00:00:00 2001 From: Christopher Bailey Date: Mon, 28 Nov 2022 14:07:53 -0500 Subject: [PATCH] Split UniFi Protect object sensor into multiple (#82595) --- .../components/unifiprotect/binary_sensor.py | 94 +++++++++++++++---- .../components/unifiprotect/const.py | 3 + .../components/unifiprotect/entity.py | 48 +++++----- .../components/unifiprotect/migrate.py | 22 ++++- .../components/unifiprotect/models.py | 39 +++++++- .../components/unifiprotect/sensor.py | 53 ++++++----- .../components/unifiprotect/strings.json | 4 + .../unifiprotect/translations/en.json | 4 + .../unifiprotect/test_binary_sensor.py | 9 +- tests/components/unifiprotect/test_repairs.py | 72 ++++++++++++-- tests/components/unifiprotect/test_sensor.py | 13 ++- 11 files changed, 274 insertions(+), 87 deletions(-) diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 05f7b37d6c8..3d018d6eee1 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -8,13 +8,13 @@ import logging from pyunifiprotect.data import ( NVR, Camera, - Event, Light, ModelType, MountType, ProtectAdoptableDeviceModel, ProtectModelWithId, Sensor, + SmartDetectObjectType, ) from pyunifiprotect.data.nvr import UOSDisk @@ -29,15 +29,15 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DISPATCH_ADOPT, DOMAIN +from .const import DEVICE_CLASS_DETECTION, DISPATCH_ADOPT, DOMAIN from .data import ProtectData from .entity import ( - EventThumbnailMixin, + EventEntityMixin, ProtectDeviceEntity, ProtectNVREntity, async_all_device_entities, ) -from .models import PermRequired, ProtectRequiredKeysMixin +from .models import PermRequired, ProtectEventMixin, ProtectRequiredKeysMixin from .utils import async_dispatch_id as _ufpd _LOGGER = logging.getLogger(__name__) @@ -51,6 +51,13 @@ class ProtectBinaryEntityDescription( """Describes UniFi Protect Binary Sensor entity.""" +@dataclass +class ProtectBinaryEventEntityDescription( + ProtectEventMixin, BinarySensorEntityDescription +): + """Describes UniFi Protect Binary Sensor entity.""" + + MOUNT_DEVICE_CLASS_MAP = { MountType.GARAGE: BinarySensorDeviceClass.GARAGE_DOOR, MountType.WINDOW: BinarySensorDeviceClass.WINDOW, @@ -179,7 +186,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( ProtectBinaryEntityDescription( key="smart_face", name="Detections: Face", - icon="mdi:human-greeting", + icon="mdi:mdi-face", entity_category=EntityCategory.DIAGNOSTIC, ufp_required_field="can_detect_face", ufp_value="is_face_detection_on", @@ -313,12 +320,66 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( ), ) -MOTION_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( - ProtectBinaryEntityDescription( +MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = ( + ProtectBinaryEventEntityDescription( key="motion", name="Motion", device_class=BinarySensorDeviceClass.MOTION, ufp_value="is_motion_detected", + ufp_event_obj="last_motion_event", + ), + ProtectBinaryEventEntityDescription( + key="smart_obj_any", + name="Object Detected", + icon="mdi:eye", + device_class=DEVICE_CLASS_DETECTION, + ufp_value="is_smart_detected", + ufp_required_field="feature_flags.has_smart_detect", + ufp_event_obj="last_smart_detect_event", + ), + ProtectBinaryEventEntityDescription( + key="smart_obj_person", + name="Person Detected", + icon="mdi:walk", + device_class=DEVICE_CLASS_DETECTION, + ufp_value="is_smart_detected", + ufp_required_field="can_detect_person", + ufp_enabled="is_person_detection_on", + ufp_event_obj="last_smart_detect_event", + ufp_smart_type=SmartDetectObjectType.PERSON, + ), + ProtectBinaryEventEntityDescription( + key="smart_obj_vehicle", + name="Vehicle Detected", + icon="mdi:car", + device_class=DEVICE_CLASS_DETECTION, + ufp_value="is_smart_detected", + ufp_required_field="can_detect_vehicle", + ufp_enabled="is_vehicle_detection_on", + ufp_event_obj="last_smart_detect_event", + ufp_smart_type=SmartDetectObjectType.VEHICLE, + ), + ProtectBinaryEventEntityDescription( + key="smart_obj_face", + name="Face Detected", + device_class=DEVICE_CLASS_DETECTION, + icon="mdi:mdi-face", + ufp_value="is_smart_detected", + ufp_required_field="can_detect_face", + ufp_enabled="is_face_detection_on", + ufp_event_obj="last_smart_detect_event", + ufp_smart_type=SmartDetectObjectType.FACE, + ), + ProtectBinaryEventEntityDescription( + key="smart_obj_package", + name="Package Detected", + device_class=DEVICE_CLASS_DETECTION, + icon="mdi:package-variant-closed", + ufp_value="is_smart_detected", + ufp_required_field="can_detect_package", + ufp_enabled="is_package_detection_on", + ufp_event_obj="last_smart_detect_event", + ufp_smart_type=SmartDetectObjectType.PACKAGE, ), ) @@ -415,6 +476,8 @@ def _async_motion_entities( ) for device in devices: for description in MOTION_SENSORS: + if not description.has_required(device): + continue entities.append(ProtectEventBinarySensor(data, device, description)) _LOGGER.debug( "Adding binary sensor entity %s for %s", @@ -508,17 +571,12 @@ class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity): self._attr_is_on = not self._disk.is_healthy -class ProtectEventBinarySensor(EventThumbnailMixin, ProtectDeviceBinarySensor): - """A UniFi Protect Device Binary Sensor with access tokens.""" +class ProtectEventBinarySensor(EventEntityMixin, BinarySensorEntity): + """A UniFi Protect Device Binary Sensor for events.""" - device: Camera + entity_description: ProtectBinaryEventEntityDescription @callback - def _async_get_event(self) -> Event | None: - """Get event from Protect device.""" - - event: Event | None = None - if self.device.is_motion_detected and self.device.last_motion_event is not None: - event = self.device.last_motion_event - - return event + 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) diff --git a/homeassistant/components/unifiprotect/const.py b/homeassistant/components/unifiprotect/const.py index f751ed6a009..cbf15c076c5 100644 --- a/homeassistant/components/unifiprotect/const.py +++ b/homeassistant/components/unifiprotect/const.py @@ -7,6 +7,7 @@ from homeassistant.const import Platform DOMAIN = "unifiprotect" ATTR_EVENT_SCORE = "event_score" +ATTR_EVENT_ID = "event_id" ATTR_WIDTH = "width" ATTR_HEIGHT = "height" ATTR_FPS = "fps" @@ -67,3 +68,5 @@ PLATFORMS = [ DISPATCH_ADD = "add_device" DISPATCH_ADOPT = "adopt_device" DISPATCH_CHANNELS = "new_camera_channels" + +DEVICE_CLASS_DETECTION = "unifiprotect__detection" diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index 9777ccbd72a..a733f6ea1af 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -24,10 +24,15 @@ from homeassistant.core import callback import homeassistant.helpers.device_registry as dr from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription -from .const import ATTR_EVENT_SCORE, DEFAULT_ATTRIBUTION, DEFAULT_BRAND, DOMAIN +from .const import ( + ATTR_EVENT_ID, + ATTR_EVENT_SCORE, + DEFAULT_ATTRIBUTION, + DEFAULT_BRAND, + DOMAIN, +) from .data import ProtectData -from .models import PermRequired, ProtectRequiredKeysMixin -from .utils import get_nested_attr +from .models import PermRequired, ProtectEventMixin, ProtectRequiredKeysMixin _LOGGER = logging.getLogger(__name__) @@ -82,10 +87,8 @@ def _async_device_entities( ): continue - if description.ufp_required_field: - required_field = get_nested_attr(device, description.ufp_required_field) - if not required_field: - continue + if not description.has_required(device): + continue entities.append( klass( @@ -294,42 +297,39 @@ class ProtectNVREntity(ProtectDeviceEntity): self._attr_available = self.data.last_update_success -class EventThumbnailMixin(ProtectDeviceEntity): +class EventEntityMixin(ProtectDeviceEntity): """Adds motion event attributes to sensor.""" - def __init__(self, *args: Any, **kwarg: Any) -> None: + entity_description: ProtectEventMixin + + def __init__( + self, + *args: Any, + **kwarg: Any, + ) -> None: """Init an sensor that has event thumbnails.""" super().__init__(*args, **kwarg) self._event: Event | None = None @callback - def _async_get_event(self) -> Event | None: - """Get event from Protect device. - - To be overridden by child classes. - """ - raise NotImplementedError() - - @callback - def _async_thumbnail_extra_attrs(self) -> dict[str, Any]: - # Camera motion sensors with object detection - attrs: dict[str, Any] = { - ATTR_EVENT_SCORE: 0, - } + 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: super()._async_update_device_from_protect(device) - self._event = self._async_get_event() + self._attr_is_on: bool | None = self.entity_description.get_is_on(device) + self._event = self.entity_description.get_event_obj(device) attrs = self.extra_state_attributes or {} self._attr_extra_state_attributes = { **attrs, - **self._async_thumbnail_extra_attrs(), + **self._async_event_extra_attrs(), } diff --git a/homeassistant/components/unifiprotect/migrate.py b/homeassistant/components/unifiprotect/migrate.py index 893ca3e458a..b40f78d0ccf 100644 --- a/homeassistant/components/unifiprotect/migrate.py +++ b/homeassistant/components/unifiprotect/migrate.py @@ -12,7 +12,10 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import entity_registry as er, issue_registry as ir +from homeassistant.helpers.issue_registry import IssueSeverity + +from .const import DOMAIN _LOGGER = logging.getLogger(__name__) @@ -30,6 +33,23 @@ async def async_migrate_data( await async_migrate_device_ids(hass, entry, protect) _LOGGER.debug("Completed Migrate: async_migrate_device_ids") + entity_registry = er.async_get(hass) + for entity in er.async_entries_for_config_entry(entity_registry, entry.entry_id): + if ( + entity.domain == Platform.SENSOR + and entity.disabled_by is None + and "detected_object" in entity.unique_id + ): + ir.async_create_issue( + hass, + DOMAIN, + "deprecate_smart_sensor", + is_fixable=False, + breaks_in_ha_version="2023.2.0", + severity=IssueSeverity.WARNING, + translation_key="deprecate_smart_sensor", + ) + async def async_get_bootstrap(protect: ProtectApiClient) -> Bootstrap: """Get UniFi Protect bootstrap or raise appropriate HA error.""" diff --git a/homeassistant/components/unifiprotect/models.py b/homeassistant/components/unifiprotect/models.py index adab5c032e1..01d4820e9a9 100644 --- a/homeassistant/components/unifiprotect/models.py +++ b/homeassistant/components/unifiprotect/models.py @@ -5,9 +5,9 @@ from collections.abc import Callable, Coroutine from dataclasses import dataclass from enum import Enum import logging -from typing import Any, Generic, TypeVar, Union +from typing import Any, Generic, TypeVar, Union, cast -from pyunifiprotect.data import NVR, ProtectAdoptableDeviceModel +from pyunifiprotect.data import NVR, Event, ProtectAdoptableDeviceModel from homeassistant.helpers.entity import EntityDescription @@ -54,6 +54,41 @@ class ProtectRequiredKeysMixin(EntityDescription, Generic[T]): return bool(get_nested_attr(obj, self.ufp_enabled)) return True + def has_required(self, obj: T) -> bool: + """Return if has required field.""" + + if self.ufp_required_field is None: + return True + return bool(get_nested_attr(obj, self.ufp_required_field)) + + +@dataclass +class ProtectEventMixin(ProtectRequiredKeysMixin[T]): + """Mixin for events.""" + + ufp_event_obj: str | None = None + ufp_smart_type: str | None = None + + def get_event_obj(self, obj: T) -> Event | None: + """Return value from UniFi Protect device.""" + + if self.ufp_event_obj is not None: + return cast(Event, get_nested_attr(obj, self.ufp_event_obj)) + return None + + def get_is_on(self, obj: T) -> bool: + """Return value if event is active.""" + + value = bool(self.get_ufp_value(obj)) + if value: + event = self.get_event_obj(obj) + value = event is not None + + if event is not None and self.ufp_smart_type is not None: + value = self.ufp_smart_type in event.smart_detect_types + + return value + @dataclass class ProtectSetableKeysMixin(ProtectRequiredKeysMixin[T]): diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index 57bd4fc7230..168faebe8ac 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -9,7 +9,6 @@ from typing import Any, cast from pyunifiprotect.data import ( NVR, Camera, - Event, Light, ModelType, ProtectAdoptableDeviceModel, @@ -41,20 +40,19 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DISPATCH_ADOPT, DOMAIN +from .const import DEVICE_CLASS_DETECTION, DISPATCH_ADOPT, DOMAIN from .data import ProtectData from .entity import ( - EventThumbnailMixin, + EventEntityMixin, ProtectDeviceEntity, ProtectNVREntity, async_all_device_entities, ) -from .models import PermRequired, ProtectRequiredKeysMixin, T +from .models import PermRequired, ProtectEventMixin, ProtectRequiredKeysMixin, T from .utils import async_dispatch_id as _ufpd, async_get_light_motion_current _LOGGER = logging.getLogger(__name__) OBJECT_TYPE_NONE = "none" -DEVICE_CLASS_DETECTION = "unifiprotect__detection" @dataclass @@ -74,6 +72,13 @@ class ProtectSensorEntityDescription( return value +@dataclass +class ProtectSensorEventEntityDescription( + ProtectEventMixin[T], SensorEntityDescription +): + """Describes UniFi Protect Sensor entity.""" + + def _get_uptime(obj: ProtectDeviceModel) -> datetime | None: if obj.up_since is None: return None @@ -513,11 +518,14 @@ NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = ( ), ) -MOTION_SENSORS: tuple[ProtectSensorEntityDescription, ...] = ( - ProtectSensorEntityDescription( +MOTION_SENSORS: tuple[ProtectSensorEventEntityDescription, ...] = ( + ProtectSensorEventEntityDescription( key="detected_object", name="Detected Object", device_class=DEVICE_CLASS_DETECTION, + entity_registry_enabled_default=False, + ufp_value="is_smart_detected", + ufp_event_obj="last_smart_detect_event", ), ) @@ -666,8 +674,8 @@ def _async_motion_entities( if not device.feature_flags.has_smart_detect: continue - for description in MOTION_SENSORS: - entities.append(ProtectEventSensor(data, device, description)) + for event_desc in MOTION_SENSORS: + entities.append(ProtectEventSensor(data, device, event_desc)) _LOGGER.debug( "Adding sensor entity %s for %s", description.name, @@ -730,29 +738,24 @@ class ProtectNVRSensor(ProtectNVREntity, SensorEntity): self._attr_native_value = self.entity_description.get_ufp_value(self.device) -class ProtectEventSensor(ProtectDeviceSensor, EventThumbnailMixin): +class ProtectEventSensor(EventEntityMixin, SensorEntity): """A UniFi Protect Device Sensor with access tokens.""" - device: Camera + entity_description: ProtectSensorEventEntityDescription - @callback - def _async_get_event(self) -> Event | None: - """Get event from Protect device.""" - - event: Event | None = None - if ( - self.device.is_smart_detected - and self.device.last_smart_detect_event is not None - and len(self.device.last_smart_detect_event.smart_detect_types) > 0 - ): - event = self.device.last_smart_detect_event - - return event + 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 - EventThumbnailMixin._async_update_device_from_protect(self, device) + EventEntityMixin._async_update_device_from_protect(self, device) if self._event is None: self._attr_native_value = OBJECT_TYPE_NONE else: diff --git a/homeassistant/components/unifiprotect/strings.json b/homeassistant/components/unifiprotect/strings.json index abac7701279..8bdd844fdbf 100644 --- a/homeassistant/components/unifiprotect/strings.json +++ b/homeassistant/components/unifiprotect/strings.json @@ -75,6 +75,10 @@ "ea_setup_failed": { "title": "Setup error using Early Access version", "description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please [downgrade to a stable version](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) of UniFi Protect to continue using the integration.\n\nError: {error}" + }, + "deprecate_smart_sensor": { + "title": "Smart Detection Sensor Deprecated", + "description": "The unified \"Detected Object\" sensor for smart detections is now deprecated. It has been replaced with individual smart detection binary sensors for each smart detection type. Please update any templates or automations accordingly." } } } diff --git a/homeassistant/components/unifiprotect/translations/en.json b/homeassistant/components/unifiprotect/translations/en.json index 65a398375fe..21b3cd64360 100644 --- a/homeassistant/components/unifiprotect/translations/en.json +++ b/homeassistant/components/unifiprotect/translations/en.json @@ -42,6 +42,10 @@ } }, "issues": { + "deprecate_smart_sensor": { + "description": "The unified \"Detected Object\" sensor for smart detections is now deprecated. It has been replaced with individual smart detection binary sensors for each smart detection type. Please update any templates or automations accordingly.", + "title": "Smart Detection Sensor Deprecated" + }, "ea_setup_failed": { "description": "You are using v{version} of UniFi Protect which is an Early Access version. An unrecoverable error occurred while trying to load the integration. Please [downgrade to a stable version](https://www.home-assistant.io/integrations/unifiprotect#downgrading-unifi-protect) of UniFi Protect to continue using the integration.\n\nError: {error}", "title": "Setup error using Early Access version" diff --git a/tests/components/unifiprotect/test_binary_sensor.py b/tests/components/unifiprotect/test_binary_sensor.py index b2eec518d40..cb8d7bb659c 100644 --- a/tests/components/unifiprotect/test_binary_sensor.py +++ b/tests/components/unifiprotect/test_binary_sensor.py @@ -50,11 +50,11 @@ async def test_binary_sensor_camera_remove( ufp.api.bootstrap.nvr.system_info.ustorage = None await init_entry(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.BINARY_SENSOR, 3, 3) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 6, 6) await remove_entities(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.BINARY_SENSOR, 0, 0) await adopt_devices(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.BINARY_SENSOR, 3, 3) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 6, 6) async def test_binary_sensor_light_remove( @@ -120,7 +120,7 @@ async def test_binary_sensor_setup_camera_all( ufp.api.bootstrap.nvr.system_info.ustorage = None await init_entry(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.BINARY_SENSOR, 3, 3) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 6, 6) entity_registry = er.async_get(hass) @@ -167,7 +167,6 @@ async def test_binary_sensor_setup_camera_all( assert state assert state.state == STATE_OFF assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - assert state.attributes[ATTR_EVENT_SCORE] == 0 async def test_binary_sensor_setup_camera_none( @@ -263,7 +262,7 @@ async def test_binary_sensor_update_motion( """Test binary_sensor motion entity.""" await init_entry(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.BINARY_SENSOR, 9, 9) + assert_entity_counts(hass, Platform.BINARY_SENSOR, 12, 12) _, entity_id = ids_from_device_description( Platform.BINARY_SENSOR, doorbell, MOTION_SENSORS[0] diff --git a/tests/components/unifiprotect/test_repairs.py b/tests/components/unifiprotect/test_repairs.py index f6f677a1976..a1279dbed84 100644 --- a/tests/components/unifiprotect/test_repairs.py +++ b/tests/components/unifiprotect/test_repairs.py @@ -6,7 +6,7 @@ from copy import copy from http import HTTPStatus from unittest.mock import Mock -from pyunifiprotect.data import Version +from pyunifiprotect.data import Camera, Version from homeassistant.components.repairs.issue_handler import ( async_process_repairs_platforms, @@ -16,7 +16,9 @@ from homeassistant.components.repairs.websocket_api import ( RepairsFlowResourceView, ) from homeassistant.components.unifiprotect.const import DOMAIN +from homeassistant.const import Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from .utils import MockUFPFixture, init_entry @@ -40,9 +42,12 @@ async def test_ea_warning_ignore( msg = await ws_client.receive_json() assert msg["success"] - assert len(msg["result"]["issues"]) == 1 - issue = msg["result"]["issues"][0] - assert issue["issue_id"] == "ea_warning" + assert len(msg["result"]["issues"]) > 0 + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "ea_warning": + issue = i + assert issue is not None url = RepairsFlowIndexView.url resp = await client.post(url, json={"handler": DOMAIN, "issue_id": "ea_warning"}) @@ -89,9 +94,12 @@ async def test_ea_warning_fix( msg = await ws_client.receive_json() assert msg["success"] - assert len(msg["result"]["issues"]) == 1 - issue = msg["result"]["issues"][0] - assert issue["issue_id"] == "ea_warning" + assert len(msg["result"]["issues"]) > 0 + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "ea_warning": + issue = i + assert issue is not None url = RepairsFlowIndexView.url resp = await client.post(url, json={"handler": DOMAIN, "issue_id": "ea_warning"}) @@ -118,3 +126,53 @@ async def test_ea_warning_fix( data = await resp.json() assert data["type"] == "create_entry" + + +async def test_deprecate_smart_default( + hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera +): + """Test Deprecate Sensor repair does not exist by default (new installs).""" + + await init_entry(hass, ufp, [doorbell]) + + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "deprecate_smart_sensor": + issue = i + assert issue is None + + +async def test_deprecate_smart_active( + hass: HomeAssistant, ufp: MockUFPFixture, hass_ws_client, doorbell: Camera +): + """Test Deprecate Sensor repair exists for existing installs.""" + + registry = er.async_get(hass) + registry.async_get_or_create( + Platform.SENSOR, + DOMAIN, + f"{doorbell.mac}_detected_object", + config_entry=ufp.entry, + ) + + await init_entry(hass, ufp, [doorbell]) + + await async_process_repairs_platforms(hass) + ws_client = await hass_ws_client(hass) + + await ws_client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await ws_client.receive_json() + + assert msg["success"] + issue = None + for i in msg["result"]["issues"]: + if i["issue_id"] == "deprecate_smart_sensor": + issue = i + assert issue is not None diff --git a/tests/components/unifiprotect/test_sensor.py b/tests/components/unifiprotect/test_sensor.py index a712c112b6d..f095260c38e 100644 --- a/tests/components/unifiprotect/test_sensor.py +++ b/tests/components/unifiprotect/test_sensor.py @@ -62,11 +62,11 @@ async def test_sensor_camera_remove( ufp.api.bootstrap.nvr.system_info.ustorage = None await init_entry(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.SENSOR, 25, 13) + assert_entity_counts(hass, Platform.SENSOR, 25, 12) await remove_entities(hass, ufp, [doorbell, unadopted_camera]) assert_entity_counts(hass, Platform.SENSOR, 12, 9) await adopt_devices(hass, ufp, [doorbell, unadopted_camera]) - assert_entity_counts(hass, Platform.SENSOR, 25, 13) + assert_entity_counts(hass, Platform.SENSOR, 25, 12) async def test_sensor_sensor_remove( @@ -318,7 +318,7 @@ async def test_sensor_setup_camera( """Test sensor entity setup for camera devices.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SENSOR, 25, 13) + assert_entity_counts(hass, Platform.SENSOR, 25, 12) entity_registry = er.async_get(hass) @@ -406,11 +406,12 @@ async def test_sensor_setup_camera( assert entity assert entity.unique_id == unique_id + await enable_entity(hass, ufp.entry.entry_id, entity_id) + state = hass.states.get(entity_id) assert state assert state.state == OBJECT_TYPE_NONE assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION - assert state.attributes[ATTR_EVENT_SCORE] == 0 async def test_sensor_setup_camera_with_last_trip_time( @@ -451,12 +452,14 @@ async def test_sensor_update_motion( """Test sensor motion entity.""" await init_entry(hass, ufp, [doorbell]) - assert_entity_counts(hass, Platform.SENSOR, 25, 13) + assert_entity_counts(hass, Platform.SENSOR, 25, 12) _, entity_id = ids_from_device_description( Platform.SENSOR, doorbell, MOTION_SENSORS[0] ) + await enable_entity(hass, ufp.entry.entry_id, entity_id) + event = Event( id="test_event_id", type=EventType.SMART_DETECT,