python-aidot-cameras 0.7.29__tar.gz → 0.7.30__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 (61) hide show
  1. {python_aidot_cameras-0.7.29/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.7.30}/PKG-INFO +2 -2
  2. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/README.md +1 -1
  3. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/pyproject.toml +1 -1
  4. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/client.py +12 -7
  5. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30/src/python_aidot_cameras.egg-info}/PKG-INFO +2 -2
  6. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_persistent_mqtt.py +12 -10
  7. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/LICENSE +0 -0
  8. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/MANIFEST.in +0 -0
  9. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/setup.cfg +0 -0
  10. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/__init__.py +0 -0
  11. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/aes_utils.py +0 -0
  12. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/__init__.py +0 -0
  13. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/constants.py +0 -0
  14. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/controls.py +0 -0
  15. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/go2rtc.py +0 -0
  16. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/lan_control.py +0 -0
  17. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/models.py +0 -0
  18. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/playback.py +0 -0
  19. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/protocol.py +0 -0
  20. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/sdes.py +0 -0
  21. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/tutk.py +0 -0
  22. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/camera/webrtc.py +0 -0
  23. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/client.py +0 -0
  24. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/const.py +0 -0
  25. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/credentials.py +0 -0
  26. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/device_client.py +0 -0
  27. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/discover.py +0 -0
  28. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/exceptions.py +0 -0
  29. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/g711.py +0 -0
  30. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/login_const.py +0 -0
  31. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/models/__init__.py +0 -0
  32. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/models/device_client_model.py +0 -0
  33. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/models/device_model.py +0 -0
  34. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/models/discover_model.py +0 -0
  35. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/aidot/py.typed +0 -0
  36. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
  37. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  38. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
  39. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
  40. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_alarm_event.py +0 -0
  41. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_backoff.py +0 -0
  42. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_go2rtc.py +0 -0
  43. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_highport_nomination.py +0 -0
  44. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_lan_control.py +0 -0
  45. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_live_stream_param.py +0 -0
  46. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_motion_poll.py +0 -0
  47. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_no_undefined_names.py +0 -0
  48. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_post_merge_hardening.py +0 -0
  49. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_sdes_adaptive.py +0 -0
  50. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_sdes_fast_liveplay.py +0 -0
  51. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_sdes_idle_release.py +0 -0
  52. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_sdes_sprop.py +0 -0
  53. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_sdes_talk.py +0 -0
  54. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_sdes_watchdog.py +0 -0
  55. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_serve_relay.py +0 -0
  56. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_speak.py +0 -0
  57. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_stream_cap.py +0 -0
  58. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_stream_idle.py +0 -0
  59. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_talk.py +0 -0
  60. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/tests/test_terminal_ack.py +0 -0
  61. {python_aidot_cameras-0.7.29 → python_aidot_cameras-0.7.30}/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.29
3
+ Version: 0.7.30
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
@@ -122,7 +122,7 @@ chosen to work out of the box; override only when tuning.
122
122
  | `AIDOT_SPROP_DIR` | Directory where captured SPS/PPS (sprop) parameter sets are cached. Set this to a writable path (e.g. for Home Assistant) if the default location is read-only. | `<package dir>` |
123
123
  | `AIDOT_DISABLE_HIGHPORT_FIX` | If set (any value), disables the DTLS high-port `USE-CANDIDATE` nomination fix and falls back to upstream aioice behavior (used to measure the baseline connect rate). | unset (fix enabled) |
124
124
  | `AIDOT_FAST_CONNECT` | Enables LAN-direct "fast connect" mode (STUN-only, skips several cloud signaling waits) when set to a truthy value. | unset (off) |
125
- | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for device commands and attribute fetches (matching the official app) instead of connecting per operation, cutting cloud connect churn. Truthy value enables. The stream-open path is unaffected. | unset (off) |
125
+ | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for device commands, attribute fetches, AND stream-open signaling (matching the official app) instead of connecting per operation, cutting cloud connect churn. **On by default** (the app's behaviour; live soak cut SDES NO_MEDIA ~57%→~19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
126
126
  | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first (skip livePlay waits + TURN relay pre-alloc) 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 for a camera. Truthy value enables. | unset (off) |
127
127
  | `AIDOT_SDES_FAST_LIVEPLAY` | Truthy value skips the `livePlayResp` blocking wait for eligible SDES cameras (~4.5 s faster cold start), while keeping the full ICE/SCTP handshake. Role-reversal models (A001064 PTZ) are always excluded. Soak-validated; opt-in. | unset (off) |
128
128
  | `AIDOT_SERVE_RELAY` | Holds 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) |
@@ -95,7 +95,7 @@ chosen to work out of the box; override only when tuning.
95
95
  | `AIDOT_SPROP_DIR` | Directory where captured SPS/PPS (sprop) parameter sets are cached. Set this to a writable path (e.g. for Home Assistant) if the default location is read-only. | `<package dir>` |
96
96
  | `AIDOT_DISABLE_HIGHPORT_FIX` | If set (any value), disables the DTLS high-port `USE-CANDIDATE` nomination fix and falls back to upstream aioice behavior (used to measure the baseline connect rate). | unset (fix enabled) |
97
97
  | `AIDOT_FAST_CONNECT` | Enables LAN-direct "fast connect" mode (STUN-only, skips several cloud signaling waits) when set to a truthy value. | unset (off) |
98
- | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for device commands and attribute fetches (matching the official app) instead of connecting per operation, cutting cloud connect churn. Truthy value enables. The stream-open path is unaffected. | unset (off) |
98
+ | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for device commands, attribute fetches, AND stream-open signaling (matching the official app) instead of connecting per operation, cutting cloud connect churn. **On by default** (the app's behaviour; live soak cut SDES NO_MEDIA ~57%→~19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
99
99
  | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first (skip livePlay waits + TURN relay pre-alloc) 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 for a camera. Truthy value enables. | unset (off) |
100
100
  | `AIDOT_SDES_FAST_LIVEPLAY` | Truthy value skips the `livePlayResp` blocking wait for eligible SDES cameras (~4.5 s faster cold start), while keeping the full ICE/SCTP handshake. Role-reversal models (A001064 PTZ) are always excluded. Soak-validated; opt-in. | unset (off) |
101
101
  | `AIDOT_SERVE_RELAY` | Holds 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) |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.7.29"
7
+ version = "0.7.30"
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"
@@ -2682,16 +2682,21 @@ class CameraMixin(_CameraControlsMixin):
2682
2682
  "1", "true", "yes", "on")
2683
2683
 
2684
2684
  def _resolve_persistent_mqtt(self) -> bool:
2685
- """Whether commands + attribute fetches reuse ONE account-level persistent
2686
- MQTT connection (matching the app's LDSBaseMqttServiceImpl) instead of
2687
- connecting per op. Opt-in (``AIDOT_PERSISTENT_MQTT`` env / per-camera
2688
- ``_persistent_mqtt_opt``), default off while it's validated. Phase 1: the
2689
- stream-open path is unchanged (it uses session client_ids)."""
2685
+ """Whether commands, attribute fetches, AND stream-open signaling reuse ONE
2686
+ account-level persistent MQTT connection (matching the app's
2687
+ LDSBaseMqttServiceImpl) instead of connecting per op.
2688
+
2689
+ **Default ON** (2026-06-17): this is exactly how the official app behaves -
2690
+ one persistent connection per login session - and a live soak showed it
2691
+ cuts SDES NO_MEDIA from ~57% to ~19% (no regression). It is also safer
2692
+ than connect-per-op, which can collide on the single authorized client_id.
2693
+ Disable via ``AIDOT_PERSISTENT_MQTT`` in {0,false,no,off} or per-camera
2694
+ ``_persistent_mqtt_opt=False`` (the explicit opt always wins)."""
2690
2695
  opt = getattr(self, "_persistent_mqtt_opt", None)
2691
2696
  if opt is not None:
2692
2697
  return bool(opt)
2693
- return os.environ.get("AIDOT_PERSISTENT_MQTT", "").strip().lower() in (
2694
- "1", "true", "yes", "on")
2698
+ return os.environ.get("AIDOT_PERSISTENT_MQTT", "").strip().lower() not in (
2699
+ "0", "false", "no", "off")
2695
2700
 
2696
2701
  async def _get_persistent_mqtt(self):
2697
2702
  """Get-or-create the account-shared ``_PersistentMqtt`` (one per account,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.7.29
3
+ Version: 0.7.30
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
@@ -122,7 +122,7 @@ chosen to work out of the box; override only when tuning.
122
122
  | `AIDOT_SPROP_DIR` | Directory where captured SPS/PPS (sprop) parameter sets are cached. Set this to a writable path (e.g. for Home Assistant) if the default location is read-only. | `<package dir>` |
123
123
  | `AIDOT_DISABLE_HIGHPORT_FIX` | If set (any value), disables the DTLS high-port `USE-CANDIDATE` nomination fix and falls back to upstream aioice behavior (used to measure the baseline connect rate). | unset (fix enabled) |
124
124
  | `AIDOT_FAST_CONNECT` | Enables LAN-direct "fast connect" mode (STUN-only, skips several cloud signaling waits) when set to a truthy value. | unset (off) |
125
- | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for device commands and attribute fetches (matching the official app) instead of connecting per operation, cutting cloud connect churn. Truthy value enables. The stream-open path is unaffected. | unset (off) |
125
+ | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for device commands, attribute fetches, AND stream-open signaling (matching the official app) instead of connecting per operation, cutting cloud connect churn. **On by default** (the app's behaviour; live soak cut SDES NO_MEDIA ~57%→~19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
126
126
  | `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first (skip livePlay waits + TURN relay pre-alloc) 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 for a camera. Truthy value enables. | unset (off) |
127
127
  | `AIDOT_SDES_FAST_LIVEPLAY` | Truthy value skips the `livePlayResp` blocking wait for eligible SDES cameras (~4.5 s faster cold start), while keeping the full ICE/SCTP handshake. Role-reversal models (A001064 PTZ) are always excluded. Soak-validated; opt-in. | unset (off) |
128
128
  | `AIDOT_SERVE_RELAY` | Holds 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) |
@@ -21,25 +21,27 @@ def _cam():
21
21
  return _CAM.__new__(_CAM)
22
22
 
23
23
 
24
- def test_default_off(monkeypatch):
24
+ def test_default_on(monkeypatch):
25
+ # default ON (matches the app's single persistent connection)
25
26
  monkeypatch.delenv("AIDOT_PERSISTENT_MQTT", raising=False)
26
- assert _cam()._resolve_persistent_mqtt() is False
27
+ assert _cam()._resolve_persistent_mqtt() is True
27
28
 
28
29
 
29
- def test_env_enables(monkeypatch):
30
- for val in ("1", "true", "TRUE", "yes", "on", " On "):
30
+ def test_env_disables(monkeypatch):
31
+ for val in ("0", "false", "FALSE", "no", "off", " Off "):
31
32
  monkeypatch.setenv("AIDOT_PERSISTENT_MQTT", val)
32
- assert _cam()._resolve_persistent_mqtt() is True, val
33
+ assert _cam()._resolve_persistent_mqtt() is False, val
33
34
 
34
35
 
35
- def test_env_falsey_stays_off(monkeypatch):
36
- for val in ("0", "false", "no", "off", "", "anything"):
36
+ def test_env_truthy_or_unknown_stays_on(monkeypatch):
37
+ for val in ("1", "true", "yes", "on", "anything"):
37
38
  monkeypatch.setenv("AIDOT_PERSISTENT_MQTT", val)
38
- assert _cam()._resolve_persistent_mqtt() is False, val
39
+ assert _cam()._resolve_persistent_mqtt() is True, val
39
40
 
40
41
 
41
- def test_kwarg_wins_over_env(monkeypatch):
42
- monkeypatch.setenv("AIDOT_PERSISTENT_MQTT", "1")
42
+ def test_kwarg_off_wins_over_default(monkeypatch):
43
+ # an explicit opt=False (e.g. user turned the HA toggle off) overrides default-on
44
+ monkeypatch.delenv("AIDOT_PERSISTENT_MQTT", raising=False)
43
45
  cam = _cam()
44
46
  cam._persistent_mqtt_opt = False
45
47
  assert cam._resolve_persistent_mqtt() is False