Fix multiple smart detects firing at once for UniFi Protect (#94133)

* Fix multiple smart detects firing at once

* Tweak

* Clean up logging. Linting

* Linting
This commit is contained in:
Christopher Bailey 2023-06-06 20:07:21 -04:00 committed by GitHub
parent a6bb70c5d2
commit 9b6a9147c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 55 additions and 40 deletions

View file

@ -14,8 +14,6 @@ from pyunifiprotect.data import (
ProtectAdoptableDeviceModel,
ProtectModelWithId,
Sensor,
SmartDetectAudioType,
SmartDetectObjectType,
)
from pyunifiprotect.data.nvr import UOSDisk
@ -364,8 +362,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
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,
ufp_event_obj="last_person_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_obj_vehicle",
@ -374,8 +371,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
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,
ufp_event_obj="last_vehicle_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_obj_face",
@ -384,8 +380,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
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,
ufp_event_obj="last_face_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_obj_package",
@ -394,8 +389,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
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,
ufp_event_obj="last_package_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_audio_any",
@ -412,8 +406,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_required_field="can_detect_smoke",
ufp_enabled="is_smoke_detection_on",
ufp_event_obj="last_smart_audio_detect_event",
ufp_smart_type=SmartDetectAudioType.SMOKE,
ufp_event_obj="last_smoke_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_audio_cmonx",
@ -422,8 +415,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_required_field="can_detect_smoke",
ufp_enabled="is_smoke_detection_on",
ufp_event_obj="last_smart_audio_detect_event",
ufp_smart_type=SmartDetectAudioType.CMONX,
ufp_event_obj="last_cmonx_detect_event",
),
)

View file

@ -40,6 +40,11 @@ from .utils import async_dispatch_id as _ufpd, async_get_devices_by_type
_LOGGER = logging.getLogger(__name__)
ProtectDeviceType = ProtectAdoptableDeviceModel | NVR
SMART_EVENTS = {
EventType.SMART_DETECT,
EventType.SMART_AUDIO_DETECT,
EventType.SMART_DETECT_LINE,
}
@callback
@ -223,6 +228,25 @@ class ProtectData:
# trigger updates for camera that the event references
elif isinstance(obj, Event):
if obj.type in SMART_EVENTS:
if obj.camera is not None:
if obj.end is None:
_LOGGER.debug(
"%s (%s): New smart detection started for %s (%s)",
obj.camera.name,
obj.camera.mac,
obj.smart_detect_types,
obj.id,
)
else:
_LOGGER.debug(
"%s (%s): Smart detection ended for %s (%s)",
obj.camera.name,
obj.camera.mac,
obj.smart_detect_types,
obj.id,
)
if obj.type == EventType.DEVICE_ADOPTED:
if obj.metadata is not None and obj.metadata.device_id is not None:
device = self.api.bootstrap.get_device_from_id(

View file

@ -41,7 +41,7 @@
"iot_class": "local_push",
"loggers": ["pyunifiprotect", "unifi_discovery"],
"quality_scale": "platinum",
"requirements": ["pyunifiprotect==4.9.1", "unifi-discovery==1.1.7"],
"requirements": ["pyunifiprotect==4.10.0", "unifi-discovery==1.1.7"],
"ssdp": [
{
"manufacturer": "Ubiquiti Networks",

View file

@ -3,6 +3,7 @@ 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
@ -10,6 +11,7 @@ from typing import Any, Generic, TypeVar, cast
from pyunifiprotect.data import NVR, Event, ProtectAdoptableDeviceModel
from homeassistant.helpers.entity import EntityDescription
from homeassistant.util import dt as dt_util
from .utils import get_nested_attr
@ -67,7 +69,6 @@ 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."""
@ -79,23 +80,22 @@ class ProtectEventMixin(ProtectRequiredKeysMixin[T]):
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 not value:
_LOGGER.debug("%s (%s): missing event", self.name, obj.mac)
event = self.get_event_obj(obj)
if event is None:
return False
if event is not None and self.ufp_smart_type is not None:
value = self.ufp_smart_type in event.smart_detect_types
if not value:
_LOGGER.debug(
"%s (%s): %s not in %s",
self.name,
obj.mac,
self.ufp_smart_type,
event.smart_detect_types,
)
now = dt_util.utcnow()
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)

View file

@ -15,7 +15,6 @@ from pyunifiprotect.data import (
ProtectDeviceModel,
ProtectModelWithId,
Sensor,
SmartDetectObjectType,
)
from homeassistant.components.sensor import (
@ -528,10 +527,9 @@ EVENT_SENSORS: tuple[ProtectSensorEventEntityDescription, ...] = (
name="License Plate Detected",
icon="mdi:car",
translation_key="license_plate",
ufp_smart_type=SmartDetectObjectType.LICENSE_PLATE,
ufp_value="is_smart_detected",
ufp_required_field="can_detect_license_plate",
ufp_event_obj="last_smart_detect_event",
ufp_event_obj="last_license_plate_detect_event",
),
)
@ -767,8 +765,7 @@ class ProtectEventSensor(EventEntityMixin, SensorEntity):
EventEntityMixin._async_update_device_from_protect(self, device)
is_on = self.entity_description.get_is_on(device)
is_license_plate = (
self.entity_description.ufp_smart_type
== SmartDetectObjectType.LICENSE_PLATE
self.entity_description.ufp_event_obj == "last_license_plate_detect_event"
)
if (
not is_on

View file

@ -2184,7 +2184,7 @@ pytrafikverket==0.3.3
pyudev==0.23.2
# homeassistant.components.unifiprotect
pyunifiprotect==4.9.1
pyunifiprotect==4.10.0
# homeassistant.components.uptimerobot
pyuptimerobot==22.2.0

View file

@ -1598,7 +1598,7 @@ pytrafikverket==0.3.3
pyudev==0.23.2
# homeassistant.components.unifiprotect
pyunifiprotect==4.9.1
pyunifiprotect==4.10.0
# homeassistant.components.uptimerobot
pyuptimerobot==22.2.0

View file

@ -537,7 +537,9 @@ async def test_camera_update_licenseplate(
new_camera = camera.copy()
new_camera.is_smart_detected = True
new_camera.last_smart_detect_event_id = event.id
new_camera.last_smart_detect_event_ids[
SmartDetectObjectType.LICENSE_PLATE
] = event.id
mock_msg = Mock()
mock_msg.changed_data = {}