uiprotect 6.3.2__tar.gz → 6.5.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.

Files changed (37) hide show
  1. {uiprotect-6.3.2 → uiprotect-6.5.0}/PKG-INFO +1 -1
  2. {uiprotect-6.3.2 → uiprotect-6.5.0}/pyproject.toml +4 -4
  3. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/bootstrap.py +8 -0
  4. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/devices.py +40 -0
  5. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/nvr.py +29 -0
  6. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/types.py +4 -0
  7. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/websocket.py +5 -1
  8. {uiprotect-6.3.2 → uiprotect-6.5.0}/LICENSE +0 -0
  9. {uiprotect-6.3.2 → uiprotect-6.5.0}/README.md +0 -0
  10. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/__init__.py +0 -0
  11. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/__main__.py +0 -0
  12. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/_compat.py +0 -0
  13. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/api.py +0 -0
  14. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/__init__.py +0 -0
  15. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/backup.py +0 -0
  16. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/base.py +0 -0
  17. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/cameras.py +0 -0
  18. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/chimes.py +0 -0
  19. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/doorlocks.py +0 -0
  20. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/events.py +0 -0
  21. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/lights.py +0 -0
  22. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/liveviews.py +0 -0
  23. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/nvr.py +0 -0
  24. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/sensors.py +0 -0
  25. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/viewers.py +0 -0
  26. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/__init__.py +0 -0
  27. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/base.py +0 -0
  28. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/convert.py +0 -0
  29. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/user.py +0 -0
  30. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/websocket.py +0 -0
  31. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/exceptions.py +0 -0
  32. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/py.typed +0 -0
  33. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/release_cache.json +0 -0
  34. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/stream.py +0 -0
  35. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/test_util/__init__.py +0 -0
  36. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/test_util/anonymize.py +0 -0
  37. {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: uiprotect
3
- Version: 6.3.2
3
+ Version: 6.5.0
4
4
  Summary: Python API for Unifi Protect (Unofficial)
5
5
  Home-page: https://github.com/uilibs/uiprotect
6
6
  Author: UI Protect Maintainers
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "uiprotect"
3
- version = "6.3.2"
3
+ version = "6.5.0"
4
4
  description = "Python API for Unifi Protect (Unofficial)"
5
5
  authors = ["UI Protect Maintainers <ui@koston.org>"]
6
6
  readme = "README.md"
@@ -50,11 +50,11 @@ propcache = ">=0.0.0"
50
50
 
51
51
  [tool.poetry.group.dev.dependencies]
52
52
  pytest = ">=7,<9"
53
- pytest-cov = ">=3,<6"
53
+ pytest-cov = ">=3,<7"
54
54
  aiosqlite = ">=0.20.0"
55
55
  asttokens = "^2.4.1"
56
56
  pytest-asyncio = ">=0.23.7,<0.25.0"
57
- pytest-benchmark = "^4.0.0"
57
+ pytest-benchmark = ">=4,<6"
58
58
  pytest-sugar = "^1.0.0"
59
59
  pytest-timeout = "^2.3.1"
60
60
  pytest-xdist = "^3.6.1"
@@ -76,7 +76,7 @@ mkdocs-material-extensions = "^1.3.1"
76
76
  pymdown-extensions = "^10.8.1"
77
77
  mkdocs-git-revision-date-localized-plugin = "^1.2.6"
78
78
  mkdocs-include-markdown-plugin = "^6.1.1"
79
- mkdocstrings = ">=0.25.1,<0.27.0"
79
+ mkdocstrings = ">=0.25.1,<0.28.0"
80
80
  mkdocstrings-python = "^1.10.3"
81
81
 
82
82
  [tool.semantic_release]
@@ -98,6 +98,14 @@ CAMERA_EVENT_ATTR_MAP: dict[EventType, tuple[str, str]] = {
98
98
  "last_smart_audio_detect_event_id",
99
99
  ),
100
100
  EventType.RING: ("last_ring", "last_ring_event_id"),
101
+ EventType.NFC_CARD_SCANNED: (
102
+ "last_nfc_card_scanned",
103
+ "last_nfc_card_scanned_event_id",
104
+ ),
105
+ EventType.FINGERPRINT_IDENTIFIED: (
106
+ "last_fingerprint_identified",
107
+ "last_fingerprint_identified_event_id",
108
+ ),
101
109
  }
102
110
 
103
111
 
@@ -273,6 +273,7 @@ class CameraChannel(ProtectBaseObject):
273
273
 
274
274
  _rtsp_url: str | None = PrivateAttr(None)
275
275
  _rtsps_url: str | None = PrivateAttr(None)
276
+ _rtsps_no_srtp_url: str | None = PrivateAttr(None)
276
277
 
277
278
  @property
278
279
  def rtsp_url(self) -> str | None:
@@ -294,6 +295,16 @@ class CameraChannel(ProtectBaseObject):
294
295
  self._rtsps_url = f"rtsps://{self._api.connection_host}:{self._api.bootstrap.nvr.ports.rtsps}/{self.rtsp_alias}?enableSrtp"
295
296
  return self._rtsps_url
296
297
 
298
+ @property
299
+ def rtsps_no_srtp_url(self) -> str | None:
300
+ if not self.is_rtsp_enabled or self.rtsp_alias is None:
301
+ return None
302
+
303
+ if self._rtsps_no_srtp_url is not None:
304
+ return self._rtsps_no_srtp_url
305
+ self._rtsps_no_srtp_url = f"rtsps://{self._api.connection_host}:{self._api.bootstrap.nvr.ports.rtsps}/{self.rtsp_alias}"
306
+ return self._rtsps_no_srtp_url
307
+
297
308
  @property
298
309
  def is_package(self) -> bool:
299
310
  return self.fps <= 2
@@ -987,6 +998,10 @@ class Camera(ProtectMotionDeviceModel):
987
998
 
988
999
  # not directly from UniFi
989
1000
  last_ring_event_id: str | None = None
1001
+ last_nfc_card_scanned_event_id: str | None = None
1002
+ last_nfc_card_scanned: datetime | None = None
1003
+ last_fingerprint_identified_event_id: str | None = None
1004
+ last_fingerprint_identified: datetime | None = None
990
1005
  last_smart_detect: datetime | None = None
991
1006
  last_smart_audio_detect: datetime | None = None
992
1007
  last_smart_detect_event_id: str | None = None
@@ -1007,6 +1022,10 @@ class Camera(ProtectMotionDeviceModel):
1007
1022
  def _get_excluded_changed_fields(cls) -> set[str]:
1008
1023
  return super()._get_excluded_changed_fields() | {
1009
1024
  "last_ring_event_id",
1025
+ "last_nfc_card_scanned",
1026
+ "last_nfc_card_scanned_event_id",
1027
+ "last_fingerprint_identified",
1028
+ "last_fingerprint_identified_event_id",
1010
1029
  "last_smart_detect",
1011
1030
  "last_smart_audio_detect",
1012
1031
  "last_smart_detect_event_id",
@@ -1075,6 +1094,10 @@ class Camera(ProtectMotionDeviceModel):
1075
1094
  pop_dict_tuple(
1076
1095
  data,
1077
1096
  (
1097
+ "lastFingerprintIdentified",
1098
+ "lastFingerprintIdentifiedEventId",
1099
+ "lastNfcCardScanned",
1100
+ "lastNfcCardScannedEventId",
1078
1101
  "lastRingEventId",
1079
1102
  "lastSmartDetect",
1080
1103
  "lastSmartAudioDetect",
@@ -1137,6 +1160,23 @@ class Camera(ProtectMotionDeviceModel):
1137
1160
  return None
1138
1161
  return self._api.bootstrap.events.get(last_smart_detect_event_id)
1139
1162
 
1163
+ @property
1164
+ def last_nfc_card_scanned_event(self) -> Event | None:
1165
+ if (
1166
+ last_nfc_card_scanned_event_id := self.last_nfc_card_scanned_event_id
1167
+ ) is None:
1168
+ return None
1169
+ return self._api.bootstrap.events.get(last_nfc_card_scanned_event_id)
1170
+
1171
+ @property
1172
+ def last_fingerprint_identified_event(self) -> Event | None:
1173
+ if (
1174
+ last_fingerprint_identified_event_id
1175
+ := self.last_fingerprint_identified_event_id
1176
+ ) is None:
1177
+ return None
1178
+ return self._api.bootstrap.events.get(last_fingerprint_identified_event_id)
1179
+
1140
1180
  @property
1141
1181
  def hdr_mode_display(self) -> Literal["auto", "off", "always"]:
1142
1182
  """Get HDR mode similar to how Protect interface works."""
@@ -136,6 +136,32 @@ class EventThumbnailAttribute(ProtectBaseObject):
136
136
  val: str
137
137
 
138
138
 
139
+ class NfcMetadata(ProtectBaseObject):
140
+ nfc_id: str
141
+ user_id: str
142
+
143
+ @classmethod
144
+ @cache
145
+ def _get_unifi_remaps(cls) -> dict[str, str]:
146
+ return {
147
+ **super()._get_unifi_remaps(),
148
+ "nfcId": "nfc_id",
149
+ "userId": "user_id",
150
+ }
151
+
152
+
153
+ class FingerprintMetadata(ProtectBaseObject):
154
+ ulp_id: str | None = None
155
+
156
+ @classmethod
157
+ @cache
158
+ def _get_unifi_remaps(cls) -> dict[str, str]:
159
+ return {
160
+ **super()._get_unifi_remaps(),
161
+ "ulpId": "ulp_id",
162
+ }
163
+
164
+
139
165
  class EventThumbnailAttributes(ProtectBaseObject):
140
166
  color: EventThumbnailAttribute | None = None
141
167
  vehicle_type: EventThumbnailAttribute | None = None
@@ -195,6 +221,9 @@ class EventMetadata(ProtectBaseObject):
195
221
  license_plate: LicensePlateMetadata | None = None
196
222
  # requires 2.11.13+
197
223
  detected_thumbnails: list[EventDetectedThumbnail] | None = None
224
+ # requires 5.1.34+
225
+ nfc: NfcMetadata | None = None
226
+ fingerprint: FingerprintMetadata | None = None
198
227
 
199
228
  _collapse_keys: ClassVar[SetStr] = {
200
229
  "lightId",
@@ -201,6 +201,8 @@ class EventType(str, ValuesEnumMixin, enum.Enum):
201
201
  POOR_CONNECTION = "poorConnection"
202
202
  STREAM_RECOVERY = "streamRecovery"
203
203
  MOTION = "motion"
204
+ NFC_CARD_SCANNED = "nfcCardScanned"
205
+ FINGERPRINT_IDENTIFIED = "fingerprintIdentified"
204
206
  RECORDING_DELETED = "recordingDeleted"
205
207
  SMART_AUDIO_DETECT = "smartAudioDetect"
206
208
  SMART_DETECT = "smartDetectZone"
@@ -274,6 +276,8 @@ class EventType(str, ValuesEnumMixin, enum.Enum):
274
276
  def device_events() -> list[str]:
275
277
  return [
276
278
  EventType.MOTION.value,
279
+ EventType.NFC_CARD_SCANNED.value,
280
+ EventType.FINGERPRINT_IDENTIFIED.value,
277
281
  EventType.RING.value,
278
282
  EventType.SMART_DETECT.value,
279
283
  EventType.SMART_AUDIO_DETECT.value,
@@ -10,6 +10,7 @@ from enum import Enum
10
10
  from http import HTTPStatus
11
11
  from typing import Any, Optional
12
12
 
13
+ import aiohttp
13
14
  from aiohttp import (
14
15
  ClientError,
15
16
  ClientSession,
@@ -125,7 +126,10 @@ class Websocket:
125
126
  # catch any and all errors for Websocket so we can clean up correctly
126
127
  try:
127
128
  self._ws_connection = await session.ws_connect(
128
- url, ssl=self.verify, headers=self._headers, timeout=self.timeout
129
+ url,
130
+ ssl=self.verify,
131
+ headers=self._headers,
132
+ timeout=aiohttp.ClientWSTimeout(ws_close=self.timeout),
129
133
  )
130
134
  while True:
131
135
  msg = await self._ws_connection.receive(self.receive_timeout)
File without changes
File without changes