python-aidot-cameras 0.10.1__tar.gz → 0.10.2__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 (88) hide show
  1. python_aidot_cameras-0.10.2/MANIFEST.in +1 -0
  2. {python_aidot_cameras-0.10.1/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.10.2}/PKG-INFO +9 -9
  3. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/README.md +8 -8
  4. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/client.py +24 -24
  5. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/controls.py +5 -5
  6. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/lan_control.py +2 -2
  7. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/models.py +1 -1
  8. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/protocol.py +5 -5
  9. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/sdes_open.py +46 -46
  10. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/webrtc.py +1 -1
  11. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/webrtc_open.py +51 -51
  12. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/client.py +1 -1
  13. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/const.py +1 -1
  14. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/credentials.py +5 -5
  15. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/pyproject.toml +5 -4
  16. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2/python_aidot_cameras.egg-info}/PKG-INFO +9 -9
  17. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/python_aidot_cameras.egg-info/SOURCES.txt +36 -36
  18. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_backoff.py +1 -1
  19. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_device_login_guard.py +1 -1
  20. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_device_user_info_cache.py +1 -1
  21. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_dtls_skip_signaling_wait.py +1 -1
  22. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_go2rtc.py +1 -1
  23. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_go2rtc_cli.py +1 -1
  24. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_ice_config_cache.py +1 -1
  25. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_keyframe_prompter.py +1 -1
  26. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_lan_control.py +1 -1
  27. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_live_stream_param.py +1 -1
  28. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_narrow_pc_ice.py +3 -3
  29. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_open_gate_delay.py +1 -1
  30. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_persistent_mqtt.py +1 -1
  31. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_persistent_mqtt_robustness.py +1 -1
  32. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_post_merge_hardening.py +1 -1
  33. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_retry_policy.py +1 -1
  34. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_adaptive.py +1 -1
  35. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_fast_liveplay.py +2 -2
  36. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_idle_release.py +4 -4
  37. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_serve_audio.py +1 -1
  38. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_serve_cmd.py +1 -1
  39. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_security_hardening.py +3 -3
  40. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_serve_relay.py +1 -1
  41. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_session_stats.py +1 -1
  42. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_stream_idle.py +1 -1
  43. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_stream_teardown.py +1 -1
  44. python_aidot_cameras-0.10.1/MANIFEST.in +0 -1
  45. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/LICENSE +0 -0
  46. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/__init__.py +0 -0
  47. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/__main__.py +0 -0
  48. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/aes_utils.py +0 -0
  49. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/__init__.py +0 -0
  50. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/constants.py +0 -0
  51. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/go2rtc.py +0 -0
  52. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/playback.py +0 -0
  53. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/sdes.py +0 -0
  54. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/camera/tutk.py +0 -0
  55. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/device_client.py +0 -0
  56. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/discover.py +0 -0
  57. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/exceptions.py +0 -0
  58. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/g711.py +0 -0
  59. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/login_const.py +0 -0
  60. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/models/__init__.py +0 -0
  61. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/models/device_client_model.py +0 -0
  62. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/models/device_model.py +0 -0
  63. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/models/discover_model.py +0 -0
  64. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/aidot/py.typed +0 -0
  65. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  66. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  67. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/python_aidot_cameras.egg-info/requires.txt +0 -0
  68. {python_aidot_cameras-0.10.1/src → python_aidot_cameras-0.10.2}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  69. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/setup.cfg +0 -0
  70. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_aioice_compat.py +0 -0
  71. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_alarm_event.py +0 -0
  72. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_dtls_not_ready_burst.py +0 -0
  73. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_dtls_pinning.py +0 -0
  74. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_egress_guard.py +0 -0
  75. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_highport_nomination.py +0 -0
  76. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_motion_poll.py +0 -0
  77. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_no_undefined_names.py +0 -0
  78. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_playback_tls.py +0 -0
  79. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_echo_wait_timeout.py +0 -0
  80. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_sprop.py +0 -0
  81. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_talk.py +0 -0
  82. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_sdes_watchdog.py +0 -0
  83. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_speak.py +0 -0
  84. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_stream_cap.py +0 -0
  85. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_talk.py +0 -0
  86. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_terminal_ack.py +0 -0
  87. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_token_refresh.py +0 -0
  88. {python_aidot_cameras-0.10.1 → python_aidot_cameras-0.10.2}/tests/test_wait_or_event.py +0 -0
@@ -0,0 +1 @@
1
+ include aidot/py.typed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.10.1
3
+ Version: 0.10.2
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
@@ -49,9 +49,9 @@ on this library.
49
49
 
50
50
  The streaming transport is auto-selected per camera from its model id:
51
51
 
52
- - **A000088** (M3 Pro) DTLS-SRTP, wired/mains.
53
- - **A001513** ("L2") SDES-SRTP, **battery** (woken on demand; validated end-to-end).
54
- - **A001064** (PTZ) SDES-SRTP, wired/mains (role-reversal handshake).
52
+ - **A000088** (M3 Pro) - DTLS-SRTP, wired/mains.
53
+ - **A001513** ("L2") - SDES-SRTP, **battery** (woken on demand; validated end-to-end).
54
+ - **A001064** (PTZ) - SDES-SRTP, wired/mains (role-reversal handshake).
55
55
 
56
56
  Other battery models (A001108, A001360) are recognized in code with the same
57
57
  battery handling. See [`docs/CAMERAS.md`](docs/CAMERAS.md#supported-cameras) for
@@ -68,7 +68,7 @@ pip install python-aidot-cameras
68
68
  pip install python-aidot-cameras[webrtc]
69
69
  ```
70
70
 
71
- `[webrtc]` pulls in the extra dependencies (aiortc, av, ) needed for live
71
+ `[webrtc]` pulls in the extra dependencies (aiortc, av, ...) needed for live
72
72
  streaming, snapshots, and two-way audio. Without it you still get lights plus
73
73
  the camera cloud/control APIs, but not live media.
74
74
 
@@ -132,7 +132,7 @@ The library reads the following environment variables.
132
132
  ### Credentials
133
133
 
134
134
  Used by the credential helper (`aidot.credentials`); they take priority over any
135
- stored credentials file. See [`src/aidot/credentials.py`](src/aidot/credentials.py).
135
+ stored credentials file. See [`aidot/credentials.py`](aidot/credentials.py).
136
136
 
137
137
  | Variable | Purpose | Default |
138
138
  | --- | --- | --- |
@@ -152,12 +152,12 @@ audio, idle release, the sprop cache path) are documented in
152
152
  | --- | --- | --- |
153
153
  | `AIDOT_MAX_CONCURRENT_OPENS` | Caps how many stream opens run concurrently across all cameras. | `2` |
154
154
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
155
- | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only off-subnet/strict-NAT viewers must leave it off. | unset (off) |
156
- | `AIDOT_SDES_SKIP_TURN_PREALLOC` | Skip the SDES TURN relay pre-allocation (~23 s of cold-start latency) so signaling goes straight out with the host candidate. Faster on a LAN, at the cost of no relay fallback for a camera on a different segment / behind strict NAT. Experimental, opt-in (truthy = `1`/`true`/`yes`/`on`). | unset (off) |
155
+ | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
156
+ | `AIDOT_SDES_SKIP_TURN_PREALLOC` | Skip the SDES TURN relay pre-allocation (~2-3 s of cold-start latency) so signaling goes straight out with the host candidate. Faster on a LAN, at the cost of no relay fallback for a camera on a different segment / behind strict NAT. Experimental, opt-in (truthy = `1`/`true`/`yes`/`on`). | unset (off) |
157
157
  | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. | unset (off) |
158
158
  | `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
159
159
  | `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
160
- | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soak cut SDES NO_MEDIA ~57%→~19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
160
+ | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soak cut SDES NO_MEDIA ~57%->~19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
161
161
  | `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
162
162
  | `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
163
163
 
@@ -21,9 +21,9 @@ on this library.
21
21
 
22
22
  The streaming transport is auto-selected per camera from its model id:
23
23
 
24
- - **A000088** (M3 Pro) DTLS-SRTP, wired/mains.
25
- - **A001513** ("L2") SDES-SRTP, **battery** (woken on demand; validated end-to-end).
26
- - **A001064** (PTZ) SDES-SRTP, wired/mains (role-reversal handshake).
24
+ - **A000088** (M3 Pro) - DTLS-SRTP, wired/mains.
25
+ - **A001513** ("L2") - SDES-SRTP, **battery** (woken on demand; validated end-to-end).
26
+ - **A001064** (PTZ) - SDES-SRTP, wired/mains (role-reversal handshake).
27
27
 
28
28
  Other battery models (A001108, A001360) are recognized in code with the same
29
29
  battery handling. See [`docs/CAMERAS.md`](docs/CAMERAS.md#supported-cameras) for
@@ -40,7 +40,7 @@ pip install python-aidot-cameras
40
40
  pip install python-aidot-cameras[webrtc]
41
41
  ```
42
42
 
43
- `[webrtc]` pulls in the extra dependencies (aiortc, av, ) needed for live
43
+ `[webrtc]` pulls in the extra dependencies (aiortc, av, ...) needed for live
44
44
  streaming, snapshots, and two-way audio. Without it you still get lights plus
45
45
  the camera cloud/control APIs, but not live media.
46
46
 
@@ -104,7 +104,7 @@ The library reads the following environment variables.
104
104
  ### Credentials
105
105
 
106
106
  Used by the credential helper (`aidot.credentials`); they take priority over any
107
- stored credentials file. See [`src/aidot/credentials.py`](src/aidot/credentials.py).
107
+ stored credentials file. See [`aidot/credentials.py`](aidot/credentials.py).
108
108
 
109
109
  | Variable | Purpose | Default |
110
110
  | --- | --- | --- |
@@ -124,12 +124,12 @@ audio, idle release, the sprop cache path) are documented in
124
124
  | --- | --- | --- |
125
125
  | `AIDOT_MAX_CONCURRENT_OPENS` | Caps how many stream opens run concurrently across all cameras. | `2` |
126
126
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
127
- | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only off-subnet/strict-NAT viewers must leave it off. | unset (off) |
128
- | `AIDOT_SDES_SKIP_TURN_PREALLOC` | Skip the SDES TURN relay pre-allocation (~23 s of cold-start latency) so signaling goes straight out with the host candidate. Faster on a LAN, at the cost of no relay fallback for a camera on a different segment / behind strict NAT. Experimental, opt-in (truthy = `1`/`true`/`yes`/`on`). | unset (off) |
127
+ | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
128
+ | `AIDOT_SDES_SKIP_TURN_PREALLOC` | Skip the SDES TURN relay pre-allocation (~2-3 s of cold-start latency) so signaling goes straight out with the host candidate. Faster on a LAN, at the cost of no relay fallback for a camera on a different segment / behind strict NAT. Experimental, opt-in (truthy = `1`/`true`/`yes`/`on`). | unset (off) |
129
129
  | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. | unset (off) |
130
130
  | `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
131
131
  | `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
132
- | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soak cut SDES NO_MEDIA ~57%→~19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
132
+ | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soak cut SDES NO_MEDIA ~57%->~19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
133
133
  | `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
134
134
  | `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
135
135
 
@@ -363,10 +363,10 @@ def _build_sdes_serve_cmd(
363
363
  # Use DeviceClient.async_open_live_stream() to obtain an instance.
364
364
  #
365
365
  # Protocol source: classes.jar.decompiled.zip / TutkManager.java
366
- # IOTC_Connect_ByUID_Parallel(uid, sid) nSID
367
- # avClientStart2(nSID, "admin", "admin123", ...) avIndex
368
- # avSendIOCtrl(avIndex, 511, ...) start video (IOTYPE_USER_IPCAM_START)
369
- # avRecvFrameData2(avIndex, ...) frame data loop
366
+ # IOTC_Connect_ByUID_Parallel(uid, sid) -> nSID
367
+ # avClientStart2(nSID, "admin", "admin123", ...) -> avIndex
368
+ # avSendIOCtrl(avIndex, 511, ...) -> start video (IOTYPE_USER_IPCAM_START)
369
+ # avRecvFrameData2(avIndex, ...) -> frame data loop
370
370
  #
371
371
  # Requires: libIOTCAPIs.so + libAVAPIs.so from the TUTK SDK.
372
372
  # Obtain them from the TUTK SDK distribution or an extracted AiDot APK.
@@ -380,7 +380,7 @@ def _build_sdes_serve_cmd(
380
380
  # Manages a single live-stream TCP session for a Leedarson/AiDot camera.
381
381
  # Use DeviceClient.async_open_live_stream() to obtain an instance.
382
382
  #
383
- # Protocol source: iOS LDSXplayer startRealPlay LDSTCPManager
383
+ # Protocol source: iOS LDSXplayer startRealPlay -> LDSTCPManager
384
384
  # connectHost:port:sessionId:aesKey:heartbeat:msg:cmd:subCmd:cmdParam:tls:
385
385
  #
386
386
  # Wire format: same 37-byte header + payload as CloudPlaybackSession, but:
@@ -596,7 +596,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
596
596
  self._raw_device: dict = device
597
597
 
598
598
  # Seed camera/diagnostic status from the cloud device "properties" payload
599
- # (Battery_remaining, Occupancy, SDcardStatus, MotionDetection_*, ). This
599
+ # (Battery_remaining, Occupancy, SDcardStatus, MotionDetection_*, ...). This
600
600
  # is the authoritative, always-current source the official app itself reads
601
601
  # - cameras do not push these reliably over MQTT - so sensors populate
602
602
  # immediately on load, before any poll. No-op for devices without it.
@@ -723,7 +723,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
723
723
 
724
724
  The device-list payload carries every camera attribute under
725
725
  ``properties`` (battery, SD-card, occupancy, motion/night-vision
726
- settings, ) plus a top-level ``online`` flag. This is the reliable,
726
+ settings, ...) plus a top-level ``online`` flag. This is the reliable,
727
727
  always-current source the official app reads, so HA populates camera
728
728
  sensors and control-entity states from it instead of an MQTT push the
729
729
  camera never sends. Returns the updated status.
@@ -1249,7 +1249,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
1249
1249
 
1250
1250
  Tries several sources in order:
1251
1251
  1. POST /v21/devices/batchGetDeviceUserInfo (AiDot platform API)
1252
- 2. POST /v5/deviceController/getP2pId?deviceId=… (Leedarson smarthome)
1252
+ 2. POST /v5/deviceController/getP2pId?deviceId=... (Leedarson smarthome)
1253
1253
  3. AiDot v32 IPC device detail (fallback)
1254
1254
  """
1255
1255
  import aiohttp
@@ -1517,7 +1517,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
1517
1517
  "payload": inner,
1518
1518
  })
1519
1519
  pub_topic = f"iot/v1/c/{device_id}/device/setDevAttrReq"
1520
- _LOGGER.info("setDevAttrReq: %s=%s %s seq=%s", attr, value, device_id, seq)
1520
+ _LOGGER.info("setDevAttrReq: %s=%s -> %s seq=%s", attr, value, device_id, seq)
1521
1521
  ok = await self._mqtt_device_cmd(
1522
1522
  pub_topic, payload, timeout=timeout, ack_keyword="setDevAttr")
1523
1523
  if ok:
@@ -1562,7 +1562,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
1562
1562
  },
1563
1563
  })
1564
1564
  pub_topic = f"iot/v1/s/{user_id}/device/devActionReq"
1565
- _LOGGER.info("devActionReq: %s %s %s", action, params, device_id)
1565
+ _LOGGER.info("devActionReq: %s %s -> %s", action, params, device_id)
1566
1566
  return await self._mqtt_device_cmd(
1567
1567
  pub_topic, payload, timeout=timeout, ack_keyword="devAction")
1568
1568
 
@@ -1927,7 +1927,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
1927
1927
  """
1928
1928
  import asyncio as _asyncio
1929
1929
 
1930
- # ── SDES path: stream briefly to a temp TS file, extract one JPEG ──── #
1930
+ # -- SDES path: stream briefly to a temp TS file, extract one JPEG ---- #
1931
1931
  if self.is_sdes_camera:
1932
1932
  import os as _os
1933
1933
  import tempfile as _tf
@@ -2009,7 +2009,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
2009
2009
  except Exception:
2010
2010
  _LOGGER.debug("camera %s: swallowed exception in %s", getattr(self, "device_id", "?"), 'async_snapshot', exc_info=True)
2011
2011
 
2012
- # ── DTLS path: on_frame callback delivers frames from aiortc ─────── #
2012
+ # -- DTLS path: on_frame callback delivers frames from aiortc ------- #
2013
2013
  frame_event = _asyncio.Event()
2014
2014
  captured: list = [None] # stores PIL Image or ndarray once decoded
2015
2015
 
@@ -2382,7 +2382,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
2382
2382
  open_timeout: float = 30.0,
2383
2383
  retries: int = 3,
2384
2384
  ) -> bool:
2385
- """Play viewercamera (push-to-talk / announce) audio through the speaker.
2385
+ """Play viewer->camera (push-to-talk / announce) audio through the speaker.
2386
2386
 
2387
2387
  ``pcm_provider()`` is polled every 20 ms and must return 320 bytes of s16le
2388
2388
  PCM @ 8 kHz mono (a short/empty frame is padded to silence) and ``None``
@@ -2578,13 +2578,13 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
2578
2578
  break
2579
2579
  if _idle_on and _serve_port is not None:
2580
2580
  _present = self._sdes_serve_consumer_present(_serve_port)
2581
- if _present: # True a viewer is pulling; stay alive
2581
+ if _present: # True -> a viewer is pulling; stay alive
2582
2582
  _last_consumer = time.monotonic()
2583
2583
  elif _idle_release_due(_present, _last_consumer,
2584
2584
  time.monotonic(), _idle_secs):
2585
2585
  _idle_release = True
2586
2586
  break
2587
- # _present is None (unreadable table) don't release
2587
+ # _present is None (unreadable table) -> don't release
2588
2588
  except asyncio.CancelledError:
2589
2589
  _done.cancel()
2590
2590
  self._stream_session = None
@@ -2836,7 +2836,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
2836
2836
  """Serve idle-release window (seconds). A per-camera ``stream_idle_s``
2837
2837
  (set via start_keepalive) overrides the ``AIDOT_STREAM_IDLE_S`` env
2838
2838
  default of 120 s. <= 0 means *never* idle-release (keep the warm session
2839
- for instant re-views sensible for mains cameras, which have no battery
2839
+ for instant re-views - sensible for mains cameras, which have no battery
2840
2840
  cost; note it holds a concurrent-stream slot for the camera's lifetime)."""
2841
2841
  opt = getattr(self, "_stream_idle_opt", None)
2842
2842
  if opt is not None:
@@ -3124,7 +3124,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
3124
3124
  import queue as _queue
3125
3125
  serve_url = self._keepalive_rtsp_url
3126
3126
  # APK parity: the official app gates re-connects to ~15 s (f0.java I1=15000)
3127
- # and never hammers. We previously floored at 5 s and a partial-success
3127
+ # and never hammers. We previously floored at 5 s and a partial-success ->
3128
3128
  # reset path could drop the effective spacing even lower, pounding a flaky
3129
3129
  # A000088 camera fast enough to wedge its DTLS stack (ICE completes but DTLS
3130
3130
  # never fires until a power-cycle). Floor the backoff at the 15 s gate and,
@@ -3233,7 +3233,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
3233
3233
  elif _t - _disc_since[0] >= _DISC_DEBOUNCE:
3234
3234
  return True
3235
3235
  else:
3236
- _disc_since[0] = None # connected/connecting reset debounce
3236
+ _disc_since[0] = None # connected/connecting -> reset debounce
3237
3237
  return False
3238
3238
 
3239
3239
  for _ in range(40): # ~12s for the receiver/track(s) to exist
@@ -3436,7 +3436,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
3436
3436
  _LOGGER.warning("DTLS serve: ffmpeg launch failed: %s", exc)
3437
3437
  return None
3438
3438
 
3439
- # ── PTZ physical pan/tilt (A001064) ─────────────────────────────────── #
3439
+ # -- PTZ physical pan/tilt (A001064) ----------------------------------- #
3440
3440
  # Uses IOCtrl cmd=4097 (IOTYPE_USER_IPCAM_PTZ_COMMAND) - NOT MQTT.
3441
3441
  # DTLS path: sent via WebRTC DataChannel. SDES path: sent via encrypted
3442
3442
  # SCTP cmd_chan. Requires an active stream session (_stream_session).
@@ -3573,11 +3573,11 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
3573
3573
  # Returns a running TutkStreamSession, or None on failure.
3574
3574
  #
3575
3575
  # Protocol: TUTK IOTC P2P (confirmed from classes.jar.decompiled.zip).
3576
- # p2pId (TUTK UID) POST /v21/devices/batchGetDeviceUserInfo
3577
- # IOTC_Connect_ByUID_Parallel(uid) nSID
3578
- # avClientStart2(nSID, "admin", "admin123") avIndex
3579
- # avSendIOCtrl(avIndex, 511, ...) start video stream
3580
- # avRecvFrameData2(avIndex, ...) frame loop
3576
+ # p2pId (TUTK UID) <- POST /v21/devices/batchGetDeviceUserInfo
3577
+ # IOTC_Connect_ByUID_Parallel(uid) -> nSID
3578
+ # avClientStart2(nSID, "admin", "admin123") -> avIndex
3579
+ # avSendIOCtrl(avIndex, 511, ...) -> start video stream
3580
+ # avRecvFrameData2(avIndex, ...) -> frame loop
3581
3581
  #
3582
3582
  # Requires libIOTCAPIs.so + libAVAPIs.so from the TUTK SDK.
3583
3583
 
@@ -19,7 +19,7 @@ class _CameraControlsMixin:
19
19
 
20
20
  async def async_set_motion_detection(self, enabled: bool) -> bool:
21
21
  """Enable or disable the camera's motion detection."""
22
- # MotionDetection_Enable is read as getString() in IpcServiceImpl use str
22
+ # MotionDetection_Enable is read as getString() in IpcServiceImpl -> use str
23
23
  return await self.async_set_device_attribute(
24
24
  "MotionDetection_Enable", "1" if enabled else "0")
25
25
 
@@ -108,7 +108,7 @@ class _CameraControlsMixin:
108
108
  speed: 0-255, default 4 (matches app default)
109
109
  preset: preset slot for "goto" command
110
110
 
111
- Source: a.java d1() / f0.java A2() avSendIOCtrl(4097, 8B payload)
111
+ Source: a.java d1() / f0.java A2() -> avSendIOCtrl(4097, 8B payload)
112
112
  Payload: [direction_code, speed, preset, 0, 0, 0, 0, 0]
113
113
 
114
114
  Direction codes (AVIOCTRLDEFs.java):
@@ -128,7 +128,7 @@ class _CameraControlsMixin:
128
128
  return False
129
129
  ok = session._avio_cmd(4097, payload)
130
130
  _LOGGER.debug(
131
- "PTZ %s (code=%d speed=%d preset=%d) %s",
131
+ "PTZ %s (code=%d speed=%d preset=%d) -> %s",
132
132
  direction, code, speed, preset, "sent" if ok else "no channel yet",
133
133
  )
134
134
  return ok
@@ -144,7 +144,7 @@ class _CameraControlsMixin:
144
144
  mirroring the official app's HD/SD toggle. Rides the active stream
145
145
  session (SETSTREAMCTRL=800), so the camera must be streaming.
146
146
 
147
- Source: f0.java g3() X2(800, SetStreamCtrlReq.parseContent(0, quality)).
147
+ Source: f0.java g3() -> X2(800, SetStreamCtrlReq.parseContent(0, quality)).
148
148
  Payload <IB3x> = channel(0) + quality byte + 3 reserved.
149
149
  """
150
150
  q = _STREAM_QUALITY.get(quality.lower())
@@ -158,7 +158,7 @@ class _CameraControlsMixin:
158
158
  return False
159
159
  ok = session._avio_cmd(SETSTREAMCTRL_CMD, payload)
160
160
  _LOGGER.debug(
161
- "set resolution %s (quality=%d) %s",
161
+ "set resolution %s (quality=%d) -> %s",
162
162
  quality, q, "sent" if ok else "no channel yet",
163
163
  )
164
164
  return ok
@@ -11,7 +11,7 @@ Design constraints (validated against LK.IPC.A000088 firmware):
11
11
  AES-ECB-encrypted JSON body, keyed by the device's 16-char ``aesKey``.
12
12
  * It is **single-session**: a second ``loginReq`` evicts the first. So this client
13
13
  never holds a socket open - every operation is a short-lived
14
- *connect login command(s) close*, serialized by a per-camera lock.
14
+ *connect -> login -> command(s) -> close*, serialized by a per-camera lock.
15
15
  * The camera acks changes with ``setDevAttrResp`` but emits no ``setDevAttrNotif``,
16
16
  so there is no push; status is obtained by polling :meth:`async_get_attributes`.
17
17
  * Only cameras that advertise ``localCtrFlag == 1`` on unicast discovery AND are
@@ -89,7 +89,7 @@ async def discover_unicast(ip: str, timeout: float = 2.0) -> Optional[dict]:
89
89
 
90
90
  Cameras ignore the broadcast sweep but answer a unicast probe. Returns the
91
91
  ``payload`` dict (``devId``, ``mac``, ``productModel``, ``lanMode``,
92
- ``localCtrFlag`` ) or ``None`` if nothing answered.
92
+ ``localCtrFlag`` ...) or ``None`` if nothing answered.
93
93
  """
94
94
  msg = {
95
95
  "protocolVer": "2.0.0",
@@ -138,7 +138,7 @@ class CameraStatusData(DeviceStatusData):
138
138
 
139
139
  Accepts either a setDevAttrNotif ``attr`` dict or a cloud device
140
140
  ``properties`` dict - both share the same camera attribute keys
141
- (Battery_remaining, Occupancy, SDcardStatus, MotionDetection_*, ).
141
+ (Battery_remaining, Occupancy, SDcardStatus, MotionDetection_*, ...).
142
142
  """
143
143
  self.update({
144
144
  k: v for k, v in attrs.items()
@@ -328,7 +328,7 @@ def _compress_sdp_for_camera(sdp: str) -> str:
328
328
 
329
329
 
330
330
  def _make_talk_audio_track(pcm_provider: Callable[[], "Optional[bytes]"]):
331
- """Build an aiortc MediaStreamTrack that emits viewercamera talk audio.
331
+ """Build an aiortc MediaStreamTrack that emits viewer->camera talk audio.
332
332
 
333
333
  Mirrors the official app (f0.java:695): a real audio track is present on the
334
334
  PeerConnection from the start, so the camera sees a genuine sender. The
@@ -1185,7 +1185,7 @@ def _mqtt_session_sync(
1185
1185
  conn_ev = threading.Event()
1186
1186
  status = {"connected": False, "rc": None, "rc_str": "", "error": None, "log": []}
1187
1187
 
1188
- # Build client - handle paho 2.0 (VERSION2) and <2.0
1188
+ # Build client - handle paho >=2.0 (VERSION2) and <2.0
1189
1189
  try:
1190
1190
  client = _paho.Client(
1191
1191
  callback_api_version=_paho.CallbackAPIVersion.VERSION2,
@@ -1203,7 +1203,7 @@ def _mqtt_session_sync(
1203
1203
  client.tls_set_context(ctx)
1204
1204
 
1205
1205
  def _on_connect(c, ud, flags, reason_code, props=None):
1206
- # paho 2 passes ReasonCode; paho <2 passes int
1206
+ # paho >=2 passes ReasonCode; paho <2 passes int
1207
1207
  try:
1208
1208
  rc = int(reason_code)
1209
1209
  except (TypeError, ValueError):
@@ -1742,7 +1742,7 @@ def _upgrade_sctp(sdp: str) -> str:
1742
1742
  RFC 8841 (and cameras / modern browsers) expect:
1743
1743
  m=application 9 UDP/DTLS/SCTP webrtc-datachannel
1744
1744
  a=sctp-port:5000
1745
- a=max-message-size:65536 required by RFC 8841 §4.3.1
1745
+ a=max-message-size:65536 <- required by RFC 8841 section 4.3.1
1746
1746
  """
1747
1747
  import re as _re
1748
1748
  out = []
@@ -1759,7 +1759,7 @@ def _upgrade_sctp(sdp: str) -> str:
1759
1759
  def _normalize_bundle_ice_credentials(sdp: str) -> str:
1760
1760
  """Unify all m-section ICE credentials to match the BUNDLE master (mid:0).
1761
1761
 
1762
- RFC 8843 §7.1.3 requires all bundled m-sections to carry the same
1762
+ RFC 8843 section 7.1.3 requires all bundled m-sections to carry the same
1763
1763
  ice-ufrag and ice-pwd. aiortc generates a separate ICETransport per
1764
1764
  transceiver, giving each a unique credential pair. Cameras that
1765
1765
  validate this requirement silently reject offers with mismatched