python-aidot-cameras 1.0.0rc4__tar.gz → 1.0.0rc5__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 (281) hide show
  1. {python_aidot_cameras-1.0.0rc4/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0rc5}/PKG-INFO +1 -1
  2. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/client.py +85 -8
  3. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/pyproject.toml +1 -1
  4. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  5. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_camera_properties.py +64 -0
  6. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/LICENSE +0 -0
  7. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/MANIFEST.in +0 -0
  8. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/README.md +0 -0
  9. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/__init__.py +0 -0
  10. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/__main__.py +0 -0
  11. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_upstream.py +0 -0
  12. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/__init__.py +0 -0
  13. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
  14. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  15. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  16. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  17. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  18. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  19. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  20. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  21. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  22. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  23. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  24. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  25. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  26. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  27. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  28. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  29. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  30. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  31. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  32. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  33. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
  34. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  35. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  36. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  37. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  38. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  39. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  40. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  41. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  42. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  43. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  44. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  45. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  46. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/ble_gateway.py +0 -0
  47. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/__init__.py +0 -0
  48. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/constants.py +0 -0
  49. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/controls.py +0 -0
  50. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/go2rtc.py +0 -0
  51. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/hwaccel.py +0 -0
  52. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/lan_control.py +0 -0
  53. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/models.py +0 -0
  54. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/playback.py +0 -0
  55. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/protocol.py +0 -0
  56. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/sd_events.py +0 -0
  57. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/sd_listing.py +0 -0
  58. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/sd_playback.py +0 -0
  59. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/sdes.py +0 -0
  60. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/sdes_open.py +0 -0
  61. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/tutk.py +0 -0
  62. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/webrtc.py +0 -0
  63. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/camera/webrtc_open.py +0 -0
  64. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/client.py +0 -0
  65. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/cloud_auth.py +0 -0
  66. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/const.py +0 -0
  67. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/credentials.py +0 -0
  68. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/crypto.py +0 -0
  69. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/device_client.py +0 -0
  70. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/discover.py +0 -0
  71. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/exceptions.py +0 -0
  72. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/g711.py +0 -0
  73. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/lan_retry.py +0 -0
  74. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/aidot_cameras/py.typed +0 -0
  75. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
  76. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  77. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  78. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/python_aidot_cameras.egg-info/requires.txt +0 -0
  79. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  80. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/setup.cfg +0 -0
  81. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_aioice_compat.py +0 -0
  82. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_alarm_event.py +0 -0
  83. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_answer_pt_kinds.py +0 -0
  84. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_answer_sdp_video_pt.py +0 -0
  85. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_answer_section_selection.py +0 -0
  86. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_audio_mux_offline.py +0 -0
  87. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
  88. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_avio_request.py +0 -0
  89. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_avio_response_parse.py +0 -0
  90. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_avio_response_router.py +0 -0
  91. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_backoff.py +0 -0
  92. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_battery_detection.py +0 -0
  93. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_battery_evidence_corroboration.py +0 -0
  94. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_battery_skip_turn.py +0 -0
  95. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_bitrate_campaign_arms.py +0 -0
  96. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_ble_gateway.py +0 -0
  97. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_bridge_state_sentinels.py +0 -0
  98. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_busy_backoff.py +0 -0
  99. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_canary_log_names_the_camera.py +0 -0
  100. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_cloud_count.py +0 -0
  101. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_cloud_plan.py +0 -0
  102. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_constants_imports_resolve.py +0 -0
  103. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_control_verdicts.py +0 -0
  104. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_deferred_hardening.py +0 -0
  105. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_device_cmd_seq_match.py +0 -0
  106. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_device_login_guard.py +0 -0
  107. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_device_user_info_cache.py +0 -0
  108. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_direct_ts_keyframe_signal.py +0 -0
  109. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_direct_ts_serve.py +0 -0
  110. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dormant_camera_cleanup.py +0 -0
  111. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_drain_is_cancellable.py +0 -0
  112. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_mux_terminal_write.py +0 -0
  113. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_not_ready_burst.py +0 -0
  114. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_pinning.py +0 -0
  115. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_resolution_ack.py +0 -0
  116. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_serve_decode_skip.py +0 -0
  117. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_serve_ice_wait.py +0 -0
  118. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_serve_open_timeout.py +0 -0
  119. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_skip_signaling_wait.py +0 -0
  120. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_slow_probe.py +0 -0
  121. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_stdout_producer.py +0 -0
  122. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_dtls_video_presence.py +0 -0
  123. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_egress_guard.py +0 -0
  124. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_env_seams.py +0 -0
  125. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_feature_probe_needs_a_live_session.py +0 -0
  126. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
  127. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_futile_keepalive.py +0 -0
  128. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_gate_requires_a_decoded_frame.py +0 -0
  129. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_go2rtc.py +0 -0
  130. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_go2rtc_cli.py +0 -0
  131. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_h264_decode_rate_limit.py +0 -0
  132. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_highport_nomination.py +0 -0
  133. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_hwaccel_probe.py +0 -0
  134. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_ice_config_cache.py +0 -0
  135. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_ice_peer_reflexive.py +0 -0
  136. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_idle_release_both_loops.py +0 -0
  137. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_keepalive_renew.py +0 -0
  138. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_key_restart_sdp.py +0 -0
  139. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_keyframe_prompter.py +0 -0
  140. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_lan_control.py +0 -0
  141. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
  142. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_late_repeats_are_dropped.py +0 -0
  143. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_light_active_color_mode.py +0 -0
  144. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_live_session_carries_talk.py +0 -0
  145. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_live_stream_param.py +0 -0
  146. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_live_validate_policy.py +0 -0
  147. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_live_video_canary.py +0 -0
  148. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_liveplay_echo_timeout.py +0 -0
  149. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_local_only_controls.py +0 -0
  150. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_logging_caps.py +0 -0
  151. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_login_connect_deadline.py +0 -0
  152. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_login_reconnect_backoff.py +0 -0
  153. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_media_counted_only_when_plaintext.py +0 -0
  154. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_media_path_is_reported.py +0 -0
  155. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_media_socket_rcvbuf.py +0 -0
  156. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_media_wait_floor.py +0 -0
  157. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_motion_poll.py +0 -0
  158. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_mqtt_client_id_is_exact.py +0 -0
  159. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_mqtt_credential_refetch.py +0 -0
  160. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_mqtt_credential_self_heal.py +0 -0
  161. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_mqtt_password_not_persisted.py +0 -0
  162. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_mqtt_session_reconnect.py +0 -0
  163. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_mqtt_thread_not_orphaned.py +0 -0
  164. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_nack.py +0 -0
  165. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
  166. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_narrow_pc_ice.py +0 -0
  167. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_no_key_material_in_logs.py +0 -0
  168. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_no_self_referential_go2rtc.py +0 -0
  169. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_no_undefined_names.py +0 -0
  170. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_offer_bandwidth_line.py +0 -0
  171. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
  172. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_offline_keepalive_pause.py +0 -0
  173. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_on_frame_is_an_av_frame.py +0 -0
  174. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_open_fail_logger.py +0 -0
  175. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_open_gate_delay.py +0 -0
  176. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_peer_id_client_type.py +0 -0
  177. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_peerid_reuse.py +0 -0
  178. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_persistent_mqtt.py +0 -0
  179. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_persistent_mqtt_robustness.py +0 -0
  180. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_playback_tls.py +0 -0
  181. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_post_merge_hardening.py +0 -0
  182. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
  183. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_public_signature_matches_impl.py +0 -0
  184. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_publish_target_readiness.py +0 -0
  185. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_quality_campaign_in_session.py +0 -0
  186. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_reboot_action.py +0 -0
  187. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_recent_recordings.py +0 -0
  188. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_remb.py +0 -0
  189. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_remb_goes_out_the_relay_aware_sender.py +0 -0
  190. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_resent_frames_are_dropped.py +0 -0
  191. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_resolution_persists.py +0 -0
  192. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_restart_cycle.py +0 -0
  193. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_retry_policy.py +0 -0
  194. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_scripts_importable.py +0 -0
  195. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sctp_init_ack_tsn.py +0 -0
  196. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sctp_reassembly.py +0 -0
  197. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sctp_sack.py +0 -0
  198. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sd_card_presence.py +0 -0
  199. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sd_event_decode.py +0 -0
  200. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sd_event_probe_payloads.py +0 -0
  201. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sd_listing.py +0 -0
  202. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sd_playback.py +0 -0
  203. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_adaptive.py +0 -0
  204. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_avio_dispatch.py +0 -0
  205. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_bridge_break_gate.py +0 -0
  206. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_connection_mode.py +0 -0
  207. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_echo_wait_timeout.py +0 -0
  208. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_fast_liveplay.py +0 -0
  209. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  210. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_first_media_stall_report.py +0 -0
  211. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_ice_teardown.py +0 -0
  212. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_idle_release.py +0 -0
  213. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_nack_bridge.py +0 -0
  214. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_offer_video_order.py +0 -0
  215. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_offer_video_pin.py +0 -0
  216. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_open_cleanup.py +0 -0
  217. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_sctp_abort.py +0 -0
  218. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_serve_audio.py +0 -0
  219. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_serve_cmd.py +0 -0
  220. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_serve_stderr_drain.py +0 -0
  221. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_session_cap.py +0 -0
  222. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_sprop.py +0 -0
  223. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_talk.py +0 -0
  224. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_teardown_exit_log.py +0 -0
  225. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_terminal_ack.py +0 -0
  226. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_terminal_ack_latency.py +0 -0
  227. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_tmmbr_bridge.py +0 -0
  228. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdes_watchdog.py +0 -0
  229. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdp_narrowing_properties.py +0 -0
  230. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sdp_payload_type_narrowing.py +0 -0
  231. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_security_hardening.py +0 -0
  232. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
  233. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_serializable_login_info.py +0 -0
  234. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_serve_input_timeout.py +0 -0
  235. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_serve_loss_tolerance.py +0 -0
  236. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_serve_pt_respects_the_pin.py +0 -0
  237. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_serve_relay.py +0 -0
  238. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
  239. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_session_stats.py +0 -0
  240. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_shared_house_seam.py +0 -0
  241. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_snapshot_cancellation.py +0 -0
  242. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_speak.py +0 -0
  243. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sprop_capture_gate.py +0 -0
  244. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sprop_staleness.py +0 -0
  245. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sprop_unstable_public.py +0 -0
  246. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
  247. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_srtp_rx_rekey.py +0 -0
  248. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_srtp_tx_key_note.py +0 -0
  249. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stable_terminal_id.py +0 -0
  250. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stall_nudge_policy.py +0 -0
  251. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
  252. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
  253. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
  254. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
  255. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stall_reports_reach_the_artifact.py +0 -0
  256. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stream_cap.py +0 -0
  257. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stream_drain_ownership.py +0 -0
  258. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stream_idle.py +0 -0
  259. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stream_slot_cap.py +0 -0
  260. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_stream_teardown.py +0 -0
  261. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_talk.py +0 -0
  262. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
  263. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_tap_to_mux_end_to_end.py +0 -0
  264. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_terminal_ack.py +0 -0
  265. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_tmmbr.py +0 -0
  266. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_token_file_robustness.py +0 -0
  267. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_token_refresh.py +0 -0
  268. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_turn_create_permission.py +0 -0
  269. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_tutk_path_is_out_of_reach.py +0 -0
  270. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_upstream_compat.py +0 -0
  271. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_validator_shares_one_token.py +0 -0
  272. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_video_profile_log.py +0 -0
  273. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_video_pts_dts.py +0 -0
  274. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_video_ts_unwrap.py +0 -0
  275. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_viewer_check_throttle.py +0 -0
  276. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_viewer_presence.py +0 -0
  277. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_viewer_present_push_mode.py +0 -0
  278. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_wait_or_event.py +0 -0
  279. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_wake_gate_camera_evidence.py +0 -0
  280. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_wake_readiness_retry.py +0 -0
  281. {python_aidot_cameras-1.0.0rc4 → python_aidot_cameras-1.0.0rc5}/tests/test_webrtc_answer_race.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 1.0.0rc4
3
+ Version: 1.0.0rc5
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
@@ -2675,12 +2675,53 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
2675
2675
  publish_items = [
2676
2676
  (f"iot/v1/s/{user_id}/device/devActionReq", payload),
2677
2677
  ]
2678
- messages, _st = await _mqtt_session_with_status(
2679
- mqtt_url, mqtt_user, mqtt_pwd, client_id,
2680
- subscribe_topics=[f"iot/v1/c/{user_id}/#", f"iot/v1/cb/{user_id}/#"],
2681
- publish_items=publish_items,
2682
- duration=timeout,
2683
- )
2678
+ sub_topics = [f"iot/v1/c/{user_id}/#", f"iot/v1/cb/{user_id}/#"]
2679
+
2680
+ # Use the PERSISTENT connection when there is one, exactly as
2681
+ # `_mqtt_device_cmd` does. Opening a fresh session here instead looked
2682
+ # harmless and is not: it connects with the same `mqttClientId` the
2683
+ # long-lived connection is already using, and a broker evicts the older
2684
+ # session on a duplicate client id. Inside Home Assistant that means the
2685
+ # query and the integration's own connection knock each other off, and
2686
+ # the reply never arrives -- the request appears to work and simply
2687
+ # returns nothing. It only "worked" when tested with the integration
2688
+ # stopped, which is the one condition that hides the conflict.
2689
+ messages = []
2690
+ pm = await self._get_persistent_mqtt() if self._resolve_persistent_mqtt() else None
2691
+ if pm is not None:
2692
+ messages, _st = await pm.request(
2693
+ publish_items=publish_items,
2694
+ subscribe_topics=sub_topics,
2695
+ timeout=timeout,
2696
+ )
2697
+ if _st and _st.get("error"):
2698
+ _LOGGER.debug(
2699
+ "async_query_device_action: persistent MQTT failed (%s); "
2700
+ "falling back for %s", _st.get("error"), device_id)
2701
+ pm = None
2702
+ if pm is None:
2703
+ messages, _st = await _mqtt_session_with_status(
2704
+ mqtt_url, mqtt_user, mqtt_pwd, client_id,
2705
+ subscribe_topics=sub_topics,
2706
+ publish_items=publish_items,
2707
+ duration=timeout,
2708
+ )
2709
+ # Several devAction messages can be in flight on a shared connection,
2710
+ # and some carry no `out` at all (a bare acknowledgement). Taking the
2711
+ # first match therefore loses data: a camera reporting six sound
2712
+ # detectors surfaced only the two from whichever reply arrived first.
2713
+ # So prefer a reply that actually carries a payload, and only fall back
2714
+ # to an empty one when nothing better arrived.
2715
+ # Two passes' worth of preference, resolved in one:
2716
+ # best - right device, right action, our seq, and a payload
2717
+ # good - right device, right action, a payload
2718
+ # `seq` is a PREFERENCE, never a filter. Requiring it looked tidy and
2719
+ # was wrong: a camera that does not echo our seq has every one of its
2720
+ # replies discarded, and the call then reports "no reply" for a camera
2721
+ # that answered. Observed live as `no reply from <device> (4 messages
2722
+ # seen)` -- four candidates rejected on the seq alone.
2723
+ best = good = None
2724
+ _empty_seen = False
2684
2725
  for topic, raw in messages:
2685
2726
  if "devAction" not in topic:
2686
2727
  continue
@@ -2691,8 +2732,44 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
2691
2732
  body = msg.get("payload") or {}
2692
2733
  if body.get("action") != action:
2693
2734
  continue
2694
- return body.get("out")
2695
- _LOGGER.debug("devActionReq %s: no reply from %s", action, device_id)
2735
+ # Match the DEVICE too. The persistent connection is subscribed to
2736
+ # the whole account (`iot/v1/c/{userId}/#`), so replies for every
2737
+ # camera arrive here -- matching on the action alone lets one
2738
+ # camera read another's answer. Observed live: four cameras all
2739
+ # reported the same SD figures, which belonged to whichever camera
2740
+ # answered first.
2741
+ # The device match IS a filter: the persistent connection carries
2742
+ # the whole account, and without this one camera reads another's
2743
+ # answer (observed live as four cameras reporting identical SD
2744
+ # figures).
2745
+ _rid = body.get("devId") or msg.get("id")
2746
+ if _rid and _rid != device_id:
2747
+ continue
2748
+ out = body.get("out")
2749
+ if out is None:
2750
+ _empty_seen = True
2751
+ continue
2752
+ if msg.get("seq") == seq:
2753
+ best = out
2754
+ break
2755
+ if good is None:
2756
+ good = out
2757
+ if best is not None:
2758
+ return best
2759
+ if good is not None:
2760
+ _LOGGER.debug(
2761
+ "devActionReq %s: accepted a reply for %s without a seq match",
2762
+ action, device_id)
2763
+ return good
2764
+ if _empty_seen:
2765
+ _LOGGER.debug("devActionReq %s: only empty replies from %s",
2766
+ action, device_id)
2767
+ return None
2768
+ # Never return None silently: a caller cannot tell "unsupported" from
2769
+ # "the reply never came back", and a silent None is what made this
2770
+ # failure invisible for an entire debugging session.
2771
+ _LOGGER.info("devActionReq %s: no reply from %s (%d messages seen)",
2772
+ action, device_id, len(messages))
2696
2773
  return None
2697
2774
 
2698
2775
  async def async_trigger_device_action(
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "1.0.0rc4"
7
+ version = "1.0.0rc5"
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 1.0.0rc4
3
+ Version: 1.0.0rc5
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
@@ -104,3 +104,67 @@ class TestSdCardInfo:
104
104
 
105
105
  def test_no_reply_is_none(self):
106
106
  assert asyncio.run(_Cam(None).async_get_sd_card_info()) is None
107
+
108
+
109
+ class TestItDoesNotFightTheIntegrationsConnection:
110
+ """The query must reuse the persistent MQTT connection when there is one.
111
+
112
+ Opening a fresh session uses the same `mqttClientId` the long-lived
113
+ connection already holds, and a broker evicts the older session on a
114
+ duplicate client id. Inside Home Assistant that means the query and the
115
+ integration knock each other off and the reply never arrives -- which looks
116
+ exactly like "this camera does not support the action".
117
+ """
118
+
119
+ def test_the_query_prefers_the_persistent_connection(self):
120
+ import inspect
121
+ from aidot_cameras.camera import client as client_mod
122
+ src = inspect.getsource(client_mod.CameraMixin.async_query_device_action)
123
+ assert "_get_persistent_mqtt" in src, (
124
+ "the query opens its own MQTT session again - that duplicates the "
125
+ "client id and the reply is lost inside Home Assistant")
126
+ assert "_resolve_persistent_mqtt" in src
127
+
128
+ def test_a_missing_reply_is_logged_not_silent(self):
129
+ import inspect
130
+ from aidot_cameras.camera import client as client_mod
131
+ src = inspect.getsource(client_mod.CameraMixin.async_query_device_action)
132
+ assert "no reply from" in src
133
+ # INFO, not debug: a silent None is what hid this failure. Find the
134
+ # logging call that owns the message rather than guessing at a
135
+ # character window near it.
136
+ import re
137
+ call = re.search(r'_LOGGER\.(\w+)\(\s*\n?\s*"[^"]*no reply from', src)
138
+ assert call is not None, "the no-reply message is not a logging call"
139
+ assert call.group(1) == "info", (
140
+ f"the no-reply message logs at {call.group(1)}, not info")
141
+
142
+
143
+ class TestRepliesAreMatchedToTheRightCamera:
144
+ """The persistent connection carries the whole account's traffic.
145
+
146
+ Observed live: all four cameras reported identical SD figures, because the
147
+ matcher keyed on the action alone and each camera accepted whichever reply
148
+ arrived first.
149
+ """
150
+
151
+ def test_it_filters_on_the_device_id(self):
152
+ import inspect
153
+ from aidot_cameras.camera import client as client_mod
154
+ src = inspect.getsource(client_mod.CameraMixin.async_query_device_action)
155
+ assert 'body.get("devId")' in src
156
+ assert "!= device_id" in src
157
+
158
+ def test_seq_is_a_preference_not_a_filter(self):
159
+ """Requiring the seq discards every reply from a camera that does not
160
+ echo it -- seen live as "no reply (4 messages seen)" on a camera that
161
+ had in fact answered four times."""
162
+ import inspect
163
+ from aidot_cameras.camera import client as client_mod
164
+ src = inspect.getsource(client_mod.CameraMixin.async_query_device_action)
165
+ assert 'msg.get("seq") == seq' in src, "seq should still be preferred"
166
+ # The rejecting form must be gone.
167
+ assert "_rseq != seq" not in src
168
+ assert "if _rseq and" not in src
169
+ # A reply without a matching seq must still be usable.
170
+ assert "good" in src