uiprotect 0.11.0__tar.gz → 0.13.0__tar.gz
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-0.11.0 → uiprotect-0.13.0}/PKG-INFO +1 -1
- {uiprotect-0.11.0 → uiprotect-0.13.0}/pyproject.toml +1 -1
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/bootstrap.py +38 -36
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/utils.py +6 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/LICENSE +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/README.md +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/__init__.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/__main__.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/api.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/__init__.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/backup.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/base.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/cameras.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/chimes.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/doorlocks.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/events.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/lights.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/liveviews.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/nvr.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/sensors.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/cli/viewers.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/__init__.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/base.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/convert.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/devices.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/nvr.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/types.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/user.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/data/websocket.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/exceptions.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/py.typed +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/release_cache.json +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/stream.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/test_util/__init__.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/test_util/anonymize.py +0 -0
- {uiprotect-0.11.0 → uiprotect-0.13.0}/src/uiprotect/websocket.py +0 -0
|
@@ -18,7 +18,7 @@ except ImportError:
|
|
|
18
18
|
from pydantic import PrivateAttr, ValidationError # type: ignore[assignment]
|
|
19
19
|
|
|
20
20
|
from ..exceptions import ClientError
|
|
21
|
-
from ..utils import utc_now
|
|
21
|
+
from ..utils import normalize_mac, utc_now
|
|
22
22
|
from .base import (
|
|
23
23
|
RECENT_EVENT_MAX,
|
|
24
24
|
ProtectBaseObject,
|
|
@@ -131,26 +131,34 @@ def _process_sensor_event(event: Event) -> None:
|
|
|
131
131
|
event.sensor.last_value_event_id = event.id
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
_CAMERA_SMART_AND_LINE_EVENTS = {
|
|
135
|
+
EventType.SMART_DETECT,
|
|
136
|
+
EventType.SMART_DETECT_LINE,
|
|
137
|
+
}
|
|
138
|
+
_CAMERA_SMART_AUDIO_EVENT = EventType.SMART_AUDIO_DETECT
|
|
139
|
+
|
|
140
|
+
|
|
134
141
|
def _process_camera_event(event: Event) -> None:
|
|
135
|
-
if event.camera is None:
|
|
142
|
+
if (camera := event.camera) is None:
|
|
136
143
|
return
|
|
137
144
|
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
event_type = event.type
|
|
146
|
+
dt_attr, event_attr = CAMERA_EVENT_ATTR_MAP[event_type]
|
|
147
|
+
dt = getattr(camera, dt_attr)
|
|
140
148
|
if dt is None or event.start >= dt or (event.end is not None and event.end >= dt):
|
|
141
|
-
setattr(
|
|
142
|
-
setattr(
|
|
143
|
-
if
|
|
149
|
+
setattr(camera, event_attr, event.id)
|
|
150
|
+
setattr(camera, dt_attr, event.start)
|
|
151
|
+
if event_type in _CAMERA_SMART_AND_LINE_EVENTS:
|
|
144
152
|
for smart_type in event.smart_detect_types:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
elif
|
|
153
|
+
camera.last_smart_detect_event_ids[smart_type] = event.id
|
|
154
|
+
camera.last_smart_detects[smart_type] = event.start
|
|
155
|
+
elif event_type is _CAMERA_SMART_AUDIO_EVENT:
|
|
148
156
|
for smart_type in event.smart_detect_types:
|
|
149
157
|
audio_type = smart_type.audio_type
|
|
150
158
|
if audio_type is None:
|
|
151
159
|
continue
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
camera.last_smart_audio_detect_event_ids[audio_type] = event.id
|
|
161
|
+
camera.last_smart_audio_detects[audio_type] = event.start
|
|
154
162
|
|
|
155
163
|
|
|
156
164
|
@dataclass
|
|
@@ -226,7 +234,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
226
234
|
items[item["id"]] = item
|
|
227
235
|
data["idLookup"][item["id"]] = ref
|
|
228
236
|
if "mac" in item:
|
|
229
|
-
cleaned_mac = item["mac"]
|
|
237
|
+
cleaned_mac = normalize_mac(item["mac"])
|
|
230
238
|
data["macLookup"][cleaned_mac] = ref
|
|
231
239
|
data[key] = items
|
|
232
240
|
|
|
@@ -312,8 +320,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
312
320
|
|
|
313
321
|
def get_device_from_mac(self, mac: str) -> ProtectAdoptableDeviceModel | None:
|
|
314
322
|
"""Retrieve a device from MAC address."""
|
|
315
|
-
|
|
316
|
-
ref = self.mac_lookup.get(mac)
|
|
323
|
+
ref = self.mac_lookup.get(normalize_mac(mac))
|
|
317
324
|
if ref is None:
|
|
318
325
|
return None
|
|
319
326
|
|
|
@@ -378,7 +385,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
378
385
|
elif (
|
|
379
386
|
isinstance(obj, ProtectAdoptableDeviceModel)
|
|
380
387
|
and obj.model is not None
|
|
381
|
-
and obj.model.value in ModelType.
|
|
388
|
+
and obj.model.value in ModelType.bootstrap_models_set()
|
|
382
389
|
):
|
|
383
390
|
key = obj.model.value + "s"
|
|
384
391
|
if not self.api.ignore_unadopted or (
|
|
@@ -387,7 +394,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
387
394
|
getattr(self, key)[obj.id] = obj
|
|
388
395
|
ref = ProtectDeviceRef(model=obj.model, id=obj.id)
|
|
389
396
|
self.id_lookup[obj.id] = ref
|
|
390
|
-
self.mac_lookup[obj.mac
|
|
397
|
+
self.mac_lookup[normalize_mac(obj.mac)] = ref
|
|
391
398
|
else:
|
|
392
399
|
_LOGGER.debug("Unexpected bootstrap model type for add: %s", obj.model)
|
|
393
400
|
return None
|
|
@@ -403,23 +410,19 @@ class Bootstrap(ProtectBaseObject):
|
|
|
403
410
|
new_obj=obj,
|
|
404
411
|
)
|
|
405
412
|
|
|
406
|
-
def _process_remove_packet(
|
|
407
|
-
|
|
408
|
-
packet: WSPacket,
|
|
409
|
-
data: dict[str, Any] | None,
|
|
410
|
-
) -> WSSubscriptionMessage | None:
|
|
411
|
-
model = packet.action_frame.data.get("modelKey")
|
|
412
|
-
device_id = packet.action_frame.data.get("id")
|
|
413
|
+
def _process_remove_packet(self, packet: WSPacket) -> WSSubscriptionMessage | None:
|
|
414
|
+
model: str | None = packet.action_frame.data.get("modelKey")
|
|
413
415
|
devices = getattr(self, f"{model}s", None)
|
|
414
416
|
|
|
415
417
|
if devices is None:
|
|
416
418
|
return None
|
|
417
419
|
|
|
420
|
+
device_id: str = packet.action_frame.data["id"]
|
|
418
421
|
self.id_lookup.pop(device_id, None)
|
|
419
422
|
device = devices.pop(device_id, None)
|
|
420
423
|
if device is None:
|
|
421
424
|
return None
|
|
422
|
-
self.mac_lookup.pop(device.mac
|
|
425
|
+
self.mac_lookup.pop(normalize_mac(device.mac), None)
|
|
423
426
|
|
|
424
427
|
self._create_stat(packet, None, False)
|
|
425
428
|
return WSSubscriptionMessage(
|
|
@@ -489,12 +492,11 @@ class Bootstrap(ProtectBaseObject):
|
|
|
489
492
|
|
|
490
493
|
key = f"{model_type}s"
|
|
491
494
|
devices = getattr(self, key)
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
obj: ProtectModelWithId = devices[action["id"]]
|
|
495
|
+
action_id: str = action["id"]
|
|
496
|
+
if action_id in devices:
|
|
497
|
+
if action_id not in devices:
|
|
498
|
+
raise ValueError(f"Unknown device update for {model_type}: {action_id}")
|
|
499
|
+
obj: ProtectModelWithId = devices[action_id]
|
|
498
500
|
data = obj.unifi_dict_to_dict(data)
|
|
499
501
|
old_obj = obj.copy()
|
|
500
502
|
obj = obj.update_from_dict(deepcopy(data))
|
|
@@ -517,7 +519,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
517
519
|
if is_recent:
|
|
518
520
|
obj.set_alarm_timeout()
|
|
519
521
|
|
|
520
|
-
devices[
|
|
522
|
+
devices[action_id] = obj
|
|
521
523
|
|
|
522
524
|
self._create_stat(packet, data, False)
|
|
523
525
|
return WSSubscriptionMessage(
|
|
@@ -529,8 +531,8 @@ class Bootstrap(ProtectBaseObject):
|
|
|
529
531
|
)
|
|
530
532
|
|
|
531
533
|
# ignore updates to events that phase out
|
|
532
|
-
if model_type !=
|
|
533
|
-
_LOGGER.debug("Unexpected %s: %s", key,
|
|
534
|
+
if model_type != _ModelType_Event_value:
|
|
535
|
+
_LOGGER.debug("Unexpected %s: %s", key, action_id)
|
|
534
536
|
return None
|
|
535
537
|
|
|
536
538
|
def process_ws_packet(
|
|
@@ -557,7 +559,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
557
559
|
|
|
558
560
|
action_action: str = action["action"]
|
|
559
561
|
if action_action == "remove":
|
|
560
|
-
return self._process_remove_packet(packet
|
|
562
|
+
return self._process_remove_packet(packet)
|
|
561
563
|
|
|
562
564
|
if not data:
|
|
563
565
|
self._create_stat(packet, None, True)
|
|
@@ -596,7 +598,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
596
598
|
else:
|
|
597
599
|
try:
|
|
598
600
|
model_type = ModelType(action["modelKey"])
|
|
599
|
-
device_id = action["id"]
|
|
601
|
+
device_id: str = action["id"]
|
|
600
602
|
task = asyncio.create_task(self.refresh_device(model_type, device_id))
|
|
601
603
|
self._refresh_tasks.add(task)
|
|
602
604
|
task.add_done_callback(self._refresh_tasks.discard)
|
|
@@ -624,3 +624,9 @@ def clamp_value(value: float, step_size: float) -> float:
|
|
|
624
624
|
"""Clamps value to multiples of step size."""
|
|
625
625
|
ratio = 1 / step_size
|
|
626
626
|
return int(value * ratio) / ratio
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
@lru_cache(maxsize=1024)
|
|
630
|
+
def normalize_mac(mac: str) -> str:
|
|
631
|
+
"""Normalize MAC address."""
|
|
632
|
+
return mac.lower().replace(":", "").replace("-", "").replace("_", "")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|