python-aidot-cameras 0.5.0__tar.gz → 0.5.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.
Files changed (30) hide show
  1. {python_aidot_cameras-0.5.0/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.5.1}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/pyproject.toml +1 -1
  3. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/__init__.py +2 -0
  4. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/client.py +20 -28
  5. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/device_client.py +15 -15
  6. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/discover.py +3 -3
  7. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1/src/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  8. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -1
  9. python_aidot_cameras-0.5.0/src/aidot/login_const.py +0 -13
  10. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/LICENSE +0 -0
  11. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/README.md +0 -0
  12. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/setup.cfg +0 -0
  13. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/aes_utils.py +0 -0
  14. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/const.py +0 -0
  15. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/credentials.py +0 -0
  16. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/exceptions.py +0 -0
  17. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/aidot/g711.py +0 -0
  18. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  19. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
  20. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
  21. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_alarm_event.py +0 -0
  22. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_highport_nomination.py +0 -0
  23. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_motion_poll.py +0 -0
  24. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_sdes_talk.py +0 -0
  25. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_sdes_watchdog.py +0 -0
  26. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_speak.py +0 -0
  27. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_stream_cap.py +0 -0
  28. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_talk.py +0 -0
  29. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_terminal_ack.py +0 -0
  30. {python_aidot_cameras-0.5.0 → python_aidot_cameras-0.5.1}/tests/test_token_refresh.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)
5
5
  Author-email: cbrightly <chris.brightly@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.5.0"
7
+ version = "0.5.1"
8
8
  description = "Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -6,6 +6,7 @@ from .discover import Discover
6
6
  from .exceptions import (
7
7
  AidotAuthFailed,
8
8
  AidotAuthTokenExpired,
9
+ AidotCameraBusy,
9
10
  AidotError,
10
11
  AidotNotLogin,
11
12
  AidotOSError,
@@ -19,6 +20,7 @@ __all__ = [
19
20
  "AidotClient",
20
21
  "DeviceClient",
21
22
  "Discover",
23
+ "AidotCameraBusy",
22
24
  "AidotError",
23
25
  "AidotAuthFailed",
24
26
  "AidotAuthTokenExpired",
@@ -1,7 +1,6 @@
1
1
  """The aidot integration."""
2
2
 
3
3
  import asyncio
4
- import hashlib
5
4
  import json
6
5
  import logging
7
6
  import base64
@@ -54,10 +53,6 @@ def rsa_password_encrypt(message: str) -> str:
54
53
  return base64.b64encode(encrypted).decode("utf-8")
55
54
 
56
55
 
57
- def md5_password(message: str) -> str:
58
- """MD5 hex digest of the password (for /users/login web-app flow)."""
59
- return hashlib.md5(message.encode("utf-8")).hexdigest()
60
-
61
56
 
62
57
  class AidotClient:
63
58
  _base_url: str = BASE_URL
@@ -128,7 +123,7 @@ class AidotClient:
128
123
  try:
129
124
  response = await self.session.post(url, headers=headers, json=data)
130
125
  response_data = await response.json(content_type=None)
131
- _LOGGER.debug("async_post_login HTTP=%d response: %s", response.status, response_data)
126
+ _LOGGER.debug("async_post_login HTTP=%d code=%s", response.status, response_data.get(CONF_CODE))
132
127
  app_code = response_data.get(CONF_CODE)
133
128
  if app_code == ServerErrorCode.USER_PWD_INCORRECT:
134
129
  raise AidotUserOrPassIncorrect
@@ -183,7 +178,7 @@ class AidotClient:
183
178
  async with self.session.get(url, headers=headers,
184
179
  timeout=aiohttp.ClientTimeout(total=10)) as resp:
185
180
  body = await resp.json(content_type=None)
186
- _LOGGER.debug("userConfig response: %s", body)
181
+ _LOGGER.debug("userConfig response keys=%s", list(body.keys()) if isinstance(body, dict) else type(body).__name__)
187
182
  # The MQTT password field may be named mqttPassword or similar.
188
183
  # Store the full response data alongside login_info for DeviceClient.
189
184
  data = body if isinstance(body, dict) else {}
@@ -253,7 +248,7 @@ class AidotClient:
253
248
  inflight = self._ensure_token_inflight
254
249
  if inflight is not None and not inflight.done():
255
250
  return await inflight
256
- fut = asyncio.get_event_loop().create_future()
251
+ fut = asyncio.get_running_loop().create_future()
257
252
  self._ensure_token_inflight = fut
258
253
  try:
259
254
  result = await self._do_ensure_token()
@@ -342,26 +337,23 @@ class AidotClient:
342
337
 
343
338
  async def async_get_all_device(self) -> dict[str, Any]:
344
339
  final_device_list: list[dict[str, Any]] = []
345
- try:
346
- houses = await self.async_get_houses()
347
- for house in houses:
348
- if house.get(CONF_IS_OWNER) is False:
349
- continue
350
- # get device_list
351
- device_list = await self.async_get_devices(house[CONF_ID])
352
- if device_list:
353
- final_device_list.extend(device_list)
354
-
355
- # get product_list
356
- productIds = ",".join([item[CONF_PRODUCT_ID] for item in final_device_list])
357
- product_list = await self.async_get_products(productIds)
358
-
359
- for product in product_list:
360
- for device in final_device_list:
361
- if device[CONF_PRODUCT_ID] == product[CONF_ID]:
362
- device[CONF_PRODUCT] = product
363
- except Exception:
364
- raise
340
+ houses = await self.async_get_houses()
341
+ for house in houses:
342
+ if house.get(CONF_IS_OWNER) is False:
343
+ continue
344
+ # get device_list
345
+ device_list = await self.async_get_devices(house[CONF_ID])
346
+ if device_list:
347
+ final_device_list.extend(device_list)
348
+
349
+ # get product_list
350
+ productIds = ",".join([item[CONF_PRODUCT_ID] for item in final_device_list])
351
+ product_list = await self.async_get_products(productIds)
352
+
353
+ for product in product_list:
354
+ for device in final_device_list:
355
+ if device[CONF_PRODUCT_ID] == product[CONF_ID]:
356
+ device[CONF_PRODUCT] = product
365
357
 
366
358
  # Share the full device ID list with every DeviceClient so that
367
359
  # batchGetDeviceUserInfo is called with all IDs (the server may return
@@ -992,7 +992,7 @@ class TutkStreamSession:
992
992
 
993
993
  async def start(self) -> bool:
994
994
  """Load native libs, connect P2P, and start the frame-receive thread."""
995
- return await asyncio.get_event_loop().run_in_executor(
995
+ return await asyncio.get_running_loop().run_in_executor(
996
996
  None, self._start_sync)
997
997
 
998
998
  def _start_sync(self) -> bool:
@@ -1211,7 +1211,7 @@ class TutkStreamSession:
1211
1211
  """Signal the receive thread to stop and wait for it."""
1212
1212
  self._stop_event.set()
1213
1213
  if self._thread is not None:
1214
- await asyncio.get_event_loop().run_in_executor(
1214
+ await asyncio.get_running_loop().run_in_executor(
1215
1215
  None, lambda: self._thread.join(timeout=5.0)
1216
1216
  )
1217
1217
 
@@ -1328,7 +1328,7 @@ class LiveStreamSession:
1328
1328
  return False
1329
1329
 
1330
1330
  # Start background receive/heartbeat task.
1331
- self._task = asyncio.get_event_loop().create_task(self._receive_loop())
1331
+ self._task = asyncio.get_running_loop().create_task(self._receive_loop())
1332
1332
  return True
1333
1333
 
1334
1334
  async def stop(self) -> None:
@@ -4248,7 +4248,7 @@ class DeviceClient(object):
4248
4248
  buf = _io.BytesIO()
4249
4249
  pil_img.save(buf, "JPEG")
4250
4250
  self.latest_jpeg = buf.getvalue()
4251
- self._last_frame_time = asyncio.get_event_loop().time()
4251
+ self._last_frame_time = asyncio.get_running_loop().time()
4252
4252
  except Exception as enc_exc:
4253
4253
  _LOGGER.debug("Streaming encode failed for %s: %s", self.device_id, enc_exc)
4254
4254
 
@@ -4287,7 +4287,7 @@ class DeviceClient(object):
4287
4287
  try:
4288
4288
  while self._streaming_active:
4289
4289
  await asyncio.sleep(5.0)
4290
- elapsed = asyncio.get_event_loop().time() - self._last_frame_time
4290
+ elapsed = asyncio.get_running_loop().time() - self._last_frame_time
4291
4291
  if self._last_frame_time > 0 and elapsed > _WATCHDOG:
4292
4292
  _LOGGER.warning(
4293
4293
  "No frames from %s in %.0fs - restarting stream",
@@ -4417,7 +4417,7 @@ class DeviceClient(object):
4417
4417
  serve_url = self._keepalive_rtsp_url
4418
4418
  _MIN_DELAY, _MAX_DELAY = 5.0, 300.0
4419
4419
  retry_delay = _MIN_DELAY
4420
- loop = asyncio.get_event_loop()
4420
+ loop = asyncio.get_running_loop()
4421
4421
  while self._streaming_active:
4422
4422
  # Thread-safe queues: taps run on the loop, the A/V mux in a thread.
4423
4423
  vq: "_queue.Queue" = _queue.Queue(maxsize=600)
@@ -7162,10 +7162,10 @@ class DeviceClient(object):
7162
7162
  # the camera silently ignores the original request and we time out.
7163
7163
  _init_done: set = set()
7164
7164
  _init_pending: set = {answer_fut, camera_offer_fut, webrtc_req_echo_fut}
7165
- _init_deadline = asyncio.get_event_loop().time() + timeout
7165
+ _init_deadline = asyncio.get_running_loop().time() + timeout
7166
7166
  _init_reconnect_resends = 0
7167
- while asyncio.get_event_loop().time() < _init_deadline:
7168
- _init_remaining = _init_deadline - asyncio.get_event_loop().time()
7167
+ while asyncio.get_running_loop().time() < _init_deadline:
7168
+ _init_remaining = _init_deadline - asyncio.get_running_loop().time()
7169
7169
  _init_done, _init_pending = await asyncio.wait(
7170
7170
  _init_pending,
7171
7171
  timeout=min(1.0, max(0.01, _init_remaining)),
@@ -7201,12 +7201,12 @@ class DeviceClient(object):
7201
7201
  and camera_offer_fut not in _rr_done):
7202
7202
  _status("webrtcReq echo received - waiting for camera webrtcResp...")
7203
7203
  _rr_secondary_limit = 20.0
7204
- _rr_secondary_deadline = asyncio.get_event_loop().time() + _rr_secondary_limit
7204
+ _rr_secondary_deadline = asyncio.get_running_loop().time() + _rr_secondary_limit
7205
7205
  _rr_done2: set = set()
7206
7206
  _rr_pending2 = _rr_pending # {answer_fut, camera_offer_fut}
7207
7207
  _rr_reconnect_resends = 0
7208
- while asyncio.get_event_loop().time() < _rr_secondary_deadline:
7209
- _remaining = _rr_secondary_deadline - asyncio.get_event_loop().time()
7208
+ while asyncio.get_running_loop().time() < _rr_secondary_deadline:
7209
+ _remaining = _rr_secondary_deadline - asyncio.get_running_loop().time()
7210
7210
  _rr_done2, _rr_pending2 = await asyncio.wait(
7211
7211
  _rr_pending2,
7212
7212
  timeout=min(1.0, max(0.01, _remaining)),
@@ -7246,11 +7246,11 @@ class DeviceClient(object):
7246
7246
  outgoing_q.put_nowait(_di_p)
7247
7247
 
7248
7248
  _rr_ext_limit = 20.0
7249
- _rr_ext_deadline = asyncio.get_event_loop().time() + _rr_ext_limit
7249
+ _rr_ext_deadline = asyncio.get_running_loop().time() + _rr_ext_limit
7250
7250
  _rr_done3: set = set()
7251
7251
  _rr_reconnect_ext = 0
7252
- while asyncio.get_event_loop().time() < _rr_ext_deadline:
7253
- _ext_rem = _rr_ext_deadline - asyncio.get_event_loop().time()
7252
+ while asyncio.get_running_loop().time() < _rr_ext_deadline:
7253
+ _ext_rem = _rr_ext_deadline - asyncio.get_running_loop().time()
7254
7254
  _rr_done3, _rr_pending2 = await asyncio.wait(
7255
7255
  _rr_pending2,
7256
7256
  timeout=min(1.0, max(0.01, _ext_rem)),
@@ -93,7 +93,7 @@ class BroadcastProtocol:
93
93
  "service": "device",
94
94
  "method": "devDiscoveryReq",
95
95
  "seq": seq,
96
- "srcAddr": f"0.{self.user_id}]",
96
+ "srcAddr": f"0.{self.user_id}",
97
97
  "tst": current_timestamp_milliseconds,
98
98
  "payload": {
99
99
  "extends": {},
@@ -166,7 +166,7 @@ class Discover:
166
166
  self._discover_callback, user_id, broadcast_addr=broadcast_ip
167
167
  )
168
168
  try:
169
- await asyncio.get_event_loop().create_datagram_endpoint(
169
+ await asyncio.get_running_loop().create_datagram_endpoint(
170
170
  lambda p=protocol: p,
171
171
  local_addr=(bind_ip, 0),
172
172
  )
@@ -183,7 +183,7 @@ class Discover:
183
183
  # Last-resort fallback
184
184
  protocol = BroadcastProtocol(self._discover_callback, user_id)
185
185
  try:
186
- await asyncio.get_event_loop().create_datagram_endpoint(
186
+ await asyncio.get_running_loop().create_datagram_endpoint(
187
187
  lambda: protocol,
188
188
  local_addr=("0.0.0.0", 0),
189
189
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)
5
5
  Author-email: cbrightly <chris.brightly@gmail.com>
6
6
  License-Expression: MIT
@@ -10,7 +10,6 @@ src/aidot/device_client.py
10
10
  src/aidot/discover.py
11
11
  src/aidot/exceptions.py
12
12
  src/aidot/g711.py
13
- src/aidot/login_const.py
14
13
  src/python_aidot_cameras.egg-info/PKG-INFO
15
14
  src/python_aidot_cameras.egg-info/SOURCES.txt
16
15
  src/python_aidot_cameras.egg-info/dependency_links.txt
@@ -1,13 +0,0 @@
1
- """Constants for the aidot integration."""
2
-
3
- APP_ID = "1383974540041977857"
4
- BASE_URL = "https://prod-us-api.arnoo.com/v17"
5
-
6
- PUBLIC_KEY_PEM = b"""
7
- -----BEGIN PUBLIC KEY-----
8
- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtQAnPCi8ksPnS1Du6z96PsKfN
9
- p2Gp/f/bHwlrAdplbX3p7/TnGpnbJGkLq8uRxf6cw+vOthTsZjkPCF7CatRvRnTj
10
- c9fcy7yE0oXa5TloYyXD6GkxgftBbN/movkJJGQCc7gFavuYoAdTRBOyQoXBtm0m
11
- kXMSjXOldI/290b9BQIDAQAB
12
- -----END PUBLIC KEY-----
13
- """