uiprotect 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of uiprotect might be problematic. Click here for more details.
- uiprotect/api.py +7 -0
- uiprotect/data/bootstrap.py +56 -69
- {uiprotect-1.0.0.dist-info → uiprotect-1.1.0.dist-info}/METADATA +1 -1
- {uiprotect-1.0.0.dist-info → uiprotect-1.1.0.dist-info}/RECORD +7 -7
- {uiprotect-1.0.0.dist-info → uiprotect-1.1.0.dist-info}/LICENSE +0 -0
- {uiprotect-1.0.0.dist-info → uiprotect-1.1.0.dist-info}/WHEEL +0 -0
- {uiprotect-1.0.0.dist-info → uiprotect-1.1.0.dist-info}/entry_points.txt +0 -0
uiprotect/api.py
CHANGED
|
@@ -65,6 +65,13 @@ from .utils import (
|
|
|
65
65
|
)
|
|
66
66
|
from .websocket import Websocket
|
|
67
67
|
|
|
68
|
+
if sys.version_info[:2] < (3, 13):
|
|
69
|
+
from http import cookies
|
|
70
|
+
|
|
71
|
+
# See: https://github.com/python/cpython/issues/112713
|
|
72
|
+
cookies.Morsel._reserved["partitioned"] = "partitioned" # type: ignore[attr-defined]
|
|
73
|
+
cookies.Morsel._flags.add("partitioned") # type: ignore[attr-defined]
|
|
74
|
+
|
|
68
75
|
TOKEN_COOKIE_MAX_EXP_SECONDS = 60
|
|
69
76
|
|
|
70
77
|
NEVER_RAN = -1000
|
uiprotect/data/bootstrap.py
CHANGED
|
@@ -22,6 +22,7 @@ from ..utils import normalize_mac, utc_now
|
|
|
22
22
|
from .base import (
|
|
23
23
|
RECENT_EVENT_MAX,
|
|
24
24
|
ProtectBaseObject,
|
|
25
|
+
ProtectDeviceModel,
|
|
25
26
|
ProtectModel,
|
|
26
27
|
ProtectModelWithId,
|
|
27
28
|
)
|
|
@@ -65,6 +66,7 @@ STATS_KEYS = {
|
|
|
65
66
|
"recordingSchedules",
|
|
66
67
|
}
|
|
67
68
|
IGNORE_DEVICE_KEYS = {"nvrMac", "guid"}
|
|
69
|
+
STATS_AND_IGNORE_DEVICE_KEYS = STATS_KEYS | IGNORE_DEVICE_KEYS
|
|
68
70
|
|
|
69
71
|
CAMERA_EVENT_ATTR_MAP: dict[EventType, tuple[str, str]] = {
|
|
70
72
|
EventType.MOTION: ("last_motion", "last_motion_event_id"),
|
|
@@ -78,56 +80,36 @@ CAMERA_EVENT_ATTR_MAP: dict[EventType, tuple[str, str]] = {
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
|
|
81
|
-
def _remove_stats_keys(data: dict[str, Any]) -> None:
|
|
82
|
-
for key in STATS_KEYS.intersection(data):
|
|
83
|
-
del data[key]
|
|
84
|
-
|
|
85
|
-
|
|
86
83
|
def _process_light_event(event: Event) -> None:
|
|
87
84
|
if event.light is None:
|
|
88
85
|
return
|
|
89
86
|
|
|
90
|
-
|
|
91
|
-
if dt is None or event.start >= dt or (event.end is not None and event.end >= dt):
|
|
87
|
+
if _event_is_in_range(event, event.light.last_motion):
|
|
92
88
|
event.light.last_motion_event_id = event.id
|
|
93
89
|
|
|
94
90
|
|
|
91
|
+
def _event_is_in_range(event: Event, dt: datetime | None) -> bool:
|
|
92
|
+
"""Check if event is in range of datetime."""
|
|
93
|
+
return (
|
|
94
|
+
dt is None or event.start >= dt or (event.end is not None and event.end >= dt)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
95
98
|
def _process_sensor_event(event: Event) -> None:
|
|
96
99
|
if event.sensor is None:
|
|
97
100
|
return
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
dt = event.sensor.motion_detected_at
|
|
101
|
-
if (
|
|
102
|
-
dt is None
|
|
103
|
-
or event.start >= dt
|
|
104
|
-
or (event.end is not None and event.end >= dt)
|
|
105
|
-
):
|
|
101
|
+
if event.type is EventType.MOTION_SENSOR:
|
|
102
|
+
if _event_is_in_range(event, event.sensor.motion_detected_at):
|
|
106
103
|
event.sensor.last_motion_event_id = event.id
|
|
107
104
|
elif event.type in {EventType.SENSOR_CLOSED, EventType.SENSOR_OPENED}:
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
dt is None
|
|
111
|
-
or event.start >= dt
|
|
112
|
-
or (event.end is not None and event.end >= dt)
|
|
113
|
-
):
|
|
105
|
+
if _event_is_in_range(event, event.sensor.open_status_changed_at):
|
|
114
106
|
event.sensor.last_contact_event_id = event.id
|
|
115
|
-
elif event.type
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
dt is None
|
|
119
|
-
or event.start >= dt
|
|
120
|
-
or (event.end is not None and event.end >= dt)
|
|
121
|
-
):
|
|
107
|
+
elif event.type is EventType.SENSOR_EXTREME_VALUE:
|
|
108
|
+
if _event_is_in_range(event, event.sensor.extreme_value_detected_at):
|
|
122
109
|
event.sensor.extreme_value_detected_at = event.end
|
|
123
110
|
event.sensor.last_value_event_id = event.id
|
|
124
|
-
elif event.type
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
dt is None
|
|
128
|
-
or event.start >= dt
|
|
129
|
-
or (event.end is not None and event.end >= dt)
|
|
130
|
-
):
|
|
111
|
+
elif event.type is EventType.SENSOR_ALARM:
|
|
112
|
+
if _event_is_in_range(event, event.sensor.alarm_triggered_at):
|
|
131
113
|
event.sensor.last_value_event_id = event.id
|
|
132
114
|
|
|
133
115
|
|
|
@@ -144,21 +126,25 @@ def _process_camera_event(event: Event) -> None:
|
|
|
144
126
|
|
|
145
127
|
event_type = event.type
|
|
146
128
|
dt_attr, event_attr = CAMERA_EVENT_ATTR_MAP[event_type]
|
|
147
|
-
dt = getattr(camera, dt_attr)
|
|
148
|
-
if
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
129
|
+
dt: datetime | None = getattr(camera, dt_attr)
|
|
130
|
+
if not _event_is_in_range(event, dt):
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
event_id = event.id
|
|
134
|
+
event_start = event.start
|
|
135
|
+
|
|
136
|
+
setattr(camera, event_attr, event_id)
|
|
137
|
+
setattr(camera, dt_attr, event_start)
|
|
138
|
+
if event_type in _CAMERA_SMART_AND_LINE_EVENTS:
|
|
139
|
+
for smart_type in event.smart_detect_types:
|
|
140
|
+
camera.last_smart_detect_event_ids[smart_type] = event_id
|
|
141
|
+
camera.last_smart_detects[smart_type] = event_start
|
|
142
|
+
elif event_type is _CAMERA_SMART_AUDIO_EVENT:
|
|
143
|
+
for smart_type in event.smart_detect_types:
|
|
144
|
+
if (audio_type := smart_type.audio_type) is None:
|
|
145
|
+
continue
|
|
146
|
+
camera.last_smart_audio_detect_event_ids[audio_type] = event_id
|
|
147
|
+
camera.last_smart_audio_detects[audio_type] = event_start
|
|
162
148
|
|
|
163
149
|
|
|
164
150
|
@dataclass
|
|
@@ -324,7 +310,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
324
310
|
if ref is None:
|
|
325
311
|
return None
|
|
326
312
|
|
|
327
|
-
devices = getattr(self, f"{ref.model.value}s")
|
|
313
|
+
devices: dict[str, ProtectModelWithId] = getattr(self, f"{ref.model.value}s")
|
|
328
314
|
return cast(ProtectAdoptableDeviceModel, devices.get(ref.id))
|
|
329
315
|
|
|
330
316
|
def get_device_from_id(self, device_id: str) -> ProtectAdoptableDeviceModel | None:
|
|
@@ -332,7 +318,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
332
318
|
ref = self.id_lookup.get(device_id)
|
|
333
319
|
if ref is None:
|
|
334
320
|
return None
|
|
335
|
-
devices = getattr(self, f"{ref.model.value}s")
|
|
321
|
+
devices: dict[str, ProtectModelWithId] = getattr(self, f"{ref.model.value}s")
|
|
336
322
|
return cast(ProtectAdoptableDeviceModel, devices.get(ref.id))
|
|
337
323
|
|
|
338
324
|
def process_event(self, event: Event) -> None:
|
|
@@ -363,14 +349,6 @@ class Bootstrap(ProtectBaseObject):
|
|
|
363
349
|
),
|
|
364
350
|
)
|
|
365
351
|
|
|
366
|
-
def _get_frame_data(
|
|
367
|
-
self,
|
|
368
|
-
packet: WSPacket,
|
|
369
|
-
) -> tuple[dict[str, Any], dict[str, Any] | None]:
|
|
370
|
-
if self.capture_ws_stats:
|
|
371
|
-
return deepcopy(packet.action_frame.data), deepcopy(packet.data_frame.data)
|
|
372
|
-
return packet.action_frame.data, packet.data_frame.data
|
|
373
|
-
|
|
374
352
|
def _process_add_packet(
|
|
375
353
|
self,
|
|
376
354
|
packet: WSPacket,
|
|
@@ -412,7 +390,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
412
390
|
|
|
413
391
|
def _process_remove_packet(self, packet: WSPacket) -> WSSubscriptionMessage | None:
|
|
414
392
|
model: str | None = packet.action_frame.data.get("modelKey")
|
|
415
|
-
devices = getattr(self, f"{model}s", None)
|
|
393
|
+
devices: dict[str, ProtectDeviceModel] | None = getattr(self, f"{model}s", None)
|
|
416
394
|
|
|
417
395
|
if devices is None:
|
|
418
396
|
return None
|
|
@@ -439,7 +417,8 @@ class Bootstrap(ProtectBaseObject):
|
|
|
439
417
|
ignore_stats: bool,
|
|
440
418
|
) -> WSSubscriptionMessage | None:
|
|
441
419
|
if ignore_stats:
|
|
442
|
-
|
|
420
|
+
for key in STATS_KEYS.intersection(data):
|
|
421
|
+
del data[key]
|
|
443
422
|
# nothing left to process
|
|
444
423
|
if not data:
|
|
445
424
|
self._create_stat(packet, None, True)
|
|
@@ -477,9 +456,10 @@ class Bootstrap(ProtectBaseObject):
|
|
|
477
456
|
ignore_stats: bool,
|
|
478
457
|
) -> WSSubscriptionMessage | None:
|
|
479
458
|
model_type = action["modelKey"]
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
459
|
+
remove_keys = (
|
|
460
|
+
STATS_AND_IGNORE_DEVICE_KEYS if ignore_stats else IGNORE_DEVICE_KEYS
|
|
461
|
+
)
|
|
462
|
+
for key in remove_keys.intersection(data):
|
|
483
463
|
del data[key]
|
|
484
464
|
# `last_motion` from cameras update every 100 milliseconds when a motion event is active
|
|
485
465
|
# this overrides the behavior to only update `last_motion` when a new event starts
|
|
@@ -491,12 +471,12 @@ class Bootstrap(ProtectBaseObject):
|
|
|
491
471
|
return None
|
|
492
472
|
|
|
493
473
|
key = f"{model_type}s"
|
|
494
|
-
devices = getattr(self, key)
|
|
474
|
+
devices: dict[str, ProtectModelWithId] = getattr(self, key)
|
|
495
475
|
action_id: str = action["id"]
|
|
496
476
|
if action_id in devices:
|
|
497
477
|
if action_id not in devices:
|
|
498
478
|
raise ValueError(f"Unknown device update for {model_type}: {action_id}")
|
|
499
|
-
obj
|
|
479
|
+
obj = devices[action_id]
|
|
500
480
|
data = obj.unifi_dict_to_dict(data)
|
|
501
481
|
old_obj = obj.copy()
|
|
502
482
|
obj = obj.update_from_dict(deepcopy(data))
|
|
@@ -542,7 +522,12 @@ class Bootstrap(ProtectBaseObject):
|
|
|
542
522
|
ignore_stats: bool = False,
|
|
543
523
|
) -> WSSubscriptionMessage | None:
|
|
544
524
|
"""Process a WS packet."""
|
|
545
|
-
action
|
|
525
|
+
action = packet.action_frame.data
|
|
526
|
+
data = packet.data_frame.data
|
|
527
|
+
if self.capture_ws_stats:
|
|
528
|
+
action = deepcopy(action)
|
|
529
|
+
data = deepcopy(data)
|
|
530
|
+
|
|
546
531
|
new_update_id: str = action["newUpdateId"]
|
|
547
532
|
if new_update_id is not None:
|
|
548
533
|
self.last_update_id = new_update_id
|
|
@@ -629,7 +614,9 @@ class Bootstrap(ProtectBaseObject):
|
|
|
629
614
|
if isinstance(device, NVR):
|
|
630
615
|
self.nvr = device
|
|
631
616
|
else:
|
|
632
|
-
devices = getattr(
|
|
617
|
+
devices: dict[str, ProtectModelWithId] = getattr(
|
|
618
|
+
self, f"{model_type.value}s"
|
|
619
|
+
)
|
|
633
620
|
devices[device.id] = device
|
|
634
621
|
_LOGGER.debug("Successfully refresh model: %s %s", model_type, device_id)
|
|
635
622
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
uiprotect/__init__.py,sha256=llnQNtiBfwQG8IkQXovvFz4LZeFjrJx7XdmmUhu3a9E,289
|
|
2
2
|
uiprotect/__main__.py,sha256=C_bHCOkv5qj6WMy-6ELoY3Y6HDhLxOa1a30CzmbZhsg,462
|
|
3
|
-
uiprotect/api.py,sha256=
|
|
3
|
+
uiprotect/api.py,sha256=hN1PvNhOTFIfOmBtnUCfpujIXBEBYIKttaYnqlS_2es,66463
|
|
4
4
|
uiprotect/cli/__init__.py,sha256=sSLW9keVQOkgFcMW18HTDjRrt9sJ0KWjn9DJDA6f9Pc,8658
|
|
5
5
|
uiprotect/cli/backup.py,sha256=ZiS7RZnJGKI8TJKLW2cOUzkRM8nyTvE5Ov_jZZGtvSM,36708
|
|
6
6
|
uiprotect/cli/base.py,sha256=zpTm2kyJe_GLixnv3Uadke__iRLh64AEwQzp-2hqS7g,7730
|
|
@@ -15,7 +15,7 @@ uiprotect/cli/sensors.py,sha256=fQtcDJCVxs4VbAqcavgBy2ABiVxAW3GXtna6_XFBp2k,8153
|
|
|
15
15
|
uiprotect/cli/viewers.py,sha256=2cyrp104ffIvgT0wYGIO0G35QMkEbFe7fSVqLwDXQYQ,2171
|
|
16
16
|
uiprotect/data/__init__.py,sha256=OcfuJl2qXfHcj_mdnrHhzZ5tEIZrw8auziX5IE7dn-I,2938
|
|
17
17
|
uiprotect/data/base.py,sha256=p75RcPjVTmHEk6BVWApougBC2RBwUdFq8x_gL4zr8xc,37313
|
|
18
|
-
uiprotect/data/bootstrap.py,sha256=
|
|
18
|
+
uiprotect/data/bootstrap.py,sha256=NH4jSEm44M0aijmViLHip2PKhVchuZJEeqWI3bcZwq0,21285
|
|
19
19
|
uiprotect/data/convert.py,sha256=rOQplUMIdTMD2SbAx_iI9BNPDscnhDvyRVLEMDhtADg,2047
|
|
20
20
|
uiprotect/data/devices.py,sha256=Nq3bOko5PFf5LvEBoD4JV8kmbq50laRdh3VHMWX7t-0,111809
|
|
21
21
|
uiprotect/data/nvr.py,sha256=aIi4d0KCJ55r1EP-33sBhvEa8lx-41jDGrDpAq5VnOE,47598
|
|
@@ -30,8 +30,8 @@ uiprotect/test_util/__init__.py,sha256=d2g7afa0LSdixQ0kjEDYwafDFME_UlW2LzxpamZ2B
|
|
|
30
30
|
uiprotect/test_util/anonymize.py,sha256=f-8ijU-_y9r-uAbhIPn0f0I6hzJpAkvJzc8UpWihObI,8478
|
|
31
31
|
uiprotect/utils.py,sha256=6OLY8hNiCzk418PjJJIlFW7jjPzVt1vxBKEzBSqMeTk,18418
|
|
32
32
|
uiprotect/websocket.py,sha256=IzDPyqbzrkOMREvahN-e2zdvVD0VABSCWy6jSoCwOT0,7299
|
|
33
|
-
uiprotect-1.
|
|
34
|
-
uiprotect-1.
|
|
35
|
-
uiprotect-1.
|
|
36
|
-
uiprotect-1.
|
|
37
|
-
uiprotect-1.
|
|
33
|
+
uiprotect-1.1.0.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
|
|
34
|
+
uiprotect-1.1.0.dist-info/METADATA,sha256=--s4fFpQ0KbuDx6VYuPnkko1w4_f2hRpGR-IJOvZrt4,10984
|
|
35
|
+
uiprotect-1.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
36
|
+
uiprotect-1.1.0.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
|
|
37
|
+
uiprotect-1.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|