Exemplo n.º 1
0
    async def get_camera_video(
            self,
            camera_id: str,
            start: datetime,
            end: datetime,
            channel_index: int = 0,
            validate_channel_id: bool = True) -> Optional[bytes]:
        """Exports MP4 video from a given camera at a specific time"""

        if validate_channel_id and self._bootstrap is not None:
            camera = self._bootstrap.cameras[camera_id]
            try:
                camera.channels[channel_index]
            except IndexError as e:
                raise BadRequest from e

        params = {
            "camera": camera_id,
            "channel": channel_index,
            "start": to_js_time(start),
            "end": to_js_time(end),
        }

        return await self.api_request_raw("video/export",
                                          params=params,
                                          raise_exception=False)
Exemplo n.º 2
0
async def test_ws_event_smart(protect_client_no_debug: ProtectApiClient, now,
                              camera, packet: WSPacket):
    protect_client = protect_client_no_debug

    def get_camera():
        return protect_client.bootstrap.cameras[camera["id"]]

    bootstrap_before = protect_client.bootstrap.unifi_dict()
    camera_before = get_camera().copy()

    expected_updated_id = "0441ecc6-f0fa-4b19-b071-7987c143138a"
    expected_event_id = "bf9a241afe74821ceffffd05"

    action_frame: WSJSONPacketFrame = packet.action_frame  # type: ignore
    action_frame.data["newUpdateId"] = expected_updated_id

    data_frame: WSJSONPacketFrame = packet.data_frame  # type: ignore
    data_frame.data = {
        "id": expected_event_id,
        "type": "smartDetectZone",
        "start": to_js_time(now - timedelta(seconds=30)),
        "end": to_js_time(now),
        "score": 0,
        "smartDetectTypes": ["person"],
        "smartDetectEvents": [],
        "camera": camera["id"],
        "partition": None,
        "user": None,
        "metadata": {},
        "thumbnail": f"e-{expected_event_id}",
        "heatmap": f"e-{expected_event_id}",
        "modelKey": "event",
    }

    msg = MagicMock()
    msg.data = packet.pack_frames()

    protect_client._process_ws_message(msg)

    bootstrap_before["lastUpdateId"] = expected_updated_id
    bootstrap = protect_client.bootstrap.unifi_dict()
    camera = get_camera()

    smart_event = camera.last_smart_detect_event
    camera.last_smart_detect_event_id = None
    camera_before.last_smart_detect_event_id = None

    assert bootstrap == bootstrap_before
    assert camera.dict() == camera_before.dict()
    assert smart_event.id == expected_event_id
    assert smart_event.type == EventType.SMART_DETECT
    assert smart_event.thumbnail_id == f"e-{expected_event_id}"
    assert smart_event.heatmap_id == f"e-{expected_event_id}"
    assert smart_event.start == (now - timedelta(seconds=30))
    assert smart_event.end == now

    for channel in camera.channels:
        assert channel._api is not None
Exemplo n.º 3
0
async def test_ws_emit_alarm_callback(
    mock_now, protect_client_no_debug: ProtectApiClient, now: datetime, sensor, packet: WSPacket
):
    mock_now.return_value = now
    protect_client = protect_client_no_debug
    protect_client.emit_message = Mock()  # type: ignore

    obj = protect_client.bootstrap.sensors[sensor["id"]]

    expected_updated_id = "0441ecc6-f0fa-4b19-b071-7987c143138a"

    action_frame: WSJSONPacketFrame = packet.action_frame  # type: ignore
    action_frame.data = {
        "action": "update",
        "newUpdateId": expected_updated_id,
        "modelKey": "sensor",
        "id": sensor["id"],
    }

    data_frame: WSJSONPacketFrame = packet.data_frame  # type: ignore
    data_frame.data = {"alarmTriggeredAt": to_js_time(now)}

    msg = MagicMock()
    msg.data = packet.pack_frames()

    assert not obj.is_alarm_detected
    with patch("pyunifiprotect.data.bootstrap.utc_now", mock_now):
        protect_client._process_ws_message(msg)
    assert obj.is_alarm_detected
    mock_now.return_value = utc_now() + EVENT_PING_INTERVAL
    assert not obj.is_alarm_detected

    protect_client.emit_message.assert_called_once()
Exemplo n.º 4
0
async def test_camera_set_lcd_text_custom_to_custom(
        camera_obj: Optional[Camera]):

    camera_obj.api.api_request.reset_mock()

    camera_obj.feature_flags.has_lcd_screen = True
    camera_obj.lcd_message = LCDMessage(
        type=DoorbellMessageType.CUSTOM_MESSAGE,
        text="Welcome",
        reset_at=None,
    )
    camera_obj._initial_data = camera_obj.dict()

    now = datetime.utcnow()
    await camera_obj.set_lcd_text(DoorbellMessageType.CUSTOM_MESSAGE, "Test",
                                  now)

    camera_obj.api.api_request.assert_called_with(
        f"cameras/{camera_obj.id}",
        method="patch",
        json={
            "lcdMessage": {
                "type": DoorbellMessageType.CUSTOM_MESSAGE.value,
                "text": "Test",
                "resetAt": to_js_time(now),
            }
        },
    )
Exemplo n.º 5
0
    async def get_events_raw(
        self,
        start: Optional[datetime] = None,
        end: Optional[datetime] = None,
        limit: Optional[int] = None,
        camera_ids: Optional[List[str]] = None,
    ) -> List[Dict[str, Any]]:
        """
        Get list of events from Protect

        Args:

        * `start`: start time for events
        * `end`: end time for events
        * `limit`: max number of events to return
        * `camera_ids`: list of Cameras to get events for

        If `limit`, `start` and `end` are not provided, it will default to all events in the last 24 hours.

        If `start` is provided, then `end` or `limit` must be provided. If `end` is provided, then `start` or
        `limit` must be provided. Otherwise, you will get a 400 error from Unifi Protect

        Providing a list of Camera IDs will not prevent non-camera events from returning.
        """

        # if no parameters are passed in, default to all events from last 24 hours
        if limit is None and start is None and end is None:
            end = utc_now() + timedelta(seconds=10)
            start = end - timedelta(hours=24)

        params: Dict[str, Any] = {}
        if limit is not None:
            params["limit"] = limit

        if start is not None:
            params["start"] = to_js_time(start)

        if end is not None:
            params["end"] = to_js_time(end)

        if camera_ids is not None:
            params["cameras"] = ",".join(camera_ids)

        return await self.api_request_list("events", params=params)
Exemplo n.º 6
0
async def test_get_events_raw_default(protect_client: ProtectApiClient, now: datetime):
    events = await protect_client.get_events_raw()

    end = now + timedelta(seconds=10)

    protect_client.api_request.assert_called_with(  # type: ignore
        url="events",
        method="get",
        require_auth=True,
        raise_exception=True,
        params={
            "start": to_js_time(end - timedelta(hours=24)),
            "end": to_js_time(end),
        },
    )
    assert len(events) == CONSTANTS["event_count"]
    for event in events:
        assert event["type"] in EventType.values()
        assert event["modelKey"] in ModelType.values()
Exemplo n.º 7
0
 async def get_events(*args, **kwargs):
     return [
         {
             "id": expected_event_id,
             "type": "ring",
             "start": to_js_time(now - timedelta(seconds=1)),
             "end": to_js_time(now),
             "score": 0,
             "smartDetectTypes": [],
             "smartDetectEvents": [],
             "camera": camera["id"],
             "partition": None,
             "user": None,
             "metadata": {},
             "thumbnail": f"e-{expected_event_id}",
             "heatmap": f"e-{expected_event_id}",
             "modelKey": "event",
         },
     ]
Exemplo n.º 8
0
async def test_get_raw_events(old_protect_client: UpvServer, now: datetime):
    events = await old_protect_client.get_raw_events()

    old_protect_client.api_request.assert_called_with(  # type: ignore
        url="events",
        method="get",
        require_auth=True,
        raise_exception=True,
        params={
            "end": str(to_js_time(now + timedelta(seconds=10))),
            "start": str(to_js_time(now - timedelta(seconds=86400))),
        },
    )
    assert len(events) == CONSTANTS["event_count"]
    for event in events:
        event_type: str = event["type"]
        model_type: str = event["modelKey"]

        assert event_type in EventType.values()
        assert model_type in ModelType.values()
Exemplo n.º 9
0
    def unifi_dict(self,
                   data: Optional[Dict[str, Any]] = None,
                   exclude: Optional[Set[str]] = None) -> Dict[str, Any]:
        data = super().unifi_dict(data=data, exclude=exclude)

        if "text" in data:
            data["text"] = self._fix_text(data["text"],
                                          data.get("type", self.type.value))
        if "resetAt" in data:
            data["resetAt"] = to_js_time(data["resetAt"])

        return data
Exemplo n.º 10
0
async def test_get_camera_video(protect_client: ProtectApiClient, now, tmp_binary_file):
    camera = list(protect_client.bootstrap.cameras.values())[0]
    start = now - timedelta(seconds=CONSTANTS["camera_video_length"])

    data = await protect_client.get_camera_video(camera.id, start, now)
    assert data is not None

    protect_client.api_request_raw.assert_called_with(  # type: ignore
        "video/export",
        params={
            "camera": camera.id,
            "channel": 0,
            "start": to_js_time(start),
            "end": to_js_time(now),
        },
        raise_exception=False,
    )

    tmp_binary_file.write(data)
    tmp_binary_file.close()

    validate_video_file(tmp_binary_file.name, CONSTANTS["camera_video_length"])
Exemplo n.º 11
0
async def test_ws_event_update(protect_client_no_debug: ProtectApiClient, now,
                               camera, packet: WSPacket):
    protect_client = protect_client_no_debug

    def get_camera() -> Camera:
        return protect_client.bootstrap.cameras[camera["id"]]

    bootstrap_before = protect_client.bootstrap.unifi_dict()
    camera_before = get_camera().copy()

    new_stats = camera_before.stats.unifi_dict()
    new_stats["rxBytes"] += 100
    new_stats["txBytes"] += 100
    new_stats["video"]["recordingEnd"] = to_js_time(now)
    new_stats_unifi = camera_before.unifi_dict(
        data={"stats": deepcopy(new_stats)})

    del new_stats_unifi["stats"]["wifiQuality"]
    del new_stats_unifi["stats"]["wifiStrength"]

    expected_updated_id = "0441ecc6-f0fa-4b19-b071-7987c143138a"

    action_frame: WSJSONPacketFrame = packet.action_frame  # type: ignore
    action_frame.data = {
        "action": "update",
        "newUpdateId": expected_updated_id,
        "modelKey": "camera",
        "id": camera["id"],
    }

    data_frame: WSJSONPacketFrame = packet.data_frame  # type: ignore
    data_frame.data = new_stats_unifi

    msg = MagicMock()
    msg.data = packet.pack_frames()

    protect_client._process_ws_message(msg)

    camera_index = -1
    for index, camera_dict in enumerate(bootstrap_before["cameras"]):
        if camera_dict["id"] == camera["id"]:
            camera_index = index
            break

    bootstrap_before["cameras"][camera_index]["stats"] = new_stats
    bootstrap_before["lastUpdateId"] = expected_updated_id
    bootstrap = protect_client.bootstrap.unifi_dict()

    assert bootstrap == bootstrap_before
Exemplo n.º 12
0
async def test_get_pacakge_camera_snapshot(protect_client: ProtectApiClient, now):
    data = await protect_client.get_package_camera_snapshot("test_id")
    assert data is not None

    protect_client.api_request_raw.assert_called_with(  # type: ignore
        "cameras/test_id/package-snapshot",
        params={
            "ts": to_js_time(now),
            "force": "true",
        },
        raise_exception=False,
    )

    img = Image.open(BytesIO(data))
    assert img.format in ("PNG", "JPEG")
Exemplo n.º 13
0
async def test_camera_set_lcd_text_none(mock_now, camera_obj: Optional[Camera],
                                        now: datetime):
    mock_now.return_value = now

    if camera_obj is None:
        pytest.skip("No camera_obj obj found")

    camera_obj.api.emit_message = Mock()
    camera_obj.api.api_request.reset_mock()

    camera_obj.feature_flags.has_lcd_screen = True
    camera_obj.lcd_message = LCDMessage(
        type=DoorbellMessageType.DO_NOT_DISTURB,
        text=DoorbellMessageType.DO_NOT_DISTURB.value.replace("_", " "),
        reset_at=None,
    )
    camera_obj._initial_data = camera_obj.dict()

    await camera_obj.set_lcd_text(None)

    expected_dt = now - timedelta(seconds=10)
    camera_obj.api.api_request.assert_called_with(
        f"cameras/{camera_obj.id}",
        method="patch",
        json={"lcdMessage": {
            "resetAt": to_js_time(expected_dt),
        }},
    )

    # old/new is actually the same here since the client
    # generating the message is the one that changed it
    camera_obj.api.emit_message.assert_called_with(
        WSSubscriptionMessage(
            action=WSAction.UPDATE,
            new_update_id=camera_obj.api.bootstrap.last_update_id,
            changed_data={"lcd_message": {
                "reset_at": expected_dt
            }},
            old_obj=camera_obj,
            new_obj=camera_obj,
        ))
Exemplo n.º 14
0
async def test_get_snapshot(old_protect_client: UpvServer, now, camera):
    data = await old_protect_client.get_snapshot_image(camera_id=camera["id"])
    if data is None:
        return

    height = old_protect_client.devices[camera["id"]].get("image_height")
    width = old_protect_client.devices[camera["id"]].get("image_width")

    old_protect_client.api_request_raw.assert_called_with(  # type: ignore
        f"cameras/{camera['id']}/snapshot",
        params={
            "h": height,
            "ts": str(to_js_time(now)),
            "force": "true",
            "w": width,
        },
        raise_exception=False,
    )

    img = Image.open(BytesIO(data))
    assert img.width == width
    assert img.height == height
Exemplo n.º 15
0
    async def get_camera_snapshot(
        self,
        camera_id: str,
        width: Optional[int] = None,
        height: Optional[int] = None,
    ) -> Optional[bytes]:
        """Gets a snapshot from a camera"""

        dt = utc_now()  # ts is only used as a cache buster
        params = {
            "ts": to_js_time(dt),
            "force": "true",
        }

        if width is not None:
            params.update({"w": width})

        if height is not None:
            params.update({"h": height})

        return await self.api_request_raw(f"cameras/{camera_id}/snapshot",
                                          params=params,
                                          raise_exception=False)
Exemplo n.º 16
0
async def test_camera_set_lcd_text_default(mock_now,
                                           camera_obj: Optional[Camera],
                                           now: datetime):
    mock_now.return_value = now

    if camera_obj is None:
        pytest.skip("No camera_obj obj found")

    camera_obj.api.api_request.reset_mock()

    camera_obj.feature_flags.has_lcd_screen = True
    camera_obj.lcd_message = LCDMessage(
        type=DoorbellMessageType.DO_NOT_DISTURB,
        text=DoorbellMessageType.DO_NOT_DISTURB.value.replace("_", " "),
        reset_at=None,
    )
    camera_obj._initial_data = camera_obj.dict()

    await camera_obj.set_lcd_text(DoorbellMessageType.LEAVE_PACKAGE_AT_DOOR,
                                  reset_at=DEFAULT)

    expected_dt = now + camera_obj.api.bootstrap.nvr.doorbell_settings.default_message_reset_timeout
    camera_obj.api.api_request.assert_called_with(
        f"cameras/{camera_obj.id}",
        method="patch",
        json={
            "lcdMessage": {
                "type":
                DoorbellMessageType.LEAVE_PACKAGE_AT_DOOR.value,
                "text":
                DoorbellMessageType.LEAVE_PACKAGE_AT_DOOR.value.replace(
                    "_", " "),
                "resetAt":
                to_js_time(expected_dt),
            }
        },
    )