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:
parent
a6bb70c5d2
commit
9b6a9147c7
8 changed files with 55 additions and 40 deletions
|
@ -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,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue