python-aidot-cameras 0.7.10__tar.gz → 0.7.11__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 (45) hide show
  1. {python_aidot_cameras-0.7.10/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.7.11}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/pyproject.toml +1 -1
  3. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/camera/client.py +44 -90
  4. python_aidot_cameras-0.7.11/src/aidot/camera/constants.py +88 -0
  5. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11/src/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  6. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
  7. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/LICENSE +0 -0
  8. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/MANIFEST.in +0 -0
  9. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/README.md +0 -0
  10. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/setup.cfg +0 -0
  11. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/__init__.py +0 -0
  12. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/aes_utils.py +0 -0
  13. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/camera/__init__.py +0 -0
  14. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/camera/lan_control.py +0 -0
  15. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/client.py +0 -0
  16. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/const.py +0 -0
  17. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/credentials.py +0 -0
  18. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/device_client.py +0 -0
  19. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/discover.py +0 -0
  20. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/exceptions.py +0 -0
  21. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/g711.py +0 -0
  22. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/login_const.py +0 -0
  23. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/models/__init__.py +0 -0
  24. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/models/device_client_model.py +0 -0
  25. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/models/device_model.py +0 -0
  26. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/models/discover_model.py +0 -0
  27. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/aidot/py.typed +0 -0
  28. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  29. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
  30. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
  31. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_alarm_event.py +0 -0
  32. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_highport_nomination.py +0 -0
  33. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_lan_control.py +0 -0
  34. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_motion_poll.py +0 -0
  35. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_no_undefined_names.py +0 -0
  36. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_post_merge_hardening.py +0 -0
  37. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_sdes_idle_release.py +0 -0
  38. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_sdes_sprop.py +0 -0
  39. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_sdes_talk.py +0 -0
  40. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_sdes_watchdog.py +0 -0
  41. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_speak.py +0 -0
  42. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_stream_cap.py +0 -0
  43. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_talk.py +0 -0
  44. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/tests/test_terminal_ack.py +0 -0
  45. {python_aidot_cameras-0.7.10 → python_aidot_cameras-0.7.11}/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.7.10
3
+ Version: 0.7.11
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.7.10"
7
+ version = "0.7.11"
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"
@@ -26,6 +26,40 @@ from ..const import (
26
26
  CONF_SERVICE_MODULES,
27
27
  )
28
28
 
29
+ # Wire/protocol constants live in constants.py; re-imported here so all
30
+ # in-module references (and any external importers) keep resolving.
31
+ from .constants import (
32
+ _LEEDARSON_APP_ID,
33
+ _SMARTHOME_URL_TEMPLATE,
34
+ _HDR_FMT,
35
+ _HDR_PREFIX_FMT,
36
+ _HDR_PREFIX_SIZE,
37
+ _HDR_SUFFIX_FMT,
38
+ _HDR_SUFFIX_SIZE,
39
+ _HDR_VERSION,
40
+ _HDR_CONTEXT,
41
+ _HDR_ENC_TYPE,
42
+ _HDR_RESULT,
43
+ _HDR_RESERVE,
44
+ _CMD_LOGIN_REQ,
45
+ _CMD_LOGIN_RES,
46
+ _CMD_HB_REQ,
47
+ _CMD_HB_RES,
48
+ _CMD_STREAM_REQ,
49
+ _CMD_STREAM_RES,
50
+ _CMD_SUBCMD,
51
+ _CMD_PARAM,
52
+ _SF_HDR_SIZE,
53
+ _FRAME_TYPE_P_FRAME,
54
+ _FRAME_TYPE_B_FRAME,
55
+ _FRAME_TYPE_I_FRAME,
56
+ _FRAME_TYPE_AUDIO,
57
+ _PTZ_DIR_CODES,
58
+ SETSTREAMCTRL_CMD,
59
+ GETSTREAMCTRL_CMD, # noqa: F401 - re-exported for back-compat (public pair; unused in-module)
60
+ _STREAM_QUALITY,
61
+ )
62
+
29
63
  _LOGGER = logging.getLogger(__name__)
30
64
 
31
65
  # Strong refs to fire-and-forget tasks: asyncio only keeps weak refs, so a
@@ -202,86 +236,6 @@ class CameraDeviceInformation(DeviceInformation):
202
236
  # Camera / Leedarson smarthome API constants
203
237
  # --------------------------------------------------------------------------- #
204
238
 
205
- # AppKey from LDSAppOpenSDK CocoaPods docs (kLDSAppOpenSDKKey = "appa070")
206
- _LEEDARSON_APP_KEY = "appa070"
207
-
208
- # APP_ID sent in smarthome HTTP headers - value from MainActivity.java SharedPrefs
209
- # (SharePreferenceUtils.setPrefString(this, "APP_ID", "1392315867093508098")).
210
- _LEEDARSON_APP_ID = "1392315867093508098"
211
-
212
- # Camera-specific backend; region prefix mirrors AidotClient._base_url pattern.
213
- # e.g. "us" -> "https://us-smarthome.arnoo.com:443"
214
- _SMARTHOME_URL_TEMPLATE = "https://{region}-smarthome.arnoo.com:443"
215
-
216
- # --------------------------------------------------------------------------- #
217
- # Playback TCP binary framing constants
218
- #
219
- # Wire layout (all big-endian) from RecordVideoEncoder.java, verified against
220
- # INettyClientInitializer.java Netty params:
221
- # lengthFieldOffset=14, lengthFieldLength=4, lengthAdjustment=19
222
- #
223
- # version(H2) seq(i4) cmd(H2) subcmd(H2) cmdParam(i4) <- 14 bytes
224
- # payloadLen(i4) <- offset 14
225
- # timestamp(q8) context(i4) encodeType(b1) result(h2) reserve(i4) <- 19 bytes
226
- # <payload bytes>
227
- # Total header = 37 bytes
228
- # --------------------------------------------------------------------------- #
229
-
230
- _HDR_FMT = ">HiHHiiqibhi"
231
- _HDR_SIZE = struct.calcsize(_HDR_FMT) # 37
232
- _HDR_PREFIX_FMT = ">HiHHii"
233
- _HDR_PREFIX_SIZE = struct.calcsize(_HDR_PREFIX_FMT) # 18
234
- _HDR_SUFFIX_FMT = ">qibhi"
235
- _HDR_SUFFIX_SIZE = struct.calcsize(_HDR_SUFFIX_FMT) # 19
236
-
237
- assert _HDR_SIZE == 37
238
- assert _HDR_PREFIX_SIZE == 18
239
- assert _HDR_SUFFIX_SIZE == 19
240
-
241
- # Fixed values for all outbound request frames
242
- _HDR_VERSION = 256 # 0x0100
243
- _HDR_CONTEXT = 1005
244
- _HDR_ENC_TYPE = 1
245
- _HDR_RESULT = 4
246
- _HDR_RESERVE = 2
247
-
248
- # TCP command codes from AppCmd.java
249
- _CMD_LOGIN_REQ = 0x0101
250
- _CMD_LOGIN_RES = 0x0102
251
- _CMD_HB_REQ = 0x0105
252
- _CMD_HB_RES = 0x0106
253
- _CMD_STREAM_REQ = 0x0107
254
- _CMD_STREAM_RES = 0x0108
255
- _CMD_SUBCMD = 0x0001
256
- _CMD_PARAM = 0x00000002
257
-
258
- # Video sub-frame header size from LDSPlayer.decodeStream():
259
- # padding(2) frameType(1) audioCodec(1) timestamp(8) encType(1) payloadLen(4)
260
- _SF_HDR_SIZE = 17
261
-
262
- # Frame type values
263
- _FRAME_TYPE_P_FRAME = 2
264
- _FRAME_TYPE_B_FRAME = 3
265
- _FRAME_TYPE_I_FRAME = 4 # keyframe
266
- _FRAME_TYPE_AUDIO = 5
267
-
268
- _AUDIO_CODEC_G711A = 1
269
-
270
- _PTZ_DIR_CODES: dict = {
271
- "stop": 0, "up": 1, "down": 2, "left": 3,
272
- "left_up": 4, "left_down": 5, "right": 6,
273
- "right_up": 7, "right_down": 8, "auto": 9,
274
- "set_point": 10, "clear_point": 11, "goto": 12,
275
- "zoom_in": 23, "zoom_out": 24,
276
- }
277
-
278
- # Live-stream resolution (AVIOCTRL_QUALITY_*). The official app's HD/SD toggle
279
- # switches between MAX(1) and MIDDLE(5) (f0.java g3() / LiveFragment d6); we
280
- # mirror those two. Sent as SETSTREAMCTRL (cmd 800) over the active session,
281
- # payload <IB3x> = channel(0) + quality byte (SMsgAVIoctrlSetStreamCtrlReq).
282
- SETSTREAMCTRL_CMD = 800
283
- GETSTREAMCTRL_CMD = 802
284
- _STREAM_QUALITY: dict = {"hd": 1, "sd": 5} # AVIOCTRL_QUALITY_MAX / _MIDDLE
285
239
 
286
240
  @dataclass
287
241
  class VideoFrame:
@@ -7794,7 +7748,7 @@ class CameraMixin:
7794
7748
  certificate_digest as _rr_cert_fp,
7795
7749
  )
7796
7750
 
7797
- def _rr_accept_cam_cert(self, remote_params):
7751
+ def _accept_camera_cert(self, remote_params):
7798
7752
  """Accept camera's actual DTLS cert (echo-reversal fingerprint fix)."""
7799
7753
  try:
7800
7754
  _cam_cert = self._ssl.get_peer_certificate(
@@ -7811,7 +7765,7 @@ class CameraMixin:
7811
7765
  for _rr_tc in pc.getTransceivers():
7812
7766
  _rr_tc.receiver.transport._validate_peer_identity = (
7813
7767
  _types.MethodType(
7814
- _rr_accept_cam_cert, _rr_tc.receiver.transport
7768
+ _accept_camera_cert, _rr_tc.receiver.transport
7815
7769
  )
7816
7770
  )
7817
7771
  _status(
@@ -9108,7 +9062,7 @@ class CameraMixin:
9108
9062
  _p = (-len(_v)) % 4
9109
9063
  return _st_ta.pack('!HH', _t, len(_v)) + _v + b'\x00' * _p
9110
9064
 
9111
- def _mi_ta(_k, _m):
9065
+ def _stun_message_integrity(_k, _m):
9112
9066
  # Patch Length to include the MI attribute (4 hdr + 20 digest = 24)
9113
9067
  _patched = _m[:2] + _st_ta.pack('!H', len(_m) - 20 + 24) + _m[4:]
9114
9068
  return _hm.new(_k, _patched, _ha.sha1).digest()
@@ -9166,7 +9120,7 @@ class CameraMixin:
9166
9120
  + _a(0x0019, b'\x11\x00\x00\x00') # REQUESTED-TRANSPORT = UDP, RFC 5766 §14.7 protocol in MSB
9167
9121
  )
9168
9122
  _h2 = b'\x00\x03' + _st_ta.pack('!H', len(_b2) + 24) + _MAGIC_TA + _tid2
9169
- _b2 += _a(0x0008, _mi_ta(_key_ta, _h2 + _b2)) # MESSAGE-INTEGRITY
9123
+ _b2 += _a(0x0008, _stun_message_integrity(_key_ta, _h2 + _b2)) # MESSAGE-INTEGRITY
9170
9124
  _r2 = b'\x00\x03' + _st_ta.pack('!H', len(_b2)) + _MAGIC_TA + _tid2 + _b2
9171
9125
  try:
9172
9126
  _ta_sock.sendto(_r2, (_ta_host, _ta_port))
@@ -10207,12 +10161,12 @@ class CameraMixin:
10207
10161
  + _struct.pack('!H', _si_xport)
10208
10162
  + _si_xip)
10209
10163
 
10210
- def _si_a(_t, _v):
10164
+ def _build_stun_attr(_t, _v):
10211
10165
  _p = (-len(_v)) % 4
10212
10166
  return (_struct.pack('!HH', _t, len(_v))
10213
10167
  + _v + b'\x00' * _p)
10214
10168
 
10215
- _si_body = _si_a(0x0012, _si_xpa) + _si_a(0x0013, _resp)
10169
+ _si_body = _build_stun_attr(0x0012, _si_xpa) + _build_stun_attr(0x0013, _resp)
10216
10170
  _send_ind = (b'\x00\x16'
10217
10171
  + _struct.pack('!H', len(_si_body))
10218
10172
  + _STUN_MAGIC + os.urandom(12)
@@ -11202,14 +11156,14 @@ class CameraMixin:
11202
11156
  + _st_br.pack('!H', _br_xport2)
11203
11157
  + _br_xip2)
11204
11158
 
11205
- def _br_a(_t, _v):
11159
+ def _build_stun_attr(_t, _v):
11206
11160
  _p = (-len(_v)) % 4
11207
11161
  return (_st_br.pack('!HH', _t, len(_v))
11208
11162
  + _v + b'\x00' * _p)
11209
11163
 
11210
11164
  _br_si_body = (
11211
- _br_a(0x0012, _br_xpa)
11212
- + _br_a(0x0013, _bresp)
11165
+ _build_stun_attr(0x0012, _br_xpa)
11166
+ + _build_stun_attr(0x0013, _bresp)
11213
11167
  )
11214
11168
  _br_send_ind = (
11215
11169
  b'\x00\x16'
@@ -11246,7 +11200,7 @@ class CameraMixin:
11246
11200
  (_br_ci, _br_cp),
11247
11201
  )
11248
11202
  except Exception:
11249
- _LOGGER.debug("camera %s: swallowed exception", '_br_a', exc_info=True)
11203
+ _LOGGER.debug("camera %s: swallowed exception", '_bridge_fn', exc_info=True)
11250
11204
  _status(
11251
11205
  f"bridge: late USE-CANDIDATE sent to"
11252
11206
  f" {len(_bridge_uc_info['cands'])} camera candidate(s)"
@@ -0,0 +1,88 @@
1
+ """Wire/protocol constants for the AiDot camera client.
2
+
3
+ Extracted verbatim from client.py (behavior-preserving). client.py
4
+ re-imports these names, so the public/runtime surface is unchanged.
5
+ """
6
+
7
+ import struct
8
+
9
+ # AppKey from LDSAppOpenSDK CocoaPods docs (kLDSAppOpenSDKKey = "appa070")
10
+ _LEEDARSON_APP_KEY = "appa070"
11
+
12
+ # APP_ID sent in smarthome HTTP headers - value from MainActivity.java SharedPrefs
13
+ # (SharePreferenceUtils.setPrefString(this, "APP_ID", "1392315867093508098")).
14
+ _LEEDARSON_APP_ID = "1392315867093508098"
15
+
16
+ # Camera-specific backend; region prefix mirrors AidotClient._base_url pattern.
17
+ # e.g. "us" -> "https://us-smarthome.arnoo.com:443"
18
+ _SMARTHOME_URL_TEMPLATE = "https://{region}-smarthome.arnoo.com:443"
19
+
20
+ # --------------------------------------------------------------------------- #
21
+ # Playback TCP binary framing constants
22
+ #
23
+ # Wire layout (all big-endian) from RecordVideoEncoder.java, verified against
24
+ # INettyClientInitializer.java Netty params:
25
+ # lengthFieldOffset=14, lengthFieldLength=4, lengthAdjustment=19
26
+ #
27
+ # version(H2) seq(i4) cmd(H2) subcmd(H2) cmdParam(i4) <- 14 bytes
28
+ # payloadLen(i4) <- offset 14
29
+ # timestamp(q8) context(i4) encodeType(b1) result(h2) reserve(i4) <- 19 bytes
30
+ # <payload bytes>
31
+ # Total header = 37 bytes
32
+ # --------------------------------------------------------------------------- #
33
+
34
+ _HDR_FMT = ">HiHHiiqibhi"
35
+ _HDR_SIZE = struct.calcsize(_HDR_FMT) # 37
36
+ _HDR_PREFIX_FMT = ">HiHHii"
37
+ _HDR_PREFIX_SIZE = struct.calcsize(_HDR_PREFIX_FMT) # 18
38
+ _HDR_SUFFIX_FMT = ">qibhi"
39
+ _HDR_SUFFIX_SIZE = struct.calcsize(_HDR_SUFFIX_FMT) # 19
40
+
41
+ assert _HDR_SIZE == 37
42
+ assert _HDR_PREFIX_SIZE == 18
43
+ assert _HDR_SUFFIX_SIZE == 19
44
+
45
+ # Fixed values for all outbound request frames
46
+ _HDR_VERSION = 256 # 0x0100
47
+ _HDR_CONTEXT = 1005
48
+ _HDR_ENC_TYPE = 1
49
+ _HDR_RESULT = 4
50
+ _HDR_RESERVE = 2
51
+
52
+ # TCP command codes from AppCmd.java
53
+ _CMD_LOGIN_REQ = 0x0101
54
+ _CMD_LOGIN_RES = 0x0102
55
+ _CMD_HB_REQ = 0x0105
56
+ _CMD_HB_RES = 0x0106
57
+ _CMD_STREAM_REQ = 0x0107
58
+ _CMD_STREAM_RES = 0x0108
59
+ _CMD_SUBCMD = 0x0001
60
+ _CMD_PARAM = 0x00000002
61
+
62
+ # Video sub-frame header size from LDSPlayer.decodeStream():
63
+ # padding(2) frameType(1) audioCodec(1) timestamp(8) encType(1) payloadLen(4)
64
+ _SF_HDR_SIZE = 17
65
+
66
+ # Frame type values
67
+ _FRAME_TYPE_P_FRAME = 2
68
+ _FRAME_TYPE_B_FRAME = 3
69
+ _FRAME_TYPE_I_FRAME = 4 # keyframe
70
+ _FRAME_TYPE_AUDIO = 5
71
+
72
+ _AUDIO_CODEC_G711A = 1
73
+
74
+ _PTZ_DIR_CODES: dict = {
75
+ "stop": 0, "up": 1, "down": 2, "left": 3,
76
+ "left_up": 4, "left_down": 5, "right": 6,
77
+ "right_up": 7, "right_down": 8, "auto": 9,
78
+ "set_point": 10, "clear_point": 11, "goto": 12,
79
+ "zoom_in": 23, "zoom_out": 24,
80
+ }
81
+
82
+ # Live-stream resolution (AVIOCTRL_QUALITY_*). The official app's HD/SD toggle
83
+ # switches between MAX(1) and MIDDLE(5) (f0.java g3() / LiveFragment d6); we
84
+ # mirror those two. Sent as SETSTREAMCTRL (cmd 800) over the active session,
85
+ # payload <IB3x> = channel(0) + quality byte (SMsgAVIoctrlSetStreamCtrlReq).
86
+ SETSTREAMCTRL_CMD = 800
87
+ GETSTREAMCTRL_CMD = 802
88
+ _STREAM_QUALITY: dict = {"hd": 1, "sd": 5} # AVIOCTRL_QUALITY_MAX / _MIDDLE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.7.10
3
+ Version: 0.7.11
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
@@ -15,6 +15,7 @@ src/aidot/login_const.py
15
15
  src/aidot/py.typed
16
16
  src/aidot/camera/__init__.py
17
17
  src/aidot/camera/client.py
18
+ src/aidot/camera/constants.py
18
19
  src/aidot/camera/lan_control.py
19
20
  src/aidot/models/__init__.py
20
21
  src/aidot/models/device_client_model.py