uiprotect 0.15.1__tar.gz → 1.0.1__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.15.1 → uiprotect-1.0.1}/PKG-INFO +1 -1
- {uiprotect-0.15.1 → uiprotect-1.0.1}/pyproject.toml +1 -1
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/api.py +8 -5
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/base.py +22 -17
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/bootstrap.py +4 -4
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/devices.py +52 -50
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/nvr.py +29 -29
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/user.py +4 -4
- {uiprotect-0.15.1 → uiprotect-1.0.1}/LICENSE +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/README.md +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/__init__.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/__main__.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/__init__.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/backup.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/base.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/cameras.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/chimes.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/doorlocks.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/events.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/lights.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/liveviews.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/nvr.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/sensors.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/cli/viewers.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/__init__.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/convert.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/types.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/data/websocket.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/exceptions.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/py.typed +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/release_cache.json +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/stream.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/test_util/__init__.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/test_util/anonymize.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/utils.py +0 -0
- {uiprotect-0.15.1 → uiprotect-1.0.1}/src/uiprotect/websocket.py +0 -0
|
@@ -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
|
|
@@ -746,10 +753,6 @@ class ProtectApiClient(BaseApiClient):
|
|
|
746
753
|
if debug:
|
|
747
754
|
set_debug()
|
|
748
755
|
|
|
749
|
-
@property
|
|
750
|
-
def is_ready(self) -> bool:
|
|
751
|
-
return self._bootstrap is not None
|
|
752
|
-
|
|
753
756
|
@cached_property
|
|
754
757
|
def bootstrap(self) -> Bootstrap:
|
|
755
758
|
if self._bootstrap is None:
|
|
@@ -1154,7 +1157,7 @@ class ProtectApiClient(BaseApiClient):
|
|
|
1154
1157
|
objs: list[ProtectModel] = []
|
|
1155
1158
|
|
|
1156
1159
|
for obj_dict in await self.get_devices_raw(model_type):
|
|
1157
|
-
obj = create_from_unifi_dict(obj_dict)
|
|
1160
|
+
obj = create_from_unifi_dict(obj_dict, api=self)
|
|
1158
1161
|
|
|
1159
1162
|
if expected_type is not None and not isinstance(obj, expected_type):
|
|
1160
1163
|
raise NvrError(f"Unexpected model returned: {obj.model}")
|
|
@@ -84,7 +84,7 @@ class ProtectBaseObject(BaseModel):
|
|
|
84
84
|
* Provides `.unifi_dict` to convert object back into UFP JSON
|
|
85
85
|
"""
|
|
86
86
|
|
|
87
|
-
_api: ProtectApiClient
|
|
87
|
+
_api: ProtectApiClient = PrivateAttr(...)
|
|
88
88
|
|
|
89
89
|
_protect_objs: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None
|
|
90
90
|
_protect_lists: ClassVar[dict[str, type[ProtectBaseObject]] | None] = None
|
|
@@ -103,7 +103,8 @@ class ProtectBaseObject(BaseModel):
|
|
|
103
103
|
Use the static method `.from_unifi_dict()` to create objects from UFP JSON data from then the main class constructor.
|
|
104
104
|
"""
|
|
105
105
|
super().__init__(**data)
|
|
106
|
-
|
|
106
|
+
if api is not None:
|
|
107
|
+
self._api = api
|
|
107
108
|
|
|
108
109
|
@classmethod
|
|
109
110
|
def from_unifi_dict(
|
|
@@ -125,7 +126,8 @@ class ProtectBaseObject(BaseModel):
|
|
|
125
126
|
(cameras, users, etc.)
|
|
126
127
|
|
|
127
128
|
"""
|
|
128
|
-
|
|
129
|
+
if api is not None:
|
|
130
|
+
data["api"] = api
|
|
129
131
|
data = cls.unifi_dict_to_dict(data)
|
|
130
132
|
|
|
131
133
|
if is_debug():
|
|
@@ -161,7 +163,8 @@ class ProtectBaseObject(BaseModel):
|
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
obj = super().construct(_fields_set=_fields_set, **values)
|
|
164
|
-
|
|
166
|
+
if api is not None:
|
|
167
|
+
obj._api = api
|
|
165
168
|
|
|
166
169
|
return obj
|
|
167
170
|
|
|
@@ -756,7 +759,9 @@ class ProtectModelWithId(ProtectModel):
|
|
|
756
759
|
if self.model is None:
|
|
757
760
|
raise BadRequest("Unknown model type")
|
|
758
761
|
|
|
759
|
-
if not self.
|
|
762
|
+
if not self._api.bootstrap.auth_user.can(
|
|
763
|
+
self.model, PermissionNode.WRITE, self
|
|
764
|
+
):
|
|
760
765
|
if revert_on_fail:
|
|
761
766
|
self.revert_changes(data_before_changes)
|
|
762
767
|
raise NotAuthorized(f"Do not have write permission for obj: {self.id}")
|
|
@@ -811,11 +816,11 @@ class ProtectModelWithId(ProtectModel):
|
|
|
811
816
|
data_frame.header = header
|
|
812
817
|
data_frame.data = updated
|
|
813
818
|
|
|
814
|
-
message = self.
|
|
819
|
+
message = self._api.bootstrap.process_ws_packet(
|
|
815
820
|
WSPacket(action_frame.packed + data_frame.packed),
|
|
816
821
|
)
|
|
817
822
|
if message is not None:
|
|
818
|
-
self.
|
|
823
|
+
self._api.emit_message(message)
|
|
819
824
|
|
|
820
825
|
|
|
821
826
|
class ProtectDeviceModel(ProtectModelWithId):
|
|
@@ -977,7 +982,7 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
|
977
982
|
|
|
978
983
|
async def _api_update(self, data: dict[str, Any]) -> None:
|
|
979
984
|
if self.model is not None:
|
|
980
|
-
return await self.
|
|
985
|
+
return await self._api.update_device(self.model, self.id, data)
|
|
981
986
|
return None
|
|
982
987
|
|
|
983
988
|
def unifi_dict(
|
|
@@ -1026,12 +1031,12 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
|
1026
1031
|
if self.bridge_id is None:
|
|
1027
1032
|
return None
|
|
1028
1033
|
|
|
1029
|
-
return self.
|
|
1034
|
+
return self._api.bootstrap.bridges[self.bridge_id]
|
|
1030
1035
|
|
|
1031
1036
|
@property
|
|
1032
1037
|
def protect_url(self) -> str:
|
|
1033
1038
|
"""UFP Web app URL for this device"""
|
|
1034
|
-
return f"{self.
|
|
1039
|
+
return f"{self._api.base_url}/protect/devices/{self.id}"
|
|
1035
1040
|
|
|
1036
1041
|
@property
|
|
1037
1042
|
def is_adopted_by_us(self) -> bool:
|
|
@@ -1053,13 +1058,13 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
|
1053
1058
|
async def reboot(self) -> None:
|
|
1054
1059
|
"""Reboots an adopted device"""
|
|
1055
1060
|
if self.model is not None:
|
|
1056
|
-
if not self.
|
|
1061
|
+
if not self._api.bootstrap.auth_user.can(
|
|
1057
1062
|
self.model,
|
|
1058
1063
|
PermissionNode.WRITE,
|
|
1059
1064
|
self,
|
|
1060
1065
|
):
|
|
1061
1066
|
raise NotAuthorized("Do not have permission to reboot device")
|
|
1062
|
-
await self.
|
|
1067
|
+
await self._api.reboot_device(self.model, self.id)
|
|
1063
1068
|
|
|
1064
1069
|
async def unadopt(self) -> None:
|
|
1065
1070
|
"""Unadopt/Unmanage adopted device"""
|
|
@@ -1067,13 +1072,13 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
|
1067
1072
|
raise BadRequest("Device is not adopted")
|
|
1068
1073
|
|
|
1069
1074
|
if self.model is not None:
|
|
1070
|
-
if not self.
|
|
1075
|
+
if not self._api.bootstrap.auth_user.can(
|
|
1071
1076
|
self.model,
|
|
1072
1077
|
PermissionNode.DELETE,
|
|
1073
1078
|
self,
|
|
1074
1079
|
):
|
|
1075
1080
|
raise NotAuthorized("Do not have permission to unadopt devices")
|
|
1076
|
-
await self.
|
|
1081
|
+
await self._api.unadopt_device(self.model, self.id)
|
|
1077
1082
|
|
|
1078
1083
|
async def adopt(self, name: str | None = None) -> None:
|
|
1079
1084
|
"""Adopts a device"""
|
|
@@ -1081,10 +1086,10 @@ class ProtectAdoptableDeviceModel(ProtectDeviceModel):
|
|
|
1081
1086
|
raise BadRequest("Device cannot be adopted")
|
|
1082
1087
|
|
|
1083
1088
|
if self.model is not None:
|
|
1084
|
-
if not self.
|
|
1089
|
+
if not self._api.bootstrap.auth_user.can(self.model, PermissionNode.CREATE):
|
|
1085
1090
|
raise NotAuthorized("Do not have permission to adopt devices")
|
|
1086
1091
|
|
|
1087
|
-
await self.
|
|
1092
|
+
await self._api.adopt_device(self.model, self.id)
|
|
1088
1093
|
if name is not None:
|
|
1089
1094
|
await self.set_name(name)
|
|
1090
1095
|
|
|
@@ -1118,4 +1123,4 @@ class ProtectMotionDeviceModel(ProtectAdoptableDeviceModel):
|
|
|
1118
1123
|
if self.last_motion_event_id is None:
|
|
1119
1124
|
return None
|
|
1120
1125
|
|
|
1121
|
-
return self.
|
|
1126
|
+
return self._api.bootstrap.events.get(self.last_motion_event_id)
|
|
@@ -272,7 +272,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
272
272
|
|
|
273
273
|
@property
|
|
274
274
|
def auth_user(self) -> User:
|
|
275
|
-
user: User = self.
|
|
275
|
+
user: User = self._api.bootstrap.users[self.auth_user_id]
|
|
276
276
|
return user
|
|
277
277
|
|
|
278
278
|
@property
|
|
@@ -388,7 +388,7 @@ class Bootstrap(ProtectBaseObject):
|
|
|
388
388
|
and obj.model.value in ModelType.bootstrap_models_set()
|
|
389
389
|
):
|
|
390
390
|
key = f"{obj.model.value}s"
|
|
391
|
-
if not self.
|
|
391
|
+
if not self._api.ignore_unadopted or (
|
|
392
392
|
obj.is_adopted and not obj.is_adopted_by_other
|
|
393
393
|
):
|
|
394
394
|
getattr(self, key)[obj.id] = obj
|
|
@@ -612,9 +612,9 @@ class Bootstrap(ProtectBaseObject):
|
|
|
612
612
|
"""Refresh a device in the bootstrap."""
|
|
613
613
|
try:
|
|
614
614
|
if model_type == ModelType.NVR:
|
|
615
|
-
device: ProtectModelWithId = await self.
|
|
615
|
+
device: ProtectModelWithId = await self._api.get_nvr()
|
|
616
616
|
else:
|
|
617
|
-
device = await self.
|
|
617
|
+
device = await self._api.get_device(model_type, device_id)
|
|
618
618
|
except (
|
|
619
619
|
ValidationError,
|
|
620
620
|
TimeoutError,
|
|
@@ -158,7 +158,7 @@ class Light(ProtectMotionDeviceModel):
|
|
|
158
158
|
if self.camera_id is None:
|
|
159
159
|
return None
|
|
160
160
|
|
|
161
|
-
return self.
|
|
161
|
+
return self._api.bootstrap.cameras[self.camera_id]
|
|
162
162
|
|
|
163
163
|
async def set_paired_camera(self, camera: Camera | None) -> None:
|
|
164
164
|
"""Sets the camera paired with the light"""
|
|
@@ -283,7 +283,7 @@ class CameraChannel(ProtectBaseObject):
|
|
|
283
283
|
|
|
284
284
|
if self._rtsp_url is not None:
|
|
285
285
|
return self._rtsp_url
|
|
286
|
-
self._rtsp_url = f"rtsp://{self.
|
|
286
|
+
self._rtsp_url = f"rtsp://{self._api.connection_host}:{self._api.bootstrap.nvr.ports.rtsp}/{self.rtsp_alias}"
|
|
287
287
|
return self._rtsp_url
|
|
288
288
|
|
|
289
289
|
@property
|
|
@@ -293,7 +293,7 @@ class CameraChannel(ProtectBaseObject):
|
|
|
293
293
|
|
|
294
294
|
if self._rtsps_url is not None:
|
|
295
295
|
return self._rtsps_url
|
|
296
|
-
self._rtsps_url = f"rtsps://{self.
|
|
296
|
+
self._rtsps_url = f"rtsps://{self._api.connection_host}:{self._api.bootstrap.nvr.ports.rtsps}/{self.rtsp_alias}?enableSrtp"
|
|
297
297
|
return self._rtsps_url
|
|
298
298
|
|
|
299
299
|
@property
|
|
@@ -1124,7 +1124,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1124
1124
|
if self.last_ring_event_id is None:
|
|
1125
1125
|
return None
|
|
1126
1126
|
|
|
1127
|
-
return self.
|
|
1127
|
+
return self._api.bootstrap.events.get(self.last_ring_event_id)
|
|
1128
1128
|
|
|
1129
1129
|
@property
|
|
1130
1130
|
def last_smart_detect_event(self) -> Event | None:
|
|
@@ -1132,7 +1132,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1132
1132
|
if self.last_smart_detect_event_id is None:
|
|
1133
1133
|
return None
|
|
1134
1134
|
|
|
1135
|
-
return self.
|
|
1135
|
+
return self._api.bootstrap.events.get(self.last_smart_detect_event_id)
|
|
1136
1136
|
|
|
1137
1137
|
@property
|
|
1138
1138
|
def hdr_mode_display(self) -> Literal["auto", "off", "always"]:
|
|
@@ -1160,7 +1160,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1160
1160
|
if event_id is None:
|
|
1161
1161
|
return None
|
|
1162
1162
|
|
|
1163
|
-
return self.
|
|
1163
|
+
return self._api.bootstrap.events.get(event_id)
|
|
1164
1164
|
|
|
1165
1165
|
@property
|
|
1166
1166
|
def last_smart_audio_detect_event(self) -> Event | None:
|
|
@@ -1168,7 +1168,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1168
1168
|
if self.last_smart_audio_detect_event_id is None:
|
|
1169
1169
|
return None
|
|
1170
1170
|
|
|
1171
|
-
return self.
|
|
1171
|
+
return self._api.bootstrap.events.get(self.last_smart_audio_detect_event_id)
|
|
1172
1172
|
|
|
1173
1173
|
def get_last_smart_audio_detect_event(
|
|
1174
1174
|
self,
|
|
@@ -1179,11 +1179,11 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1179
1179
|
if event_id is None:
|
|
1180
1180
|
return None
|
|
1181
1181
|
|
|
1182
|
-
return self.
|
|
1182
|
+
return self._api.bootstrap.events.get(event_id)
|
|
1183
1183
|
|
|
1184
1184
|
@property
|
|
1185
1185
|
def timelapse_url(self) -> str:
|
|
1186
|
-
return f"{self.
|
|
1186
|
+
return f"{self._api.base_url}/protect/timelapse/{self.id}"
|
|
1187
1187
|
|
|
1188
1188
|
@property
|
|
1189
1189
|
def is_privacy_on(self) -> bool:
|
|
@@ -1199,7 +1199,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1199
1199
|
motion/smart detection events.
|
|
1200
1200
|
"""
|
|
1201
1201
|
if self.use_global:
|
|
1202
|
-
return self.
|
|
1202
|
+
return self._api.bootstrap.nvr.is_global_recording_enabled
|
|
1203
1203
|
|
|
1204
1204
|
return self.recording_settings.mode is not RecordingMode.NEVER
|
|
1205
1205
|
|
|
@@ -1208,7 +1208,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1208
1208
|
"""Is smart detections allowed for this camera?"""
|
|
1209
1209
|
return (
|
|
1210
1210
|
self.is_recording_enabled
|
|
1211
|
-
and self.
|
|
1211
|
+
and self._api.bootstrap.nvr.is_smart_detections_enabled
|
|
1212
1212
|
)
|
|
1213
1213
|
|
|
1214
1214
|
@property
|
|
@@ -1216,7 +1216,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1216
1216
|
"""Is license plate detections allowed for this camera?"""
|
|
1217
1217
|
return (
|
|
1218
1218
|
self.is_recording_enabled
|
|
1219
|
-
and self.
|
|
1219
|
+
and self._api.bootstrap.nvr.is_license_plate_detections_enabled
|
|
1220
1220
|
)
|
|
1221
1221
|
|
|
1222
1222
|
@property
|
|
@@ -1224,22 +1224,22 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1224
1224
|
"""Is face detections allowed for this camera?"""
|
|
1225
1225
|
return (
|
|
1226
1226
|
self.is_recording_enabled
|
|
1227
|
-
and self.
|
|
1227
|
+
and self._api.bootstrap.nvr.is_face_detections_enabled
|
|
1228
1228
|
)
|
|
1229
1229
|
|
|
1230
1230
|
@property
|
|
1231
1231
|
def active_recording_settings(self) -> RecordingSettings:
|
|
1232
1232
|
"""Get active recording settings."""
|
|
1233
|
-
if self.use_global and self.
|
|
1234
|
-
return self.
|
|
1233
|
+
if self.use_global and self._api.bootstrap.nvr.global_camera_settings:
|
|
1234
|
+
return self._api.bootstrap.nvr.global_camera_settings.recording_settings
|
|
1235
1235
|
|
|
1236
1236
|
return self.recording_settings
|
|
1237
1237
|
|
|
1238
1238
|
@property
|
|
1239
1239
|
def active_smart_detect_settings(self) -> SmartDetectSettings:
|
|
1240
1240
|
"""Get active smart detection settings."""
|
|
1241
|
-
if self.use_global and self.
|
|
1242
|
-
return self.
|
|
1241
|
+
if self.use_global and self._api.bootstrap.nvr.global_camera_settings:
|
|
1242
|
+
return self._api.bootstrap.nvr.global_camera_settings.smart_detect_settings
|
|
1243
1243
|
|
|
1244
1244
|
return self.smart_detect_settings
|
|
1245
1245
|
|
|
@@ -1991,7 +1991,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
1991
1991
|
|
|
1992
1992
|
Datetime of screenshot is approximate. It may be +/- a few seconds.
|
|
1993
1993
|
"""
|
|
1994
|
-
if not self.
|
|
1994
|
+
if not self._api.bootstrap.auth_user.can(
|
|
1995
1995
|
ModelType.CAMERA,
|
|
1996
1996
|
PermissionNode.READ_MEDIA,
|
|
1997
1997
|
self,
|
|
@@ -2003,7 +2003,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2003
2003
|
if height is None and width is None and self.high_camera_channel is not None:
|
|
2004
2004
|
height = self.high_camera_channel.height
|
|
2005
2005
|
|
|
2006
|
-
return await self.
|
|
2006
|
+
return await self._api.get_camera_snapshot(self.id, width, height, dt=dt)
|
|
2007
2007
|
|
|
2008
2008
|
async def get_package_snapshot(
|
|
2009
2009
|
self,
|
|
@@ -2019,7 +2019,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2019
2019
|
if not self.feature_flags.has_package_camera:
|
|
2020
2020
|
raise BadRequest("Device does not have package camera")
|
|
2021
2021
|
|
|
2022
|
-
if not self.
|
|
2022
|
+
if not self._api.bootstrap.auth_user.can(
|
|
2023
2023
|
ModelType.CAMERA,
|
|
2024
2024
|
PermissionNode.READ_MEDIA,
|
|
2025
2025
|
self,
|
|
@@ -2031,7 +2031,9 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2031
2031
|
if height is None and width is None and self.package_camera_channel is not None:
|
|
2032
2032
|
height = self.package_camera_channel.height
|
|
2033
2033
|
|
|
2034
|
-
return await self.
|
|
2034
|
+
return await self._api.get_package_camera_snapshot(
|
|
2035
|
+
self.id, width, height, dt=dt
|
|
2036
|
+
)
|
|
2035
2037
|
|
|
2036
2038
|
async def get_video(
|
|
2037
2039
|
self,
|
|
@@ -2057,7 +2059,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2057
2059
|
value. Protect app gives the options for 60x (fps=4), 120x (fps=8), 300x
|
|
2058
2060
|
(fps=20), and 600x (fps=40).
|
|
2059
2061
|
"""
|
|
2060
|
-
if not self.
|
|
2062
|
+
if not self._api.bootstrap.auth_user.can(
|
|
2061
2063
|
ModelType.CAMERA,
|
|
2062
2064
|
PermissionNode.READ_MEDIA,
|
|
2063
2065
|
self,
|
|
@@ -2066,7 +2068,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2066
2068
|
f"Do not have permission to read media for camera: {self.id}",
|
|
2067
2069
|
)
|
|
2068
2070
|
|
|
2069
|
-
return await self.
|
|
2071
|
+
return await self._api.get_camera_video(
|
|
2070
2072
|
self.id,
|
|
2071
2073
|
start,
|
|
2072
2074
|
end,
|
|
@@ -2410,7 +2412,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2410
2412
|
if reset_at == DEFAULT:
|
|
2411
2413
|
reset_at = (
|
|
2412
2414
|
utc_now()
|
|
2413
|
-
+ self.
|
|
2415
|
+
+ self._api.bootstrap.nvr.doorbell_settings.default_message_reset_timeout
|
|
2414
2416
|
)
|
|
2415
2417
|
|
|
2416
2418
|
def callback() -> None:
|
|
@@ -2584,7 +2586,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2584
2586
|
pan = self.feature_flags.pan.to_native_value(pan, is_relative=True)
|
|
2585
2587
|
tilt = self.feature_flags.tilt.to_native_value(tilt, is_relative=True)
|
|
2586
2588
|
|
|
2587
|
-
await self.
|
|
2589
|
+
await self._api.relative_move_ptz_camera(
|
|
2588
2590
|
self.id,
|
|
2589
2591
|
pan=pan,
|
|
2590
2592
|
tilt=tilt,
|
|
@@ -2606,7 +2608,7 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2606
2608
|
|
|
2607
2609
|
z value is zoom, but since it is capped at 1000, probably better to use `ptz_zoom`.
|
|
2608
2610
|
"""
|
|
2609
|
-
await self.
|
|
2611
|
+
await self._api.center_ptz_camera(self.id, x=x, y=y, z=z)
|
|
2610
2612
|
|
|
2611
2613
|
async def ptz_zoom(
|
|
2612
2614
|
self,
|
|
@@ -2628,14 +2630,14 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2628
2630
|
if not use_native:
|
|
2629
2631
|
zoom = self.feature_flags.zoom.to_native_value(zoom)
|
|
2630
2632
|
|
|
2631
|
-
await self.
|
|
2633
|
+
await self._api.zoom_ptz_camera(self.id, zoom=zoom, speed=speed)
|
|
2632
2634
|
|
|
2633
2635
|
async def get_ptz_position(self) -> PTZPosition:
|
|
2634
2636
|
"""Get current PTZ Position."""
|
|
2635
2637
|
if not self.feature_flags.is_ptz:
|
|
2636
2638
|
raise BadRequest("Camera does not support PTZ features.")
|
|
2637
2639
|
|
|
2638
|
-
return await self.
|
|
2640
|
+
return await self._api.get_position_ptz_camera(self.id)
|
|
2639
2641
|
|
|
2640
2642
|
async def goto_ptz_slot(self, *, slot: int) -> None:
|
|
2641
2643
|
"""
|
|
@@ -2646,42 +2648,42 @@ class Camera(ProtectMotionDeviceModel):
|
|
|
2646
2648
|
if not self.feature_flags.is_ptz:
|
|
2647
2649
|
raise BadRequest("Camera does not support PTZ features.")
|
|
2648
2650
|
|
|
2649
|
-
await self.
|
|
2651
|
+
await self._api.goto_ptz_camera(self.id, slot=slot)
|
|
2650
2652
|
|
|
2651
2653
|
async def create_ptz_preset(self, *, name: str) -> PTZPreset:
|
|
2652
2654
|
"""Create PTZ Preset for camera based on current camera settings."""
|
|
2653
2655
|
if not self.feature_flags.is_ptz:
|
|
2654
2656
|
raise BadRequest("Camera does not support PTZ features.")
|
|
2655
2657
|
|
|
2656
|
-
return await self.
|
|
2658
|
+
return await self._api.create_preset_ptz_camera(self.id, name=name)
|
|
2657
2659
|
|
|
2658
2660
|
async def get_ptz_presets(self) -> list[PTZPreset]:
|
|
2659
2661
|
"""Get PTZ Presets for camera."""
|
|
2660
2662
|
if not self.feature_flags.is_ptz:
|
|
2661
2663
|
raise BadRequest("Camera does not support PTZ features.")
|
|
2662
2664
|
|
|
2663
|
-
return await self.
|
|
2665
|
+
return await self._api.get_presets_ptz_camera(self.id)
|
|
2664
2666
|
|
|
2665
2667
|
async def delete_ptz_preset(self, *, slot: int) -> None:
|
|
2666
2668
|
"""Delete PTZ preset for camera."""
|
|
2667
2669
|
if not self.feature_flags.is_ptz:
|
|
2668
2670
|
raise BadRequest("Camera does not support PTZ features.")
|
|
2669
2671
|
|
|
2670
|
-
await self.
|
|
2672
|
+
await self._api.delete_preset_ptz_camera(self.id, slot=slot)
|
|
2671
2673
|
|
|
2672
2674
|
async def get_ptz_home(self) -> PTZPreset:
|
|
2673
2675
|
"""Get PTZ home preset (-1)."""
|
|
2674
2676
|
if not self.feature_flags.is_ptz:
|
|
2675
2677
|
raise BadRequest("Camera does not support PTZ features.")
|
|
2676
2678
|
|
|
2677
|
-
return await self.
|
|
2679
|
+
return await self._api.get_home_ptz_camera(self.id)
|
|
2678
2680
|
|
|
2679
2681
|
async def set_ptz_home(self) -> PTZPreset:
|
|
2680
2682
|
"""Get PTZ home preset (-1) to current position."""
|
|
2681
2683
|
if not self.feature_flags.is_ptz:
|
|
2682
2684
|
raise BadRequest("Camera does not support PTZ features.")
|
|
2683
2685
|
|
|
2684
|
-
return await self.
|
|
2686
|
+
return await self._api.set_home_ptz_camera(self.id)
|
|
2685
2687
|
|
|
2686
2688
|
# endregion
|
|
2687
2689
|
|
|
@@ -2704,7 +2706,7 @@ class Viewer(ProtectAdoptableDeviceModel):
|
|
|
2704
2706
|
@property
|
|
2705
2707
|
def liveview(self) -> Liveview | None:
|
|
2706
2708
|
# user may not have permission to see the liveview
|
|
2707
|
-
return self.
|
|
2709
|
+
return self._api.bootstrap.liveviews.get(self.liveview_id)
|
|
2708
2710
|
|
|
2709
2711
|
async def set_liveview(self, liveview: Liveview) -> None:
|
|
2710
2712
|
"""
|
|
@@ -2838,7 +2840,7 @@ class Sensor(ProtectAdoptableDeviceModel):
|
|
|
2838
2840
|
if self.camera_id is None:
|
|
2839
2841
|
return None
|
|
2840
2842
|
|
|
2841
|
-
return self.
|
|
2843
|
+
return self._api.bootstrap.cameras[self.camera_id]
|
|
2842
2844
|
|
|
2843
2845
|
@property
|
|
2844
2846
|
def is_tampering_detected(self) -> bool:
|
|
@@ -2889,28 +2891,28 @@ class Sensor(ProtectAdoptableDeviceModel):
|
|
|
2889
2891
|
if self.last_motion_event_id is None:
|
|
2890
2892
|
return None
|
|
2891
2893
|
|
|
2892
|
-
return self.
|
|
2894
|
+
return self._api.bootstrap.events.get(self.last_motion_event_id)
|
|
2893
2895
|
|
|
2894
2896
|
@property
|
|
2895
2897
|
def last_contact_event(self) -> Event | None:
|
|
2896
2898
|
if self.last_contact_event_id is None:
|
|
2897
2899
|
return None
|
|
2898
2900
|
|
|
2899
|
-
return self.
|
|
2901
|
+
return self._api.bootstrap.events.get(self.last_contact_event_id)
|
|
2900
2902
|
|
|
2901
2903
|
@property
|
|
2902
2904
|
def last_value_event(self) -> Event | None:
|
|
2903
2905
|
if self.last_value_event_id is None:
|
|
2904
2906
|
return None
|
|
2905
2907
|
|
|
2906
|
-
return self.
|
|
2908
|
+
return self._api.bootstrap.events.get(self.last_value_event_id)
|
|
2907
2909
|
|
|
2908
2910
|
@property
|
|
2909
2911
|
def last_alarm_event(self) -> Event | None:
|
|
2910
2912
|
if self.last_alarm_event_id is None:
|
|
2911
2913
|
return None
|
|
2912
2914
|
|
|
2913
|
-
return self.
|
|
2915
|
+
return self._api.bootstrap.events.get(self.last_alarm_event_id)
|
|
2914
2916
|
|
|
2915
2917
|
@property
|
|
2916
2918
|
def is_leak_detected(self) -> bool:
|
|
@@ -3065,7 +3067,7 @@ class Sensor(ProtectAdoptableDeviceModel):
|
|
|
3065
3067
|
|
|
3066
3068
|
async def clear_tamper(self) -> None:
|
|
3067
3069
|
"""Clears tamper status for sensor"""
|
|
3068
|
-
if not self.
|
|
3070
|
+
if not self._api.bootstrap.auth_user.can(
|
|
3069
3071
|
ModelType.SENSOR,
|
|
3070
3072
|
PermissionNode.WRITE,
|
|
3071
3073
|
self,
|
|
@@ -3073,7 +3075,7 @@ class Sensor(ProtectAdoptableDeviceModel):
|
|
|
3073
3075
|
raise NotAuthorized(
|
|
3074
3076
|
f"Do not have permission to clear tamper for sensor: {self.id}",
|
|
3075
3077
|
)
|
|
3076
|
-
await self.
|
|
3078
|
+
await self._api.clear_tamper_sensor(self.id)
|
|
3077
3079
|
|
|
3078
3080
|
|
|
3079
3081
|
class Doorlock(ProtectAdoptableDeviceModel):
|
|
@@ -3121,7 +3123,7 @@ class Doorlock(ProtectAdoptableDeviceModel):
|
|
|
3121
3123
|
if self.camera_id is None:
|
|
3122
3124
|
return None
|
|
3123
3125
|
|
|
3124
|
-
return self.
|
|
3126
|
+
return self._api.bootstrap.cameras[self.camera_id]
|
|
3125
3127
|
|
|
3126
3128
|
async def set_paired_camera(self, camera: Camera | None) -> None:
|
|
3127
3129
|
"""Sets the camera paired with the sensor"""
|
|
@@ -3157,14 +3159,14 @@ class Doorlock(ProtectAdoptableDeviceModel):
|
|
|
3157
3159
|
if self.lock_status != LockStatusType.OPEN:
|
|
3158
3160
|
raise BadRequest("Lock is not open")
|
|
3159
3161
|
|
|
3160
|
-
await self.
|
|
3162
|
+
await self._api.close_lock(self.id)
|
|
3161
3163
|
|
|
3162
3164
|
async def open_lock(self) -> None:
|
|
3163
3165
|
"""Open doorlock (unlock)"""
|
|
3164
3166
|
if self.lock_status != LockStatusType.CLOSED:
|
|
3165
3167
|
raise BadRequest("Lock is not closed")
|
|
3166
3168
|
|
|
3167
|
-
await self.
|
|
3169
|
+
await self._api.open_lock(self.id)
|
|
3168
3170
|
|
|
3169
3171
|
async def calibrate(self) -> None:
|
|
3170
3172
|
"""
|
|
@@ -3172,7 +3174,7 @@ class Doorlock(ProtectAdoptableDeviceModel):
|
|
|
3172
3174
|
|
|
3173
3175
|
Door must be open and lock unlocked.
|
|
3174
3176
|
"""
|
|
3175
|
-
await self.
|
|
3177
|
+
await self._api.calibrate_lock(self.id)
|
|
3176
3178
|
|
|
3177
3179
|
|
|
3178
3180
|
class ChimeFeatureFlags(ProtectBaseObject):
|
|
@@ -3203,7 +3205,7 @@ class RingSetting(ProtectBaseObject):
|
|
|
3203
3205
|
if self.camera_id is None:
|
|
3204
3206
|
return None # type: ignore[unreachable]
|
|
3205
3207
|
|
|
3206
|
-
return self.
|
|
3208
|
+
return self._api.bootstrap.cameras[self.camera_id]
|
|
3207
3209
|
|
|
3208
3210
|
|
|
3209
3211
|
class ChimeTrack(ProtectBaseObject):
|
|
@@ -3260,7 +3262,7 @@ class Chime(ProtectAdoptableDeviceModel):
|
|
|
3260
3262
|
"""Paired Cameras for chime"""
|
|
3261
3263
|
if len(self.camera_ids) == 0:
|
|
3262
3264
|
return []
|
|
3263
|
-
return [self.
|
|
3265
|
+
return [self._api.bootstrap.cameras[c] for c in self.camera_ids]
|
|
3264
3266
|
|
|
3265
3267
|
async def set_volume(self, level: int) -> None:
|
|
3266
3268
|
"""Set the volume on chime."""
|
|
@@ -3321,11 +3323,11 @@ class Chime(ProtectAdoptableDeviceModel):
|
|
|
3321
3323
|
repeat_times: int | None = None,
|
|
3322
3324
|
) -> None:
|
|
3323
3325
|
"""Plays chime tone"""
|
|
3324
|
-
await self.
|
|
3326
|
+
await self._api.play_speaker(self.id, volume=volume, repeat_times=repeat_times)
|
|
3325
3327
|
|
|
3326
3328
|
async def play_buzzer(self) -> None:
|
|
3327
3329
|
"""Plays chime buzzer"""
|
|
3328
|
-
await self.
|
|
3330
|
+
await self._api.play_buzzer(self.id)
|
|
3329
3331
|
|
|
3330
3332
|
async def set_repeat_times(self, value: int) -> None:
|
|
3331
3333
|
"""Set repeat times on chime."""
|
|
@@ -124,11 +124,11 @@ class SmartDetectTrack(ProtectBaseObject):
|
|
|
124
124
|
|
|
125
125
|
@property
|
|
126
126
|
def camera(self) -> Camera:
|
|
127
|
-
return self.
|
|
127
|
+
return self._api.bootstrap.cameras[self.camera_id]
|
|
128
128
|
|
|
129
129
|
@property
|
|
130
130
|
def event(self) -> Event | None:
|
|
131
|
-
return self.
|
|
131
|
+
return self._api.bootstrap.events.get(self.event_id)
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
class LicensePlateMetadata(ProtectBaseObject):
|
|
@@ -331,28 +331,28 @@ class Event(ProtectModelWithId):
|
|
|
331
331
|
if self.camera_id is None:
|
|
332
332
|
return None
|
|
333
333
|
|
|
334
|
-
return self.
|
|
334
|
+
return self._api.bootstrap.cameras.get(self.camera_id)
|
|
335
335
|
|
|
336
336
|
@property
|
|
337
337
|
def light(self) -> Light | None:
|
|
338
338
|
if self.metadata is None or self.metadata.light_id is None:
|
|
339
339
|
return None
|
|
340
340
|
|
|
341
|
-
return self.
|
|
341
|
+
return self._api.bootstrap.lights.get(self.metadata.light_id)
|
|
342
342
|
|
|
343
343
|
@property
|
|
344
344
|
def sensor(self) -> Sensor | None:
|
|
345
345
|
if self.metadata is None or self.metadata.sensor_id is None:
|
|
346
346
|
return None
|
|
347
347
|
|
|
348
|
-
return self.
|
|
348
|
+
return self._api.bootstrap.sensors.get(self.metadata.sensor_id)
|
|
349
349
|
|
|
350
350
|
@property
|
|
351
351
|
def user(self) -> User | None:
|
|
352
352
|
if self.user_id is None:
|
|
353
353
|
return None
|
|
354
354
|
|
|
355
|
-
return self.
|
|
355
|
+
return self._api.bootstrap.users.get(self.user_id)
|
|
356
356
|
|
|
357
357
|
@property
|
|
358
358
|
def smart_detect_events(self) -> list[Event]:
|
|
@@ -360,9 +360,9 @@ class Event(ProtectModelWithId):
|
|
|
360
360
|
return self._smart_detect_events
|
|
361
361
|
|
|
362
362
|
self._smart_detect_events = [
|
|
363
|
-
self.
|
|
363
|
+
self._api.bootstrap.events[g]
|
|
364
364
|
for g in self.smart_detect_event_ids
|
|
365
|
-
if g in self.
|
|
365
|
+
if g in self._api.bootstrap.events
|
|
366
366
|
]
|
|
367
367
|
return self._smart_detect_events
|
|
368
368
|
|
|
@@ -374,7 +374,7 @@ class Event(ProtectModelWithId):
|
|
|
374
374
|
"""Gets thumbnail for event"""
|
|
375
375
|
if self.thumbnail_id is None:
|
|
376
376
|
return None
|
|
377
|
-
if not self.
|
|
377
|
+
if not self._api.bootstrap.auth_user.can(
|
|
378
378
|
ModelType.CAMERA,
|
|
379
379
|
PermissionNode.READ_MEDIA,
|
|
380
380
|
self.camera,
|
|
@@ -382,7 +382,7 @@ class Event(ProtectModelWithId):
|
|
|
382
382
|
raise NotAuthorized(
|
|
383
383
|
f"Do not have permission to read media for camera: {self.id}",
|
|
384
384
|
)
|
|
385
|
-
return await self.
|
|
385
|
+
return await self._api.get_event_thumbnail(self.thumbnail_id, width, height)
|
|
386
386
|
|
|
387
387
|
async def get_animated_thumbnail(
|
|
388
388
|
self,
|
|
@@ -394,7 +394,7 @@ class Event(ProtectModelWithId):
|
|
|
394
394
|
"""Gets animated thumbnail for event"""
|
|
395
395
|
if self.thumbnail_id is None:
|
|
396
396
|
return None
|
|
397
|
-
if not self.
|
|
397
|
+
if not self._api.bootstrap.auth_user.can(
|
|
398
398
|
ModelType.CAMERA,
|
|
399
399
|
PermissionNode.READ_MEDIA,
|
|
400
400
|
self.camera,
|
|
@@ -402,7 +402,7 @@ class Event(ProtectModelWithId):
|
|
|
402
402
|
raise NotAuthorized(
|
|
403
403
|
f"Do not have permission to read media for camera: {self.id}",
|
|
404
404
|
)
|
|
405
|
-
return await self.
|
|
405
|
+
return await self._api.get_event_animated_thumbnail(
|
|
406
406
|
self.thumbnail_id,
|
|
407
407
|
width,
|
|
408
408
|
height,
|
|
@@ -413,7 +413,7 @@ class Event(ProtectModelWithId):
|
|
|
413
413
|
"""Gets heatmap for event"""
|
|
414
414
|
if self.heatmap_id is None:
|
|
415
415
|
return None
|
|
416
|
-
if not self.
|
|
416
|
+
if not self._api.bootstrap.auth_user.can(
|
|
417
417
|
ModelType.CAMERA,
|
|
418
418
|
PermissionNode.READ_MEDIA,
|
|
419
419
|
self.camera,
|
|
@@ -421,7 +421,7 @@ class Event(ProtectModelWithId):
|
|
|
421
421
|
raise NotAuthorized(
|
|
422
422
|
f"Do not have permission to read media for camera: {self.id}",
|
|
423
423
|
)
|
|
424
|
-
return await self.
|
|
424
|
+
return await self._api.get_event_heatmap(self.heatmap_id)
|
|
425
425
|
|
|
426
426
|
async def get_video(
|
|
427
427
|
self,
|
|
@@ -446,7 +446,7 @@ class Event(ProtectModelWithId):
|
|
|
446
446
|
if self.end is None:
|
|
447
447
|
raise BadRequest("Event is ongoing")
|
|
448
448
|
|
|
449
|
-
if not self.
|
|
449
|
+
if not self._api.bootstrap.auth_user.can(
|
|
450
450
|
ModelType.CAMERA,
|
|
451
451
|
PermissionNode.READ_MEDIA,
|
|
452
452
|
self.camera,
|
|
@@ -454,7 +454,7 @@ class Event(ProtectModelWithId):
|
|
|
454
454
|
raise NotAuthorized(
|
|
455
455
|
f"Do not have permission to read media for camera: {self.id}",
|
|
456
456
|
)
|
|
457
|
-
return await self.
|
|
457
|
+
return await self._api.get_camera_video(
|
|
458
458
|
self.camera.id,
|
|
459
459
|
self.start,
|
|
460
460
|
self.end,
|
|
@@ -475,7 +475,7 @@ class Event(ProtectModelWithId):
|
|
|
475
475
|
raise BadRequest("Not a smart detect event")
|
|
476
476
|
|
|
477
477
|
if self._smart_detect_track is None:
|
|
478
|
-
self._smart_detect_track = await self.
|
|
478
|
+
self._smart_detect_track = await self._api.get_event_smart_detect_track(
|
|
479
479
|
self.id,
|
|
480
480
|
)
|
|
481
481
|
|
|
@@ -1043,7 +1043,7 @@ class NVR(ProtectDeviceModel):
|
|
|
1043
1043
|
return super().unifi_dict_to_dict(data)
|
|
1044
1044
|
|
|
1045
1045
|
async def _api_update(self, data: dict[str, Any]) -> None:
|
|
1046
|
-
return await self.
|
|
1046
|
+
return await self._api.update_nvr(data)
|
|
1047
1047
|
|
|
1048
1048
|
@property
|
|
1049
1049
|
def is_analytics_enabled(self) -> bool:
|
|
@@ -1051,7 +1051,7 @@ class NVR(ProtectDeviceModel):
|
|
|
1051
1051
|
|
|
1052
1052
|
@property
|
|
1053
1053
|
def protect_url(self) -> str:
|
|
1054
|
-
return f"{self.
|
|
1054
|
+
return f"{self._api.base_url}/protect/devices/{self._api.bootstrap.nvr.id}"
|
|
1055
1055
|
|
|
1056
1056
|
@property
|
|
1057
1057
|
def display_name(self) -> str:
|
|
@@ -1062,7 +1062,7 @@ class NVR(ProtectDeviceModel):
|
|
|
1062
1062
|
"""Vault Cameras for NVR"""
|
|
1063
1063
|
if len(self.vault_camera_ids) == 0:
|
|
1064
1064
|
return []
|
|
1065
|
-
return [self.
|
|
1065
|
+
return [self._api.bootstrap.cameras[c] for c in self.vault_camera_ids]
|
|
1066
1066
|
|
|
1067
1067
|
@property
|
|
1068
1068
|
def is_global_recording_enabled(self) -> bool:
|
|
@@ -1194,7 +1194,7 @@ class NVR(ProtectDeviceModel):
|
|
|
1194
1194
|
|
|
1195
1195
|
async def reboot(self) -> None:
|
|
1196
1196
|
"""Reboots the NVR"""
|
|
1197
|
-
await self.
|
|
1197
|
+
await self._api.reboot_nvr()
|
|
1198
1198
|
|
|
1199
1199
|
async def _read_cache_file(self, file_path: Path) -> set[Version] | None:
|
|
1200
1200
|
versions: set[Version] | None = None
|
|
@@ -1218,16 +1218,16 @@ class NVR(ProtectDeviceModel):
|
|
|
1218
1218
|
return True
|
|
1219
1219
|
|
|
1220
1220
|
# 2.6.14 is an EA version that looks like a release version
|
|
1221
|
-
cache_file_path = self.
|
|
1221
|
+
cache_file_path = self._api.cache_dir / "release_cache.json"
|
|
1222
1222
|
versions = await self._read_cache_file(
|
|
1223
1223
|
cache_file_path,
|
|
1224
1224
|
) or await self._read_cache_file(RELEASE_CACHE)
|
|
1225
1225
|
if versions is None or self.version not in versions:
|
|
1226
|
-
versions = await self.
|
|
1226
|
+
versions = await self._api.get_release_versions()
|
|
1227
1227
|
try:
|
|
1228
1228
|
_LOGGER.debug("Fetching releases from APT repos...")
|
|
1229
|
-
tmp = self.
|
|
1230
|
-
await aos.makedirs(self.
|
|
1229
|
+
tmp = self._api.cache_dir / "release_cache.tmp.json"
|
|
1230
|
+
await aos.makedirs(self._api.cache_dir, exist_ok=True)
|
|
1231
1231
|
async with aiofiles.open(tmp, "wb") as cache_file:
|
|
1232
1232
|
await cache_file.write(orjson.dumps([str(v) for v in versions]))
|
|
1233
1233
|
await aos.rename(tmp, cache_file_path)
|
|
@@ -1487,9 +1487,9 @@ class LiveviewSlot(ProtectBaseObject):
|
|
|
1487
1487
|
|
|
1488
1488
|
# user may not have permission to see the cameras in the liveview
|
|
1489
1489
|
self._cameras = [
|
|
1490
|
-
self.
|
|
1490
|
+
self._api.bootstrap.cameras[g]
|
|
1491
1491
|
for g in self.camera_ids
|
|
1492
|
-
if g in self.
|
|
1492
|
+
if g in self._api.bootstrap.cameras
|
|
1493
1493
|
]
|
|
1494
1494
|
return self._cameras
|
|
1495
1495
|
|
|
@@ -1519,8 +1519,8 @@ class Liveview(ProtectModelWithId):
|
|
|
1519
1519
|
|
|
1520
1520
|
Will be none if the user only has read only access and it was not made by their user.
|
|
1521
1521
|
"""
|
|
1522
|
-
return self.
|
|
1522
|
+
return self._api.bootstrap.users.get(self.owner_id)
|
|
1523
1523
|
|
|
1524
1524
|
@property
|
|
1525
1525
|
def protect_url(self) -> str:
|
|
1526
|
-
return f"{self.
|
|
1526
|
+
return f"{self._api.base_url}/protect/liveview/{self.id}"
|
|
@@ -54,7 +54,7 @@ class Permission(ProtectBaseObject):
|
|
|
54
54
|
if self.obj_ids == {"self"} or self.obj_ids is None:
|
|
55
55
|
return None
|
|
56
56
|
|
|
57
|
-
devices = getattr(self.
|
|
57
|
+
devices = getattr(self._api.bootstrap, f"{self.model.value}s")
|
|
58
58
|
return [devices[oid] for oid in self.obj_ids]
|
|
59
59
|
|
|
60
60
|
|
|
@@ -110,7 +110,7 @@ class CloudAccount(ProtectModelWithId):
|
|
|
110
110
|
|
|
111
111
|
@property
|
|
112
112
|
def user(self) -> User:
|
|
113
|
-
return self.
|
|
113
|
+
return self._api.bootstrap.users[self.user_id]
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
class UserFeatureFlags(ProtectBaseObject):
|
|
@@ -199,9 +199,9 @@ class User(ProtectModelWithId):
|
|
|
199
199
|
return self._groups
|
|
200
200
|
|
|
201
201
|
self._groups = [
|
|
202
|
-
self.
|
|
202
|
+
self._api.bootstrap.groups[g]
|
|
203
203
|
for g in self.group_ids
|
|
204
|
-
if g in self.
|
|
204
|
+
if g in self._api.bootstrap.groups
|
|
205
205
|
]
|
|
206
206
|
return self._groups
|
|
207
207
|
|
|
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
|