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

View file

@ -40,6 +40,11 @@ from .utils import async_dispatch_id as _ufpd, async_get_devices_by_type
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ProtectDeviceType = ProtectAdoptableDeviceModel | NVR ProtectDeviceType = ProtectAdoptableDeviceModel | NVR
SMART_EVENTS = {
EventType.SMART_DETECT,
EventType.SMART_AUDIO_DETECT,
EventType.SMART_DETECT_LINE,
}
@callback @callback
@ -223,6 +228,25 @@ class ProtectData:
# trigger updates for camera that the event references # trigger updates for camera that the event references
elif isinstance(obj, Event): 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.type == EventType.DEVICE_ADOPTED:
if obj.metadata is not None and obj.metadata.device_id is not None: if obj.metadata is not None and obj.metadata.device_id is not None:
device = self.api.bootstrap.get_device_from_id( device = self.api.bootstrap.get_device_from_id(

View file

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

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Callable, Coroutine from collections.abc import Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta
from enum import Enum from enum import Enum
import logging import logging
from typing import Any, Generic, TypeVar, cast 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 pyunifiprotect.data import NVR, Event, ProtectAdoptableDeviceModel
from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.entity import EntityDescription
from homeassistant.util import dt as dt_util
from .utils import get_nested_attr from .utils import get_nested_attr
@ -67,7 +69,6 @@ class ProtectEventMixin(ProtectRequiredKeysMixin[T]):
"""Mixin for events.""" """Mixin for events."""
ufp_event_obj: str | None = None ufp_event_obj: str | None = None
ufp_smart_type: str | None = None
def get_event_obj(self, obj: T) -> Event | None: def get_event_obj(self, obj: T) -> Event | None:
"""Return value from UniFi Protect device.""" """Return value from UniFi Protect device."""
@ -79,22 +80,21 @@ class ProtectEventMixin(ProtectRequiredKeysMixin[T]):
def get_is_on(self, obj: T) -> bool: def get_is_on(self, obj: T) -> bool:
"""Return value if event is active.""" """Return value if event is active."""
value = bool(self.get_ufp_value(obj))
if value:
event = self.get_event_obj(obj) event = self.get_event_obj(obj)
value = event is not None if event is None:
if not value: return False
_LOGGER.debug("%s (%s): missing event", self.name, obj.mac)
if event is not None and self.ufp_smart_type is not None: now = dt_util.utcnow()
value = self.ufp_smart_type in event.smart_detect_types value = now > event.start
if not value: 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( _LOGGER.debug(
"%s (%s): %s not in %s", "%s (%s): end ended at %s",
self.name, self.name,
obj.mac, obj.mac,
self.ufp_smart_type, event.end.isoformat(),
event.smart_detect_types,
) )
if value: if value:

View file

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

View file

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

View file

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

View file

@ -537,7 +537,9 @@ async def test_camera_update_licenseplate(
new_camera = camera.copy() new_camera = camera.copy()
new_camera.is_smart_detected = True 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 = Mock()
mock_msg.changed_data = {} mock_msg.changed_data = {}