uiprotect 0.1.0__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/__init__.py +13 -0
- uiprotect/__main__.py +24 -0
- uiprotect/api.py +1936 -0
- uiprotect/cli/__init__.py +314 -0
- uiprotect/cli/backup.py +1103 -0
- uiprotect/cli/base.py +238 -0
- uiprotect/cli/cameras.py +574 -0
- uiprotect/cli/chimes.py +180 -0
- uiprotect/cli/doorlocks.py +125 -0
- uiprotect/cli/events.py +258 -0
- uiprotect/cli/lights.py +119 -0
- uiprotect/cli/liveviews.py +65 -0
- uiprotect/cli/nvr.py +154 -0
- uiprotect/cli/sensors.py +278 -0
- uiprotect/cli/viewers.py +76 -0
- uiprotect/data/__init__.py +157 -0
- uiprotect/data/base.py +1116 -0
- uiprotect/data/bootstrap.py +634 -0
- uiprotect/data/convert.py +77 -0
- uiprotect/data/devices.py +3384 -0
- uiprotect/data/nvr.py +1520 -0
- uiprotect/data/types.py +630 -0
- uiprotect/data/user.py +236 -0
- uiprotect/data/websocket.py +236 -0
- uiprotect/exceptions.py +41 -0
- uiprotect/py.typed +0 -0
- uiprotect/release_cache.json +1 -0
- uiprotect/stream.py +166 -0
- uiprotect/test_util/__init__.py +531 -0
- uiprotect/test_util/anonymize.py +257 -0
- uiprotect/utils.py +610 -0
- uiprotect/websocket.py +225 -0
- uiprotect-0.1.0.dist-info/LICENSE +23 -0
- uiprotect-0.1.0.dist-info/METADATA +245 -0
- uiprotect-0.1.0.dist-info/RECORD +37 -0
- uiprotect-0.1.0.dist-info/WHEEL +4 -0
- uiprotect-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
import shutil
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Callable, Coroutine
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from shlex import split
|
|
12
|
+
from subprocess import run
|
|
13
|
+
from typing import Any, overload
|
|
14
|
+
|
|
15
|
+
import aiohttp
|
|
16
|
+
from PIL import Image
|
|
17
|
+
|
|
18
|
+
from uiprotect.api import ProtectApiClient
|
|
19
|
+
from uiprotect.data import EventType, WSJSONPacketFrame, WSPacket
|
|
20
|
+
from uiprotect.exceptions import BadRequest
|
|
21
|
+
from uiprotect.test_util.anonymize import (
|
|
22
|
+
anonymize_data,
|
|
23
|
+
anonymize_prefixed_event_id,
|
|
24
|
+
)
|
|
25
|
+
from uiprotect.utils import from_js_time, is_online, run_async, write_json
|
|
26
|
+
|
|
27
|
+
BLANK_VIDEO_CMD = "ffmpeg -y -hide_banner -loglevel error -f lavfi -i color=size=1280x720:rate=25:color=black -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -t {length} {filename}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def placeholder_image(
|
|
31
|
+
output_path: Path,
|
|
32
|
+
width: int,
|
|
33
|
+
height: int | None = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
if height is None:
|
|
36
|
+
height = width
|
|
37
|
+
|
|
38
|
+
image = Image.new("RGB", (width, height), (128, 128, 128))
|
|
39
|
+
image.save(output_path, "PNG")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
_LOGGER = logging.getLogger(__name__)
|
|
43
|
+
LOG_CALLABLE = Callable[[str], None]
|
|
44
|
+
PROGRESS_CALLABLE = Callable[[int, str], Coroutine[Any, Any, None]]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SampleDataGenerator:
|
|
48
|
+
"""Generate sample data for debugging and testing purposes"""
|
|
49
|
+
|
|
50
|
+
_record_num_ws: int = 0
|
|
51
|
+
_record_ws_start_time: float = time.monotonic()
|
|
52
|
+
_record_listen_for_events: bool = False
|
|
53
|
+
_record_ws_messages: dict[str, dict[str, Any]] = {}
|
|
54
|
+
_log: LOG_CALLABLE | None = None
|
|
55
|
+
_log_warning: LOG_CALLABLE | None = None
|
|
56
|
+
_ws_progress: PROGRESS_CALLABLE | None = None
|
|
57
|
+
|
|
58
|
+
constants: dict[str, Any] = {}
|
|
59
|
+
client: ProtectApiClient
|
|
60
|
+
output_folder: Path
|
|
61
|
+
do_zip: bool
|
|
62
|
+
anonymize: bool
|
|
63
|
+
wait_time: int
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
client: ProtectApiClient,
|
|
68
|
+
output: Path,
|
|
69
|
+
anonymize: bool,
|
|
70
|
+
wait_time: int,
|
|
71
|
+
log: LOG_CALLABLE | None = None,
|
|
72
|
+
log_warning: LOG_CALLABLE | None = None,
|
|
73
|
+
ws_progress: PROGRESS_CALLABLE | None = None,
|
|
74
|
+
do_zip: bool = False,
|
|
75
|
+
) -> None:
|
|
76
|
+
self.client = client
|
|
77
|
+
self.output_folder = output
|
|
78
|
+
self.do_zip = do_zip
|
|
79
|
+
self.anonymize = anonymize
|
|
80
|
+
self.wait_time = wait_time
|
|
81
|
+
self._log = log
|
|
82
|
+
self._log_warning = log_warning
|
|
83
|
+
self._ws_progress = ws_progress
|
|
84
|
+
|
|
85
|
+
if self._log_warning is None and self._log is not None:
|
|
86
|
+
self._log_warning = self._log
|
|
87
|
+
|
|
88
|
+
def log(self, msg: str) -> None:
|
|
89
|
+
if self._log is not None:
|
|
90
|
+
self._log(msg)
|
|
91
|
+
else:
|
|
92
|
+
_LOGGER.debug(msg)
|
|
93
|
+
|
|
94
|
+
def log_warning(self, msg: str) -> None:
|
|
95
|
+
if self._log_warning is not None:
|
|
96
|
+
self._log_warning(msg)
|
|
97
|
+
else:
|
|
98
|
+
_LOGGER.warning(msg)
|
|
99
|
+
|
|
100
|
+
def generate(self) -> None:
|
|
101
|
+
run_async(self.async_generate())
|
|
102
|
+
|
|
103
|
+
async def async_generate(self, close_session: bool = True) -> None:
|
|
104
|
+
self.log(f"Output folder: {self.output_folder}")
|
|
105
|
+
self.output_folder.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
websocket = await self.client.get_websocket()
|
|
107
|
+
websocket.subscribe(self._handle_ws_message)
|
|
108
|
+
|
|
109
|
+
self.log("Updating devices...")
|
|
110
|
+
await self.client.update()
|
|
111
|
+
|
|
112
|
+
bootstrap: dict[str, Any] = await self.client.api_request_obj("bootstrap")
|
|
113
|
+
bootstrap = await self.write_json_file("sample_bootstrap", bootstrap)
|
|
114
|
+
self.constants["server_name"] = bootstrap["nvr"]["name"]
|
|
115
|
+
self.constants["server_id"] = bootstrap["nvr"]["mac"]
|
|
116
|
+
self.constants["server_version"] = bootstrap["nvr"]["version"]
|
|
117
|
+
self.constants["server_ip"] = bootstrap["nvr"]["host"]
|
|
118
|
+
self.constants["server_model"] = bootstrap["nvr"]["type"]
|
|
119
|
+
self.constants["last_update_id"] = bootstrap["lastUpdateId"]
|
|
120
|
+
self.constants["user_id"] = bootstrap["authUserId"]
|
|
121
|
+
self.constants["counts"] = {
|
|
122
|
+
"camera": len(bootstrap["cameras"]),
|
|
123
|
+
"user": len(bootstrap["users"]),
|
|
124
|
+
"group": len(bootstrap["groups"]),
|
|
125
|
+
"liveview": len(bootstrap["liveviews"]),
|
|
126
|
+
"viewer": len(bootstrap["viewers"]),
|
|
127
|
+
"light": len(bootstrap["lights"]),
|
|
128
|
+
"bridge": len(bootstrap["bridges"]),
|
|
129
|
+
"sensor": len(bootstrap["sensors"]),
|
|
130
|
+
"doorlock": len(bootstrap["doorlocks"]),
|
|
131
|
+
"chime": len(bootstrap["chimes"]),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
motion_event, smart_detection = await self.generate_event_data()
|
|
135
|
+
await self.generate_device_data(motion_event, smart_detection)
|
|
136
|
+
await self.record_ws_events()
|
|
137
|
+
|
|
138
|
+
if close_session:
|
|
139
|
+
await self.client.close_session()
|
|
140
|
+
|
|
141
|
+
await self.write_json_file("sample_constants", self.constants, anonymize=False)
|
|
142
|
+
|
|
143
|
+
if self.do_zip:
|
|
144
|
+
self.log("Zipping files...")
|
|
145
|
+
|
|
146
|
+
def zip_files() -> None:
|
|
147
|
+
shutil.make_archive(str(self.output_folder), "zip", self.output_folder)
|
|
148
|
+
shutil.rmtree(self.output_folder)
|
|
149
|
+
|
|
150
|
+
loop = asyncio.get_running_loop()
|
|
151
|
+
await loop.run_in_executor(None, zip_files)
|
|
152
|
+
|
|
153
|
+
async def record_ws_events(self) -> None:
|
|
154
|
+
if self.wait_time <= 0:
|
|
155
|
+
self.log("Skipping recording Websocket messages...")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
self._record_num_ws = 0
|
|
159
|
+
self._record_ws_start_time = time.monotonic()
|
|
160
|
+
self._record_listen_for_events = True
|
|
161
|
+
self._record_ws_messages = {}
|
|
162
|
+
|
|
163
|
+
self.log(f"Waiting {self.wait_time} seconds for WS messages...")
|
|
164
|
+
if self._ws_progress is not None:
|
|
165
|
+
await self._ws_progress(self.wait_time, "Waiting for WS messages")
|
|
166
|
+
else:
|
|
167
|
+
await asyncio.sleep(self.wait_time)
|
|
168
|
+
|
|
169
|
+
self._record_listen_for_events = False
|
|
170
|
+
await self.client.async_disconnect_ws()
|
|
171
|
+
await self.write_json_file(
|
|
172
|
+
"sample_ws_messages",
|
|
173
|
+
self._record_ws_messages,
|
|
174
|
+
anonymize=False,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
@overload
|
|
178
|
+
async def write_json_file(
|
|
179
|
+
self,
|
|
180
|
+
name: str,
|
|
181
|
+
data: list[Any],
|
|
182
|
+
anonymize: bool | None = None,
|
|
183
|
+
) -> list[Any]: ...
|
|
184
|
+
|
|
185
|
+
@overload
|
|
186
|
+
async def write_json_file(
|
|
187
|
+
self,
|
|
188
|
+
name: str,
|
|
189
|
+
data: dict[str, Any],
|
|
190
|
+
anonymize: bool | None = None,
|
|
191
|
+
) -> dict[str, Any]: ...
|
|
192
|
+
|
|
193
|
+
async def write_json_file(
|
|
194
|
+
self,
|
|
195
|
+
name: str,
|
|
196
|
+
data: list[Any] | dict[str, Any],
|
|
197
|
+
anonymize: bool | None = None,
|
|
198
|
+
) -> list[Any] | dict[str, Any]:
|
|
199
|
+
if anonymize is None:
|
|
200
|
+
anonymize = self.anonymize
|
|
201
|
+
|
|
202
|
+
if anonymize:
|
|
203
|
+
data = anonymize_data(data)
|
|
204
|
+
|
|
205
|
+
self.log(f"Writing {name}...")
|
|
206
|
+
await write_json(self.output_folder / f"{name}.json", data)
|
|
207
|
+
|
|
208
|
+
return data
|
|
209
|
+
|
|
210
|
+
async def write_binary_file(
|
|
211
|
+
self,
|
|
212
|
+
name: str,
|
|
213
|
+
ext: str,
|
|
214
|
+
raw: bytes | None,
|
|
215
|
+
) -> None:
|
|
216
|
+
def write() -> None:
|
|
217
|
+
if raw is None:
|
|
218
|
+
self.log(f"No image data, skipping {name}...")
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
self.log(f"Writing {name}...")
|
|
222
|
+
Path(self.output_folder / f"{name}.{ext}").write_bytes(raw)
|
|
223
|
+
|
|
224
|
+
loop = asyncio.get_running_loop()
|
|
225
|
+
await loop.run_in_executor(None, write)
|
|
226
|
+
|
|
227
|
+
async def write_image_file(self, name: str, raw: bytes | None) -> None:
|
|
228
|
+
await self.write_binary_file(name, "png", raw)
|
|
229
|
+
|
|
230
|
+
async def generate_event_data(
|
|
231
|
+
self,
|
|
232
|
+
) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
|
|
233
|
+
data = await self.client.get_events_raw()
|
|
234
|
+
|
|
235
|
+
self.constants["time"] = datetime.now(tz=timezone.utc).isoformat()
|
|
236
|
+
self.constants["event_count"] = len(data)
|
|
237
|
+
|
|
238
|
+
motion_event: dict[str, Any] | None = None
|
|
239
|
+
smart_detection: dict[str, Any] | None = None
|
|
240
|
+
for event_dict in reversed(data):
|
|
241
|
+
if (
|
|
242
|
+
motion_event is None
|
|
243
|
+
and event_dict["type"] == EventType.MOTION.value
|
|
244
|
+
and event_dict["camera"] is not None
|
|
245
|
+
and event_dict["thumbnail"] is not None
|
|
246
|
+
and event_dict["heatmap"] is not None
|
|
247
|
+
and event_dict["end"] is not None
|
|
248
|
+
):
|
|
249
|
+
motion_event = deepcopy(event_dict)
|
|
250
|
+
self.log(f"Using motion event: {motion_event['id']}...")
|
|
251
|
+
elif (
|
|
252
|
+
smart_detection is None
|
|
253
|
+
and event_dict["type"] == EventType.SMART_DETECT.value
|
|
254
|
+
and event_dict["camera"] is not None
|
|
255
|
+
and event_dict["end"] is not None
|
|
256
|
+
):
|
|
257
|
+
smart_detection = deepcopy(event_dict)
|
|
258
|
+
self.log(f"Using smart detection event: {smart_detection['id']}...")
|
|
259
|
+
|
|
260
|
+
if motion_event is not None and smart_detection is not None:
|
|
261
|
+
break
|
|
262
|
+
|
|
263
|
+
# anonymize data after pulling events
|
|
264
|
+
data = await self.write_json_file("sample_raw_events", data)
|
|
265
|
+
|
|
266
|
+
return motion_event, smart_detection
|
|
267
|
+
|
|
268
|
+
async def generate_device_data(
|
|
269
|
+
self,
|
|
270
|
+
motion_event: dict[str, Any] | None,
|
|
271
|
+
smart_detection: dict[str, Any] | None,
|
|
272
|
+
) -> None:
|
|
273
|
+
await asyncio.gather(
|
|
274
|
+
self.generate_camera_data(),
|
|
275
|
+
self.generate_motion_data(motion_event),
|
|
276
|
+
self.generate_smart_detection_data(smart_detection),
|
|
277
|
+
self.generate_light_data(),
|
|
278
|
+
self.generate_viewport_data(),
|
|
279
|
+
self.generate_sensor_data(),
|
|
280
|
+
self.generate_lock_data(),
|
|
281
|
+
self.generate_chime_data(),
|
|
282
|
+
self.generate_bridge_data(),
|
|
283
|
+
self.generate_liveview_data(),
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
async def generate_camera_data(self) -> None:
|
|
287
|
+
objs = await self.client.api_request_list("cameras")
|
|
288
|
+
device_id: str | None = None
|
|
289
|
+
camera_is_online = False
|
|
290
|
+
for obj_dict in objs:
|
|
291
|
+
device_id = obj_dict["id"]
|
|
292
|
+
if is_online(obj_dict):
|
|
293
|
+
camera_is_online = True
|
|
294
|
+
break
|
|
295
|
+
|
|
296
|
+
if device_id is None:
|
|
297
|
+
self.log("No camera found. Skipping camera endpoints...")
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# json data
|
|
301
|
+
obj = await self.client.api_request_obj(f"cameras/{device_id}")
|
|
302
|
+
await self.write_json_file("sample_camera", deepcopy(obj))
|
|
303
|
+
self.constants["camera_online"] = camera_is_online
|
|
304
|
+
|
|
305
|
+
if not camera_is_online:
|
|
306
|
+
self.log(
|
|
307
|
+
"Camera is not online, skipping snapshot, thumbnail and heatmap generation",
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# snapshot
|
|
311
|
+
width = obj["channels"][0]["width"]
|
|
312
|
+
height = obj["channels"][0]["height"]
|
|
313
|
+
filename = "sample_camera_snapshot"
|
|
314
|
+
if self.anonymize:
|
|
315
|
+
self.log(f"Writing {filename}...")
|
|
316
|
+
placeholder_image(self.output_folder / f"{filename}.png", width, height)
|
|
317
|
+
else:
|
|
318
|
+
snapshot = await self.client.get_camera_snapshot(obj["id"], width, height)
|
|
319
|
+
await self.write_image_file(filename, snapshot)
|
|
320
|
+
|
|
321
|
+
async def generate_motion_data(
|
|
322
|
+
self,
|
|
323
|
+
motion_event: dict[str, Any] | None,
|
|
324
|
+
) -> None:
|
|
325
|
+
if motion_event is None:
|
|
326
|
+
self.log("No motion event, skipping thumbnail and heatmap generation...")
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
# event thumbnail
|
|
330
|
+
filename = "sample_camera_thumbnail"
|
|
331
|
+
thumbnail_id = motion_event["thumbnail"]
|
|
332
|
+
if self.anonymize:
|
|
333
|
+
self.log(f"Writing {filename}...")
|
|
334
|
+
placeholder_image(self.output_folder / f"{filename}.png", 640, 360)
|
|
335
|
+
thumbnail_id = anonymize_prefixed_event_id(thumbnail_id)
|
|
336
|
+
else:
|
|
337
|
+
img = await self.client.get_event_thumbnail(thumbnail_id)
|
|
338
|
+
await self.write_image_file(filename, img)
|
|
339
|
+
self.constants["camera_thumbnail"] = thumbnail_id
|
|
340
|
+
|
|
341
|
+
# event heatmap
|
|
342
|
+
filename = "sample_camera_heatmap"
|
|
343
|
+
heatmap_id = motion_event["heatmap"]
|
|
344
|
+
if self.anonymize:
|
|
345
|
+
self.log(f"Writing {filename}...")
|
|
346
|
+
placeholder_image(self.output_folder / f"{filename}.png", 640, 360)
|
|
347
|
+
heatmap_id = anonymize_prefixed_event_id(heatmap_id)
|
|
348
|
+
else:
|
|
349
|
+
img = await self.client.get_event_heatmap(heatmap_id)
|
|
350
|
+
await self.write_image_file(filename, img)
|
|
351
|
+
self.constants["camera_heatmap"] = heatmap_id
|
|
352
|
+
|
|
353
|
+
# event video
|
|
354
|
+
filename = "sample_camera_video"
|
|
355
|
+
length = int((motion_event["end"] - motion_event["start"]) / 1000)
|
|
356
|
+
if self.anonymize:
|
|
357
|
+
run(
|
|
358
|
+
split( # noqa: S603
|
|
359
|
+
BLANK_VIDEO_CMD.format(
|
|
360
|
+
length=length,
|
|
361
|
+
filename=self.output_folder / f"{filename}.mp4",
|
|
362
|
+
),
|
|
363
|
+
),
|
|
364
|
+
check=True,
|
|
365
|
+
)
|
|
366
|
+
else:
|
|
367
|
+
video = await self.client.get_camera_video(
|
|
368
|
+
motion_event["camera"],
|
|
369
|
+
from_js_time(motion_event["start"]),
|
|
370
|
+
from_js_time(motion_event["end"]),
|
|
371
|
+
2,
|
|
372
|
+
)
|
|
373
|
+
await self.write_binary_file(filename, "mp4", video)
|
|
374
|
+
self.constants["camera_video_length"] = length
|
|
375
|
+
|
|
376
|
+
async def generate_smart_detection_data(
|
|
377
|
+
self,
|
|
378
|
+
smart_detection: dict[str, Any] | None,
|
|
379
|
+
) -> None:
|
|
380
|
+
if smart_detection is None:
|
|
381
|
+
self.log("No smart detection event, skipping smart detection data...")
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
data = await self.client.get_event_smart_detect_track_raw(
|
|
386
|
+
smart_detection["id"],
|
|
387
|
+
)
|
|
388
|
+
except BadRequest:
|
|
389
|
+
self.log_warning("Event smart tracking missing")
|
|
390
|
+
else:
|
|
391
|
+
await self.write_json_file("sample_event_smart_track", data)
|
|
392
|
+
|
|
393
|
+
async def generate_light_data(self) -> None:
|
|
394
|
+
objs = await self.client.api_request_list("lights")
|
|
395
|
+
device_id: str | None = None
|
|
396
|
+
for obj_dict in objs:
|
|
397
|
+
device_id = obj_dict["id"]
|
|
398
|
+
if is_online(obj_dict):
|
|
399
|
+
break
|
|
400
|
+
|
|
401
|
+
if device_id is None:
|
|
402
|
+
self.log("No light found. Skipping light endpoints...")
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
obj = await self.client.api_request_obj(f"lights/{device_id}")
|
|
406
|
+
await self.write_json_file("sample_light", obj)
|
|
407
|
+
|
|
408
|
+
async def generate_viewport_data(self) -> None:
|
|
409
|
+
objs = await self.client.api_request_list("viewers")
|
|
410
|
+
device_id: str | None = None
|
|
411
|
+
for obj_dict in objs:
|
|
412
|
+
device_id = obj_dict["id"]
|
|
413
|
+
if is_online(obj_dict):
|
|
414
|
+
break
|
|
415
|
+
|
|
416
|
+
if device_id is None:
|
|
417
|
+
self.log("No viewer found. Skipping viewer endpoints...")
|
|
418
|
+
return
|
|
419
|
+
|
|
420
|
+
obj = await self.client.api_request_obj(f"viewers/{device_id}")
|
|
421
|
+
await self.write_json_file("sample_viewport", obj)
|
|
422
|
+
|
|
423
|
+
async def generate_sensor_data(self) -> None:
|
|
424
|
+
objs = await self.client.api_request_list("sensors")
|
|
425
|
+
device_id: str | None = None
|
|
426
|
+
for obj_dict in objs:
|
|
427
|
+
device_id = obj_dict["id"]
|
|
428
|
+
if is_online(obj_dict):
|
|
429
|
+
break
|
|
430
|
+
|
|
431
|
+
if device_id is None:
|
|
432
|
+
self.log("No sensor found. Skipping sensor endpoints...")
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
obj = await self.client.api_request_obj(f"sensors/{device_id}")
|
|
436
|
+
await self.write_json_file("sample_sensor", obj)
|
|
437
|
+
|
|
438
|
+
async def generate_lock_data(self) -> None:
|
|
439
|
+
objs = await self.client.api_request_list("doorlocks")
|
|
440
|
+
device_id: str | None = None
|
|
441
|
+
for obj_dict in objs:
|
|
442
|
+
device_id = obj_dict["id"]
|
|
443
|
+
if is_online(obj_dict):
|
|
444
|
+
break
|
|
445
|
+
|
|
446
|
+
if device_id is None:
|
|
447
|
+
self.log("No doorlock found. Skipping doorlock endpoints...")
|
|
448
|
+
return
|
|
449
|
+
|
|
450
|
+
obj = await self.client.api_request_obj(f"doorlocks/{device_id}")
|
|
451
|
+
await self.write_json_file("sample_doorlock", obj)
|
|
452
|
+
|
|
453
|
+
async def generate_chime_data(self) -> None:
|
|
454
|
+
objs = await self.client.api_request_list("chimes")
|
|
455
|
+
device_id: str | None = None
|
|
456
|
+
for obj_dict in objs:
|
|
457
|
+
device_id = obj_dict["id"]
|
|
458
|
+
if is_online(obj_dict):
|
|
459
|
+
break
|
|
460
|
+
|
|
461
|
+
if device_id is None:
|
|
462
|
+
self.log("No chime found. Skipping doorlock endpoints...")
|
|
463
|
+
return
|
|
464
|
+
|
|
465
|
+
obj = await self.client.api_request_obj(f"chimes/{device_id}")
|
|
466
|
+
await self.write_json_file("sample_chime", obj)
|
|
467
|
+
|
|
468
|
+
async def generate_bridge_data(self) -> None:
|
|
469
|
+
objs = await self.client.api_request_list("bridges")
|
|
470
|
+
device_id: str | None = None
|
|
471
|
+
for obj_dict in objs:
|
|
472
|
+
device_id = obj_dict["id"]
|
|
473
|
+
if is_online(obj_dict):
|
|
474
|
+
break
|
|
475
|
+
|
|
476
|
+
if device_id is None:
|
|
477
|
+
self.log("No bridge found. Skipping bridge endpoints...")
|
|
478
|
+
return
|
|
479
|
+
|
|
480
|
+
obj = await self.client.api_request_obj(f"bridges/{device_id}")
|
|
481
|
+
await self.write_json_file("sample_bridge", obj)
|
|
482
|
+
|
|
483
|
+
async def generate_liveview_data(self) -> None:
|
|
484
|
+
objs = await self.client.api_request_list("liveviews")
|
|
485
|
+
device_id: str | None = None
|
|
486
|
+
for obj_dict in objs:
|
|
487
|
+
device_id = obj_dict["id"]
|
|
488
|
+
break
|
|
489
|
+
|
|
490
|
+
if device_id is None:
|
|
491
|
+
self.log("No liveview found. Skipping liveview endpoints...")
|
|
492
|
+
return
|
|
493
|
+
|
|
494
|
+
obj = await self.client.api_request_obj(f"liveviews/{device_id}")
|
|
495
|
+
await self.write_json_file("sample_liveview", obj)
|
|
496
|
+
|
|
497
|
+
def _handle_ws_message(self, msg: aiohttp.WSMessage) -> None:
|
|
498
|
+
if not self._record_listen_for_events:
|
|
499
|
+
return
|
|
500
|
+
|
|
501
|
+
now = time.monotonic()
|
|
502
|
+
self._record_num_ws += 1
|
|
503
|
+
time_offset = now - self._record_ws_start_time
|
|
504
|
+
|
|
505
|
+
if msg.type == aiohttp.WSMsgType.BINARY:
|
|
506
|
+
packet = WSPacket(msg.data)
|
|
507
|
+
|
|
508
|
+
if not isinstance(packet.action_frame, WSJSONPacketFrame):
|
|
509
|
+
self.log_warning(
|
|
510
|
+
f"Got non-JSON action frame: {packet.action_frame.payload_format}",
|
|
511
|
+
)
|
|
512
|
+
return
|
|
513
|
+
|
|
514
|
+
if not isinstance(packet.data_frame, WSJSONPacketFrame):
|
|
515
|
+
self.log_warning(
|
|
516
|
+
f"Got non-JSON data frame: {packet.data_frame.payload_format}",
|
|
517
|
+
)
|
|
518
|
+
return
|
|
519
|
+
|
|
520
|
+
if self.anonymize:
|
|
521
|
+
packet.action_frame.data = anonymize_data(packet.action_frame.data)
|
|
522
|
+
packet.data_frame.data = anonymize_data(packet.data_frame.data)
|
|
523
|
+
packet.pack_frames()
|
|
524
|
+
|
|
525
|
+
self._record_ws_messages[str(time_offset)] = {
|
|
526
|
+
"raw": packet.raw_base64,
|
|
527
|
+
"action": packet.action_frame.data,
|
|
528
|
+
"data": packet.data_frame.data,
|
|
529
|
+
}
|
|
530
|
+
else:
|
|
531
|
+
self.log_warning(f"Got non-binary message: {msg.type}")
|