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.
- {uiprotect-6.3.2 → uiprotect-6.5.0}/PKG-INFO +1 -1
- {uiprotect-6.3.2 → uiprotect-6.5.0}/pyproject.toml +4 -4
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/bootstrap.py +8 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/devices.py +40 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/nvr.py +29 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/types.py +4 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/websocket.py +5 -1
- {uiprotect-6.3.2 → uiprotect-6.5.0}/LICENSE +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/README.md +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/__init__.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/__main__.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/_compat.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/api.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/__init__.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/backup.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/base.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/cameras.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/chimes.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/doorlocks.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/events.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/lights.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/liveviews.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/nvr.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/sensors.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/cli/viewers.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/__init__.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/base.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/convert.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/user.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/data/websocket.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/exceptions.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/py.typed +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/release_cache.json +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/stream.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/test_util/__init__.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/test_util/anonymize.py +0 -0
- {uiprotect-6.3.2 → uiprotect-6.5.0}/src/uiprotect/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "uiprotect"
|
|
3
|
-
version = "6.
|
|
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,<
|
|
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 = "
|
|
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.
|
|
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,
|
|
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
|
|
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
|