Split UniFi Protect object sensor into multiple (#82595)
This commit is contained in:
parent
892be99ca0
commit
b842e26d36
11 changed files with 274 additions and 87 deletions
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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]):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue