uiprotect 1.11.1__py3-none-any.whl → 1.12.1__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/data/base.py CHANGED
@@ -61,6 +61,9 @@ RECENT_EVENT_MAX = timedelta(seconds=30)
61
61
  EVENT_PING_INTERVAL = timedelta(seconds=3)
62
62
  EVENT_PING_INTERVAL_SECONDS = EVENT_PING_INTERVAL.total_seconds()
63
63
 
64
+ _EMPTY_EVENT_PING_BACK: dict[Any, Any] = {}
65
+
66
+
64
67
  _LOGGER = logging.getLogger(__name__)
65
68
 
66
69
 
@@ -788,7 +791,7 @@ class ProtectModelWithId(ProtectModel):
788
791
 
789
792
  def _emit_message(self, updated: dict[str, Any]) -> None:
790
793
  """Emits fake WS message for ProtectApiClient to process."""
791
- if updated == {}:
794
+ if _is_ping_back := updated is _EMPTY_EVENT_PING_BACK:
792
795
  _LOGGER.debug("Event ping callback started for %s", self.id)
793
796
 
794
797
  if self.model is None:
@@ -817,7 +820,9 @@ class ProtectModelWithId(ProtectModel):
817
820
 
818
821
  message = self._api.bootstrap.process_ws_packet(
819
822
  WSPacket(action_frame.packed + data_frame.packed),
823
+ is_ping_back=_is_ping_back,
820
824
  )
825
+
821
826
  if message is not None:
822
827
  self._api.emit_message(message)
823
828
 
@@ -876,7 +881,7 @@ class ProtectDeviceModel(ProtectModelWithId):
876
881
  self._callback_ping = loop.call_later(
877
882
  EVENT_PING_INTERVAL_SECONDS,
878
883
  self._emit_message,
879
- {},
884
+ _EMPTY_EVENT_PING_BACK,
880
885
  )
881
886
 
882
887
  async def set_name(self, name: str | None) -> None:
@@ -56,6 +56,7 @@ _LOGGER = logging.getLogger(__name__)
56
56
  MAX_SUPPORTED_CAMERAS = 256
57
57
  MAX_EVENT_HISTORY_IN_STATE_MACHINE = MAX_SUPPORTED_CAMERAS * 2
58
58
  STATS_KEYS = {
59
+ "eventStats",
59
60
  "storageStats",
60
61
  "stats",
61
62
  "systemInfo",
@@ -394,8 +395,7 @@ class Bootstrap(ProtectBaseObject):
394
395
 
395
396
  device_id: str = packet.action_frame.data["id"]
396
397
  self.id_lookup.pop(device_id, None)
397
- device = devices.pop(device_id, None)
398
- if device is None:
398
+ if (device := devices.pop(device_id, None)) is None:
399
399
  return None
400
400
  self.mac_lookup.pop(normalize_mac(device.mac), None)
401
401
 
@@ -451,7 +451,15 @@ class Bootstrap(ProtectBaseObject):
451
451
  action: dict[str, Any],
452
452
  data: dict[str, Any],
453
453
  ignore_stats: bool,
454
+ is_ping_back: bool,
454
455
  ) -> WSSubscriptionMessage | None:
456
+ """
457
+ Process a device update packet.
458
+
459
+ If is_ping_back is True, the packet is an empty packet
460
+ that was generated internally as a result of an event
461
+ that will expire and result in a state change.
462
+ """
455
463
  remove_keys = (
456
464
  STATS_AND_IGNORE_DEVICE_KEYS if ignore_stats else IGNORE_DEVICE_KEYS
457
465
  )
@@ -462,7 +470,7 @@ class Bootstrap(ProtectBaseObject):
462
470
  if model_type is ModelType.CAMERA and "lastMotion" in data:
463
471
  del data["lastMotion"]
464
472
  # nothing left to process
465
- if not data:
473
+ if not data and not is_ping_back:
466
474
  self._create_stat(packet, None, True)
467
475
  return None
468
476
 
@@ -476,6 +484,12 @@ class Bootstrap(ProtectBaseObject):
476
484
 
477
485
  obj = devices[action_id]
478
486
  data = obj.unifi_dict_to_dict(data)
487
+
488
+ if not data and not is_ping_back:
489
+ # nothing left to process
490
+ self._create_stat(packet, None, True)
491
+ return None
492
+
479
493
  old_obj = obj.copy()
480
494
  obj = obj.update_from_dict(deepcopy(data))
481
495
 
@@ -486,19 +500,17 @@ class Bootstrap(ProtectBaseObject):
486
500
  elif model_type is ModelType.CAMERA:
487
501
  if TYPE_CHECKING:
488
502
  assert isinstance(obj, Camera)
489
- if "last_ring" in data and obj.last_ring:
490
- is_recent = obj.last_ring + RECENT_EVENT_MAX >= utc_now()
491
- _LOGGER.debug("last_ring for %s (%s)", obj.id, is_recent)
492
- if is_recent:
503
+ if "last_ring" in data and (last_ring := obj.last_ring):
504
+ if is_recent := last_ring + RECENT_EVENT_MAX >= utc_now():
493
505
  obj.set_ring_timeout()
506
+ _LOGGER.debug("last_ring for %s (%s)", obj.id, is_recent)
494
507
  elif model_type is ModelType.SENSOR:
495
508
  if TYPE_CHECKING:
496
509
  assert isinstance(obj, Sensor)
497
- if "alarm_triggered_at" in data and obj.alarm_triggered_at:
498
- is_recent = obj.alarm_triggered_at + RECENT_EVENT_MAX >= utc_now()
499
- _LOGGER.debug("alarm_triggered_at for %s (%s)", obj.id, is_recent)
500
- if is_recent:
510
+ if "alarm_triggered_at" in data and (trigged_at := obj.alarm_triggered_at):
511
+ if is_recent := trigged_at + RECENT_EVENT_MAX >= utc_now():
501
512
  obj.set_alarm_timeout()
513
+ _LOGGER.debug("alarm_triggered_at for %s (%s)", obj.id, is_recent)
502
514
 
503
515
  devices[action_id] = obj
504
516
  self._create_stat(packet, data, False)
@@ -515,6 +527,7 @@ class Bootstrap(ProtectBaseObject):
515
527
  packet: WSPacket,
516
528
  models: set[ModelType] | None = None,
517
529
  ignore_stats: bool = False,
530
+ is_ping_back: bool = False,
518
531
  ) -> WSSubscriptionMessage | None:
519
532
  """Process a WS packet."""
520
533
  action = packet.action_frame.data
@@ -523,17 +536,16 @@ class Bootstrap(ProtectBaseObject):
523
536
  action = deepcopy(action)
524
537
  data = deepcopy(data)
525
538
 
526
- new_update_id: str = action["newUpdateId"]
539
+ new_update_id: str | None = action["newUpdateId"]
527
540
  if new_update_id is not None:
528
541
  self.last_update_id = new_update_id
529
542
 
530
543
  model_key: str = action["modelKey"]
531
- if model_key not in ModelType.values_set():
544
+ if (model_type := ModelType.from_string(model_key)) is ModelType.UNKNOWN:
532
545
  _LOGGER.debug("Unknown model type: %s", model_key)
533
546
  self._create_stat(packet, None, True)
534
547
  return None
535
548
 
536
- model_type = ModelType.from_string(model_key)
537
549
  if models and model_type not in models:
538
550
  self._create_stat(packet, None, True)
539
551
  return None
@@ -542,7 +554,7 @@ class Bootstrap(ProtectBaseObject):
542
554
  if action_action == "remove":
543
555
  return self._process_remove_packet(model_type, packet)
544
556
 
545
- if not data:
557
+ if not data and not is_ping_back:
546
558
  self._create_stat(packet, None, True)
547
559
  return None
548
560
 
@@ -554,11 +566,7 @@ class Bootstrap(ProtectBaseObject):
554
566
  return self._process_nvr_update(packet, data, ignore_stats)
555
567
  if model_type in ModelType.bootstrap_models_types_and_event_set:
556
568
  return self._process_device_update(
557
- model_type,
558
- packet,
559
- action,
560
- data,
561
- ignore_stats,
569
+ model_type, packet, action, data, ignore_stats, is_ping_back
562
570
  )
563
571
  except (ValidationError, ValueError) as err:
564
572
  self._handle_ws_error(action, err)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 1.11.1
3
+ Version: 1.12.1
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
5
  Home-page: https://github.com/uilibs/uiprotect
6
6
  License: MIT
@@ -14,8 +14,8 @@ uiprotect/cli/nvr.py,sha256=TwxEg2XT8jXAbOqv6gc7KFXELKadeItEDYweSL4_-e8,4260
14
14
  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
- uiprotect/data/base.py,sha256=apIXKZHL6dbyXTT4C4lkyo4M-Nf_DwsVXoXBL5jcXVo,37574
18
- uiprotect/data/bootstrap.py,sha256=gDoYacz_WOIOSegGoL-kuw3WQdRcDWUYPd1BL8oBZJ8,21515
17
+ uiprotect/data/base.py,sha256=yzGm5RCeo9AqmgArWC8O7LhBp_Qxz1XSel3xRyhNAbM,37718
18
+ uiprotect/data/bootstrap.py,sha256=wjRFVrv1rOFVDcij6ZYm7dzMe0iz6AqYciQozC-J2TI,21893
19
19
  uiprotect/data/convert.py,sha256=8h6Il_DhMkPRDPj9F_rA2UZIlTuchS3BQD24peKpk2A,2185
20
20
  uiprotect/data/devices.py,sha256=Nq3bOko5PFf5LvEBoD4JV8kmbq50laRdh3VHMWX7t-0,111809
21
21
  uiprotect/data/nvr.py,sha256=XC4NO1c_Mom-hIpzj9ksKFcgKbHd6ToqWjkgzxJ1PJY,47636
@@ -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.11.1.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
- uiprotect-1.11.1.dist-info/METADATA,sha256=fkeec2n5_8G4aBG4D3zqBu4FJM7Nle4ATE14_bm6neo,10985
35
- uiprotect-1.11.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
- uiprotect-1.11.1.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
- uiprotect-1.11.1.dist-info/RECORD,,
33
+ uiprotect-1.12.1.dist-info/LICENSE,sha256=INx18jhdbVXMEiiBANeKEbrbz57ckgzxk5uutmmcxGk,1111
34
+ uiprotect-1.12.1.dist-info/METADATA,sha256=tQiyGoMyj_g6AQLes0kHr4TWIXfENceZ_ksLycnREqI,10985
35
+ uiprotect-1.12.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
36
+ uiprotect-1.12.1.dist-info/entry_points.txt,sha256=J78AUTPrTTxgI3s7SVgrmGqDP7piX2wuuEORzhDdVRA,47
37
+ uiprotect-1.12.1.dist-info/RECORD,,