Dispatch unifiprotect websocket messages based on model (#119633)

This commit is contained in:
J. Nick Koston 2024-06-13 16:17:31 -05:00 committed by GitHub
parent de27f24a4c
commit 0c3a5ae5da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 91 additions and 36 deletions

View File

@ -6,7 +6,7 @@ from collections.abc import Callable, Iterable
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import partial from functools import partial
import logging import logging
from typing import Any, cast from typing import TYPE_CHECKING, Any, cast
from typing_extensions import Generator from typing_extensions import Generator
from uiprotect import ProtectApiClient from uiprotect import ProtectApiClient
@ -16,7 +16,6 @@ from uiprotect.data import (
Camera, Camera,
Event, Event,
EventType, EventType,
Liveview,
ModelType, ModelType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
WSSubscriptionMessage, WSSubscriptionMessage,
@ -231,41 +230,49 @@ class ProtectData:
@callback @callback
def _async_process_ws_message(self, message: WSSubscriptionMessage) -> None: def _async_process_ws_message(self, message: WSSubscriptionMessage) -> None:
if message.new_obj is None: """Process a message from the websocket."""
if (new_obj := message.new_obj) is None:
if isinstance(message.old_obj, ProtectAdoptableDeviceModel): if isinstance(message.old_obj, ProtectAdoptableDeviceModel):
self._async_remove_device(message.old_obj) self._async_remove_device(message.old_obj)
return return
obj = message.new_obj model_type = new_obj.model
if isinstance(obj, (ProtectAdoptableDeviceModel, NVR)): if model_type is ModelType.EVENT:
if message.old_obj is None and isinstance(obj, ProtectAdoptableDeviceModel): if TYPE_CHECKING:
self._async_add_device(obj) assert isinstance(new_obj, Event)
elif getattr(obj, "is_adopted_by_us", True):
self._async_update_device(obj, message.changed_data)
# trigger updates for camera that the event references
elif isinstance(obj, Event):
if _LOGGER.isEnabledFor(logging.DEBUG): if _LOGGER.isEnabledFor(logging.DEBUG):
log_event(obj) log_event(new_obj)
if obj.type is EventType.DEVICE_ADOPTED: if (
if obj.metadata is not None and obj.metadata.device_id is not None: (new_obj.type is EventType.DEVICE_ADOPTED)
device = self.api.bootstrap.get_device_from_id( and (metadata := new_obj.metadata)
obj.metadata.device_id and (device_id := metadata.device_id)
) and (device := self.api.bootstrap.get_device_from_id(device_id))
if device is not None: ):
self._async_add_device(device) self._async_add_device(device)
elif obj.camera is not None: elif camera := new_obj.camera:
self._async_signal_device_update(obj.camera) self._async_signal_device_update(camera)
elif obj.light is not None: elif light := new_obj.light:
self._async_signal_device_update(obj.light) self._async_signal_device_update(light)
elif obj.sensor is not None: elif sensor := new_obj.sensor:
self._async_signal_device_update(obj.sensor) self._async_signal_device_update(sensor)
return
if model_type is ModelType.LIVEVIEW and len(self.api.bootstrap.viewers) > 0:
# alert user viewport needs restart so voice clients can get new options # alert user viewport needs restart so voice clients can get new options
elif len(self.api.bootstrap.viewers) > 0 and isinstance(obj, Liveview):
_LOGGER.warning( _LOGGER.warning(
"Liveviews updated. Restart Home Assistant to update Viewport select" "Liveviews updated. Restart Home Assistant to update Viewport select"
" options" " options"
) )
return
if message.old_obj is None and isinstance(new_obj, ProtectAdoptableDeviceModel):
self._async_add_device(new_obj)
return
if getattr(new_obj, "is_adopted_by_us", True) and hasattr(new_obj, "mac"):
if TYPE_CHECKING:
assert isinstance(new_obj, (ProtectAdoptableDeviceModel, NVR))
self._async_update_device(new_obj, message.changed_data)
@callback @callback
def _async_process_updates(self, updates: Bootstrap | None) -> None: def _async_process_updates(self, updates: Bootstrap | None) -> None:

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from datetime import datetime, timedelta from datetime import datetime, timedelta
from unittest.mock import Mock from unittest.mock import Mock
from uiprotect.data import Camera, Event, EventType, Light, MountType, Sensor from uiprotect.data import Camera, Event, EventType, Light, ModelType, MountType, Sensor
from uiprotect.data.nvr import EventMetadata from uiprotect.data.nvr import EventMetadata
from homeassistant.components.binary_sensor import BinarySensorDeviceClass from homeassistant.components.binary_sensor import BinarySensorDeviceClass
@ -281,6 +281,7 @@ async def test_binary_sensor_update_motion(
) )
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=1), start=fixed_now - timedelta(seconds=1),
@ -289,19 +290,21 @@ async def test_binary_sensor_update_motion(
smart_detect_types=[], smart_detect_types=[],
smart_detect_event_ids=[], smart_detect_event_ids=[],
camera_id=doorbell.id, camera_id=doorbell.id,
api=ufp.api,
) )
new_camera = doorbell.copy() new_camera = doorbell.copy()
new_camera.is_motion_detected = True new_camera.is_motion_detected = True
new_camera.last_motion_event_id = event.id new_camera.last_motion_event_id = event.id
mock_msg = Mock()
mock_msg.changed_data = {}
mock_msg.new_obj = new_camera
ufp.api.bootstrap.cameras = {new_camera.id: new_camera} ufp.api.bootstrap.cameras = {new_camera.id: new_camera}
ufp.api.bootstrap.events = {event.id: event} ufp.api.bootstrap.events = {event.id: event}
mock_msg = Mock()
mock_msg.changed_data = {}
mock_msg.new_obj = event
ufp.ws_msg(mock_msg) ufp.ws_msg(mock_msg)
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
@ -325,6 +328,7 @@ async def test_binary_sensor_update_light_motion(
event_metadata = EventMetadata(light_id=light.id) event_metadata = EventMetadata(light_id=light.id)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION_LIGHT, type=EventType.MOTION_LIGHT,
start=fixed_now - timedelta(seconds=1), start=fixed_now - timedelta(seconds=1),

View File

@ -10,6 +10,7 @@ from uiprotect.data import (
Camera, Camera,
Event, Event,
EventType, EventType,
ModelType,
Permission, Permission,
SmartDetectObjectType, SmartDetectObjectType,
) )
@ -72,6 +73,7 @@ async def test_resolve_media_thumbnail(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -103,6 +105,7 @@ async def test_resolve_media_event(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -172,6 +175,7 @@ async def test_browse_media_event_ongoing(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -591,6 +595,7 @@ async def test_browse_media_recent(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -628,6 +633,7 @@ async def test_browse_media_recent_truncated(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -660,6 +666,7 @@ async def test_browse_media_recent_truncated(
[ [
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.RING, type=EventType.RING,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -673,6 +680,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -686,6 +694,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -708,6 +717,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -721,6 +731,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -734,6 +745,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -757,6 +769,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -786,6 +799,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -820,6 +834,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -852,6 +867,7 @@ async def test_browse_media_recent_truncated(
), ),
( (
Event( Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_AUDIO_DETECT, type=EventType.SMART_AUDIO_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0), start=datetime(1000, 1, 1, 0, 0, 0),
@ -906,6 +922,7 @@ async def test_browse_media_eventthumb(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -969,6 +986,7 @@ async def test_browse_media_browse_day(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -1010,6 +1028,7 @@ async def test_browse_media_browse_whole_month(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -1052,6 +1071,7 @@ async def test_browse_media_browse_whole_month_december(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False) await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event1 = Event( event1 = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=fixed_now - timedelta(seconds=3663), start=fixed_now - timedelta(seconds=3663),
@ -1063,6 +1083,7 @@ async def test_browse_media_browse_whole_month_december(
) )
event1._api = ufp.api event1._api = ufp.api
event2 = Event( event2 = Event(
model=ModelType.EVENT,
id="test_event_id2", id="test_event_id2",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -1074,6 +1095,7 @@ async def test_browse_media_browse_whole_month_december(
) )
event2._api = ufp.api event2._api = ufp.api
event3 = Event( event3 = Event(
model=ModelType.EVENT,
id="test_event_id3", id="test_event_id3",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),
@ -1085,6 +1107,7 @@ async def test_browse_media_browse_whole_month_december(
) )
event3._api = ufp.api event3._api = ufp.api
event4 = Event( event4 = Event(
model=ModelType.EVENT,
id="test_event_id4", id="test_event_id4",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20), start=fixed_now - timedelta(seconds=20),

View File

@ -5,7 +5,7 @@ from __future__ import annotations
from datetime import datetime, timedelta from datetime import datetime, timedelta
from unittest.mock import Mock from unittest.mock import Mock
from uiprotect.data import Camera, Event, EventType from uiprotect.data import Camera, Event, EventType, ModelType
from homeassistant.components.recorder import Recorder from homeassistant.components.recorder import Recorder
from homeassistant.components.recorder.history import get_significant_states from homeassistant.components.recorder.history import get_significant_states
@ -40,6 +40,7 @@ async def test_exclude_attributes(
) )
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.MOTION, type=EventType.MOTION,
start=fixed_now - timedelta(seconds=1), start=fixed_now - timedelta(seconds=1),

View File

@ -6,7 +6,15 @@ from datetime import datetime, timedelta
from unittest.mock import Mock from unittest.mock import Mock
import pytest import pytest
from uiprotect.data import NVR, Camera, Event, EventType, Sensor, SmartDetectObjectType from uiprotect.data import (
NVR,
Camera,
Event,
EventType,
ModelType,
Sensor,
SmartDetectObjectType,
)
from uiprotect.data.nvr import EventMetadata, LicensePlateMetadata from uiprotect.data.nvr import EventMetadata, LicensePlateMetadata
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
@ -438,6 +446,7 @@ async def test_sensor_update_alarm(
event_metadata = EventMetadata(sensor_id=sensor_all.id, alarm_type="smoke") event_metadata = EventMetadata(sensor_id=sensor_all.id, alarm_type="smoke")
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SENSOR_ALARM, type=EventType.SENSOR_ALARM,
start=fixed_now - timedelta(seconds=1), start=fixed_now - timedelta(seconds=1),
@ -521,6 +530,7 @@ async def test_camera_update_licenseplate(
license_plate=LicensePlateMetadata(name="ABCD1234", confidence_level=95) license_plate=LicensePlateMetadata(name="ABCD1234", confidence_level=95)
) )
event = Event( event = Event(
model=ModelType.EVENT,
id="test_event_id", id="test_event_id",
type=EventType.SMART_DETECT, type=EventType.SMART_DETECT,
start=fixed_now - timedelta(seconds=1), start=fixed_now - timedelta(seconds=1),

View File

@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, Mock
from aiohttp import ClientResponse from aiohttp import ClientResponse
import pytest import pytest
from uiprotect.data import Camera, Event, EventType from uiprotect.data import Camera, Event, EventType, ModelType
from uiprotect.exceptions import ClientError from uiprotect.exceptions import ClientError
from homeassistant.components.unifiprotect.views import ( from homeassistant.components.unifiprotect.views import (
@ -179,6 +179,7 @@ async def test_video_bad_event(
await init_entry(hass, ufp, [camera]) await init_entry(hass, ufp, [camera])
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id="test_id", camera_id="test_id",
start=fixed_now - timedelta(seconds=30), start=fixed_now - timedelta(seconds=30),
@ -205,6 +206,7 @@ async def test_video_bad_event_ongoing(
await init_entry(hass, ufp, [camera]) await init_entry(hass, ufp, [camera])
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=fixed_now - timedelta(seconds=30), start=fixed_now - timedelta(seconds=30),
@ -232,6 +234,7 @@ async def test_video_bad_perms(
await init_entry(hass, ufp, [camera]) await init_entry(hass, ufp, [camera])
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=fixed_now - timedelta(seconds=30), start=fixed_now - timedelta(seconds=30),
@ -260,6 +263,7 @@ async def test_video_bad_nvr_id(
await init_entry(hass, ufp, [camera]) await init_entry(hass, ufp, [camera])
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=fixed_now - timedelta(seconds=30), start=fixed_now - timedelta(seconds=30),
@ -294,6 +298,7 @@ async def test_video_bad_camera_id(
await init_entry(hass, ufp, [camera]) await init_entry(hass, ufp, [camera])
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=fixed_now - timedelta(seconds=30), start=fixed_now - timedelta(seconds=30),
@ -328,6 +333,7 @@ async def test_video_bad_camera_perms(
await init_entry(hass, ufp, [camera]) await init_entry(hass, ufp, [camera])
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=fixed_now - timedelta(seconds=30), start=fixed_now - timedelta(seconds=30),
@ -368,6 +374,7 @@ async def test_video_bad_params(
event_start = fixed_now - timedelta(seconds=30) event_start = fixed_now - timedelta(seconds=30)
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=event_start, start=event_start,
@ -405,6 +412,7 @@ async def test_video_bad_video(
event_start = fixed_now - timedelta(seconds=30) event_start = fixed_now - timedelta(seconds=30)
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=event_start, start=event_start,
@ -447,6 +455,7 @@ async def test_video(
event_start = fixed_now - timedelta(seconds=30) event_start = fixed_now - timedelta(seconds=30)
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=event_start, start=event_start,
@ -490,6 +499,7 @@ async def test_video_entity_id(
event_start = fixed_now - timedelta(seconds=30) event_start = fixed_now - timedelta(seconds=30)
event = Event( event = Event(
model=ModelType.EVENT,
api=ufp.api, api=ufp.api,
camera_id=camera.id, camera_id=camera.id,
start=event_start, start=event_start,