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 functools import partial
import logging
from typing import Any, cast
from typing import TYPE_CHECKING, Any, cast
from typing_extensions import Generator
from uiprotect import ProtectApiClient
@ -16,7 +16,6 @@ from uiprotect.data import (
Camera,
Event,
EventType,
Liveview,
ModelType,
ProtectAdoptableDeviceModel,
WSSubscriptionMessage,
@ -231,41 +230,49 @@ class ProtectData:
@callback
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):
self._async_remove_device(message.old_obj)
return
obj = message.new_obj
if isinstance(obj, (ProtectAdoptableDeviceModel, NVR)):
if message.old_obj is None and isinstance(obj, ProtectAdoptableDeviceModel):
self._async_add_device(obj)
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):
model_type = new_obj.model
if model_type is ModelType.EVENT:
if TYPE_CHECKING:
assert isinstance(new_obj, Event)
if _LOGGER.isEnabledFor(logging.DEBUG):
log_event(obj)
if obj.type is 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(
obj.metadata.device_id
)
if device is not None:
self._async_add_device(device)
elif obj.camera is not None:
self._async_signal_device_update(obj.camera)
elif obj.light is not None:
self._async_signal_device_update(obj.light)
elif obj.sensor is not None:
self._async_signal_device_update(obj.sensor)
# alert user viewport needs restart so voice clients can get new options
elif len(self.api.bootstrap.viewers) > 0 and isinstance(obj, Liveview):
log_event(new_obj)
if (
(new_obj.type is EventType.DEVICE_ADOPTED)
and (metadata := new_obj.metadata)
and (device_id := metadata.device_id)
and (device := self.api.bootstrap.get_device_from_id(device_id))
):
self._async_add_device(device)
elif camera := new_obj.camera:
self._async_signal_device_update(camera)
elif light := new_obj.light:
self._async_signal_device_update(light)
elif sensor := new_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
_LOGGER.warning(
"Liveviews updated. Restart Home Assistant to update Viewport select"
" 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
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 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 homeassistant.components.binary_sensor import BinarySensorDeviceClass
@ -281,6 +281,7 @@ async def test_binary_sensor_update_motion(
)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
start=fixed_now - timedelta(seconds=1),
@ -289,19 +290,21 @@ async def test_binary_sensor_update_motion(
smart_detect_types=[],
smart_detect_event_ids=[],
camera_id=doorbell.id,
api=ufp.api,
)
new_camera = doorbell.copy()
new_camera.is_motion_detected = True
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.events = {event.id: event}
mock_msg = Mock()
mock_msg.changed_data = {}
mock_msg.new_obj = event
ufp.ws_msg(mock_msg)
await hass.async_block_till_done()
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 = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION_LIGHT,
start=fixed_now - timedelta(seconds=1),

View file

@ -10,6 +10,7 @@ from uiprotect.data import (
Camera,
Event,
EventType,
ModelType,
Permission,
SmartDetectObjectType,
)
@ -72,6 +73,7 @@ async def test_resolve_media_thumbnail(
await init_entry(hass, ufp, [doorbell], regenerate_ids=False)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
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)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
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)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
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)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
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)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20),
@ -660,6 +666,7 @@ async def test_browse_media_recent_truncated(
[
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.RING,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -673,6 +680,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -686,6 +694,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -708,6 +717,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -721,6 +731,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -734,6 +745,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -757,6 +769,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -786,6 +799,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -820,6 +834,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=datetime(1000, 1, 1, 0, 0, 0),
@ -852,6 +867,7 @@ async def test_browse_media_recent_truncated(
),
(
Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_AUDIO_DETECT,
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)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
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)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
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)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
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)
event1 = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=fixed_now - timedelta(seconds=3663),
@ -1063,6 +1083,7 @@ async def test_browse_media_browse_whole_month_december(
)
event1._api = ufp.api
event2 = Event(
model=ModelType.EVENT,
id="test_event_id2",
type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20),
@ -1074,6 +1095,7 @@ async def test_browse_media_browse_whole_month_december(
)
event2._api = ufp.api
event3 = Event(
model=ModelType.EVENT,
id="test_event_id3",
type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20),
@ -1085,6 +1107,7 @@ async def test_browse_media_browse_whole_month_december(
)
event3._api = ufp.api
event4 = Event(
model=ModelType.EVENT,
id="test_event_id4",
type=EventType.MOTION,
start=fixed_now - timedelta(seconds=20),

View file

@ -5,7 +5,7 @@ from __future__ import annotations
from datetime import datetime, timedelta
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.history import get_significant_states
@ -40,6 +40,7 @@ async def test_exclude_attributes(
)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.MOTION,
start=fixed_now - timedelta(seconds=1),

View file

@ -6,7 +6,15 @@ from datetime import datetime, timedelta
from unittest.mock import Mock
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 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 = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SENSOR_ALARM,
start=fixed_now - timedelta(seconds=1),
@ -521,6 +530,7 @@ async def test_camera_update_licenseplate(
license_plate=LicensePlateMetadata(name="ABCD1234", confidence_level=95)
)
event = Event(
model=ModelType.EVENT,
id="test_event_id",
type=EventType.SMART_DETECT,
start=fixed_now - timedelta(seconds=1),

View file

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