python-aidot-cameras 1.0.0rc13__tar.gz → 1.0.0rc15__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 (296) hide show
  1. {python_aidot_cameras-1.0.0rc13/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0rc15}/PKG-INFO +1 -1
  2. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/client.py +82 -13
  3. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/controls.py +43 -0
  4. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/pyproject.toml +1 -1
  5. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  6. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/python_aidot_cameras.egg-info/SOURCES.txt +2 -0
  7. python_aidot_cameras-1.0.0rc15/tests/test_auto_alarm.py +70 -0
  8. python_aidot_cameras-1.0.0rc15/tests/test_peer_id_class_env.py +62 -0
  9. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_peer_id_client_type.py +37 -36
  10. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/LICENSE +0 -0
  11. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/MANIFEST.in +0 -0
  12. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/README.md +0 -0
  13. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/__init__.py +0 -0
  14. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/__main__.py +0 -0
  15. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_upstream.py +0 -0
  16. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/__init__.py +0 -0
  17. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
  18. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  19. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  20. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  21. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  22. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  23. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  24. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  25. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  26. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  27. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  28. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  29. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  30. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  31. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  32. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  33. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  34. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  35. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  36. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  37. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
  38. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  39. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  40. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  41. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  42. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  43. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  44. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  45. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  46. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  47. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  48. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  49. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  50. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/ble_gateway.py +0 -0
  51. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/__init__.py +0 -0
  52. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/constants.py +0 -0
  53. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/go2rtc.py +0 -0
  54. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/hwaccel.py +0 -0
  55. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/lan_control.py +0 -0
  56. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/models.py +0 -0
  57. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/playback.py +0 -0
  58. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/protocol.py +0 -0
  59. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/sd_events.py +0 -0
  60. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/sd_listing.py +0 -0
  61. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/sd_playback.py +0 -0
  62. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/sdes.py +0 -0
  63. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/sdes_open.py +0 -0
  64. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/tutk.py +0 -0
  65. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/webrtc.py +0 -0
  66. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/camera/webrtc_open.py +0 -0
  67. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/client.py +0 -0
  68. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/cloud_auth.py +0 -0
  69. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/const.py +0 -0
  70. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/credentials.py +0 -0
  71. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/crypto.py +0 -0
  72. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/device_client.py +0 -0
  73. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/discover.py +0 -0
  74. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/exceptions.py +0 -0
  75. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/g711.py +0 -0
  76. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/lan_retry.py +0 -0
  77. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/aidot_cameras/py.typed +0 -0
  78. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  79. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  80. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/python_aidot_cameras.egg-info/requires.txt +0 -0
  81. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  82. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/setup.cfg +0 -0
  83. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_abandoned_attempt_skips_the_doomed_serve.py +0 -0
  84. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_abandoned_wait_still_maps_audio.py +0 -0
  85. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_aioice_compat.py +0 -0
  86. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_alarm_event.py +0 -0
  87. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_answer_pt_kinds.py +0 -0
  88. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_answer_sdp_video_pt.py +0 -0
  89. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_answer_section_selection.py +0 -0
  90. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_audio_mux_offline.py +0 -0
  91. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
  92. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_avio_request.py +0 -0
  93. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_avio_response_parse.py +0 -0
  94. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_avio_response_router.py +0 -0
  95. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_backoff.py +0 -0
  96. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_battery_detection.py +0 -0
  97. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_battery_evidence_corroboration.py +0 -0
  98. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_battery_skip_turn.py +0 -0
  99. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_battery_stale_offer_is_abandoned_early.py +0 -0
  100. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_battery_wake_gate_app_parity.py +0 -0
  101. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_bitrate_campaign_arms.py +0 -0
  102. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_ble_gateway.py +0 -0
  103. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_bridge_state_sentinels.py +0 -0
  104. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_busy_backoff.py +0 -0
  105. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_camera_properties.py +0 -0
  106. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_canary_log_names_the_camera.py +0 -0
  107. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_cloud_count.py +0 -0
  108. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_cloud_plan.py +0 -0
  109. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_constants_imports_resolve.py +0 -0
  110. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_control_verdicts.py +0 -0
  111. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_deferred_hardening.py +0 -0
  112. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_detection_types.py +0 -0
  113. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_device_cmd_seq_match.py +0 -0
  114. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_device_login_guard.py +0 -0
  115. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_device_user_info_cache.py +0 -0
  116. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_direct_ts_keyframe_signal.py +0 -0
  117. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_direct_ts_serve.py +0 -0
  118. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dormant_camera_cleanup.py +0 -0
  119. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_drain_is_cancellable.py +0 -0
  120. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_mux_terminal_write.py +0 -0
  121. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_not_ready_burst.py +0 -0
  122. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_pinning.py +0 -0
  123. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_resolution_ack.py +0 -0
  124. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_serve_decode_skip.py +0 -0
  125. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_serve_ice_wait.py +0 -0
  126. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_serve_open_timeout.py +0 -0
  127. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_skip_signaling_wait.py +0 -0
  128. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_slow_probe.py +0 -0
  129. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_stdout_producer.py +0 -0
  130. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_dtls_video_presence.py +0 -0
  131. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_egress_guard.py +0 -0
  132. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_env_seams.py +0 -0
  133. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_feature_probe_needs_a_live_session.py +0 -0
  134. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
  135. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_futile_keepalive.py +0 -0
  136. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_gate_requires_a_decoded_frame.py +0 -0
  137. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_go2rtc.py +0 -0
  138. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_go2rtc_cli.py +0 -0
  139. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_h264_decode_rate_limit.py +0 -0
  140. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_highport_nomination.py +0 -0
  141. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_hwaccel_probe.py +0 -0
  142. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_ice_config_cache.py +0 -0
  143. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_ice_credentials_match_the_app.py +0 -0
  144. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_ice_peer_reflexive.py +0 -0
  145. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_idle_release_both_loops.py +0 -0
  146. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_keepalive_renew.py +0 -0
  147. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_key_restart_sdp.py +0 -0
  148. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_keyframe_prompter.py +0 -0
  149. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_lan_control.py +0 -0
  150. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
  151. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_late_repeats_are_dropped.py +0 -0
  152. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_light_active_color_mode.py +0 -0
  153. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_live_session_carries_talk.py +0 -0
  154. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_live_stream_param.py +0 -0
  155. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_live_validate_policy.py +0 -0
  156. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_live_video_canary.py +0 -0
  157. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_liveplay_echo_timeout.py +0 -0
  158. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_local_only_controls.py +0 -0
  159. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_logging_caps.py +0 -0
  160. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_login_connect_deadline.py +0 -0
  161. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_login_reconnect_backoff.py +0 -0
  162. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_media_counted_only_when_plaintext.py +0 -0
  163. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_media_path_is_reported.py +0 -0
  164. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_media_socket_rcvbuf.py +0 -0
  165. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_media_wait_floor.py +0 -0
  166. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_motion_poll.py +0 -0
  167. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_mqtt_client_id_is_exact.py +0 -0
  168. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_mqtt_credential_refetch.py +0 -0
  169. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_mqtt_credential_self_heal.py +0 -0
  170. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_mqtt_password_not_persisted.py +0 -0
  171. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_mqtt_session_reconnect.py +0 -0
  172. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_mqtt_thread_not_orphaned.py +0 -0
  173. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_nack.py +0 -0
  174. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
  175. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_narrow_pc_ice.py +0 -0
  176. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_no_key_material_in_logs.py +0 -0
  177. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_no_self_referential_go2rtc.py +0 -0
  178. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_no_unbounded_hex_dumps.py +0 -0
  179. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_no_undefined_names.py +0 -0
  180. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_nominates_as_soon_as_the_answer_lands.py +0 -0
  181. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_off_subnet_candidate.py +0 -0
  182. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_offer_bandwidth_line.py +0 -0
  183. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
  184. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_offline_keepalive_pause.py +0 -0
  185. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_on_frame_is_an_av_frame.py +0 -0
  186. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_open_fail_logger.py +0 -0
  187. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_open_gate_delay.py +0 -0
  188. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_peerid_reuse.py +0 -0
  189. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_persistent_mqtt.py +0 -0
  190. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_persistent_mqtt_robustness.py +0 -0
  191. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_playback_tls.py +0 -0
  192. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_post_merge_hardening.py +0 -0
  193. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
  194. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_public_signature_matches_impl.py +0 -0
  195. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_publish_target_readiness.py +0 -0
  196. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_quality_campaign_in_session.py +0 -0
  197. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_reboot_action.py +0 -0
  198. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_recent_recordings.py +0 -0
  199. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_remb.py +0 -0
  200. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_remb_goes_out_the_relay_aware_sender.py +0 -0
  201. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_resent_frames_are_dropped.py +0 -0
  202. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_resolution_persists.py +0 -0
  203. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_restart_cycle.py +0 -0
  204. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_retry_policy.py +0 -0
  205. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_scripts_importable.py +0 -0
  206. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sctp_init_ack_tsn.py +0 -0
  207. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sctp_reassembly.py +0 -0
  208. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sctp_sack.py +0 -0
  209. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sd_card_presence.py +0 -0
  210. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sd_event_decode.py +0 -0
  211. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sd_event_probe_payloads.py +0 -0
  212. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sd_listing.py +0 -0
  213. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sd_playback.py +0 -0
  214. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_adaptive.py +0 -0
  215. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_avio_dispatch.py +0 -0
  216. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_bridge_break_gate.py +0 -0
  217. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_connection_mode.py +0 -0
  218. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_echo_wait_is_not_dead_time.py +0 -0
  219. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_fast_liveplay.py +0 -0
  220. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  221. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_first_media_stall_report.py +0 -0
  222. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_ice_teardown.py +0 -0
  223. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_idle_release.py +0 -0
  224. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_nack_bridge.py +0 -0
  225. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_offer_video_order.py +0 -0
  226. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_offer_video_pin.py +0 -0
  227. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_open_cleanup.py +0 -0
  228. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_sctp_abort.py +0 -0
  229. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_serve_audio.py +0 -0
  230. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_serve_cmd.py +0 -0
  231. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_serve_stderr_drain.py +0 -0
  232. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_session_cap.py +0 -0
  233. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_sprop.py +0 -0
  234. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_talk.py +0 -0
  235. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_teardown_exit_log.py +0 -0
  236. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_terminal_ack.py +0 -0
  237. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_terminal_ack_latency.py +0 -0
  238. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_tmmbr_bridge.py +0 -0
  239. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdes_watchdog.py +0 -0
  240. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdp_narrowing_properties.py +0 -0
  241. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sdp_payload_type_narrowing.py +0 -0
  242. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_security_hardening.py +0 -0
  243. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
  244. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_serializable_login_info.py +0 -0
  245. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_serve_input_timeout.py +0 -0
  246. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_serve_loss_tolerance.py +0 -0
  247. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_serve_pt_respects_the_pin.py +0 -0
  248. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_serve_relay.py +0 -0
  249. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
  250. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_session_stats.py +0 -0
  251. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_shared_house_seam.py +0 -0
  252. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_signalling_is_attributable_to_a_camera.py +0 -0
  253. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_snapshot_cancellation.py +0 -0
  254. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_speak.py +0 -0
  255. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sprop_capture_gate.py +0 -0
  256. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sprop_staleness.py +0 -0
  257. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sprop_unstable_public.py +0 -0
  258. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
  259. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_srtp_rx_rekey.py +0 -0
  260. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_srtp_tx_key_note.py +0 -0
  261. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stable_terminal_id.py +0 -0
  262. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stall_nudge_policy.py +0 -0
  263. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
  264. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
  265. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
  266. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
  267. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stall_reports_reach_the_artifact.py +0 -0
  268. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_status_and_trace_channels.py +0 -0
  269. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stream_cap.py +0 -0
  270. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stream_drain_ownership.py +0 -0
  271. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stream_idle.py +0 -0
  272. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stream_slot_cap.py +0 -0
  273. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_stream_teardown.py +0 -0
  274. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_talk.py +0 -0
  275. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
  276. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_tap_to_mux_end_to_end.py +0 -0
  277. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_terminal_ack.py +0 -0
  278. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_tmmbr.py +0 -0
  279. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_token_file_robustness.py +0 -0
  280. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_token_refresh.py +0 -0
  281. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_transport_failure_reporting.py +0 -0
  282. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_turn_create_permission.py +0 -0
  283. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_tutk_path_is_out_of_reach.py +0 -0
  284. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_unbuildable_summary_repeats_at_debug.py +0 -0
  285. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_upstream_compat.py +0 -0
  286. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_validator_shares_one_token.py +0 -0
  287. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_video_profile_log.py +0 -0
  288. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_video_pts_dts.py +0 -0
  289. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_video_ts_unwrap.py +0 -0
  290. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_viewer_check_throttle.py +0 -0
  291. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_viewer_presence.py +0 -0
  292. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_viewer_present_push_mode.py +0 -0
  293. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_wait_or_event.py +0 -0
  294. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_wake_gate_camera_evidence.py +0 -0
  295. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/tests/test_wake_readiness_retry.py +0 -0
  296. {python_aidot_cameras-1.0.0rc13 → python_aidot_cameras-1.0.0rc15}/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.0rc13
3
+ Version: 1.0.0rc15
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
@@ -1163,6 +1163,37 @@ def _stable_terminal_id(seed: "Optional[str]" = None, width: int = 6) -> str:
1163
1163
  EXPT_PEERID_FILE = os.environ.get("AIDOT_EXPT_PEERID_FILE")
1164
1164
 
1165
1165
 
1166
+ #: Screening knob for the peer id's CLIENT CLASS only, off unless set.
1167
+ #: `_expt_peer_id_fields` covers the same ground but reads a file, and the
1168
+ #: live-validation harness can only pass environment variables - so an arm that
1169
+ #: has to run there needs this route.
1170
+ EXPT_PEERID_CLASS = "AIDOT_EXPT_PEERID_CLASS"
1171
+
1172
+
1173
+ def _expt_peer_id_class():
1174
+ """One hex character to pin field 2's first character, or None.
1175
+
1176
+ The camera takes its client class from that character
1177
+ (``client_type = field2[0] - '0'``). Pinning it to ``'0'`` (APP_ANDROID)
1178
+ was tried and reverted - it breaks the A000088, which accepts the session
1179
+ and then sends nothing. The vendor WEB app sends ``'2'``, and that arm has
1180
+ never been screened; this exists to screen it.
1181
+
1182
+ **Pins the class character and nothing else.** The three trailing integers
1183
+ encode transport, and an SDES tail pushed at a DTLS camera is silently
1184
+ discarded - the failure mode an earlier unscoped knob produced. Those stay
1185
+ exactly as computed.
1186
+
1187
+ Fails closed: anything that is not a single hex digit is ignored rather
1188
+ than applied, because a peer id of the wrong shape is rejected outright and
1189
+ would break every open rather than one arm of an experiment.
1190
+ """
1191
+ raw = (os.environ.get(EXPT_PEERID_CLASS) or "").strip()
1192
+ if len(raw) != 1 or raw not in "0123456789abcdef":
1193
+ return None
1194
+ return raw
1195
+
1196
+
1166
1197
  def _expt_peer_id_fields(device_id=None, path=None):
1167
1198
  """Experiment override for one device's peer-id fields, or None.
1168
1199
 
@@ -6518,8 +6549,11 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
6518
6549
  # the vendor Android app hard-codes '0' there and the vendor web app
6519
6550
  # sends '2'. That is all verified.
6520
6551
  #
6521
- # **It is NOT verified for the rest of the fleet, and pinning it breaks
6522
- # the A000088.** Setting this character to '0' for every camera was
6552
+ # **Pinning it to '0' breaks the A000088. Pinning it to '2' does not.**
6553
+ # The two are not interchangeable and the earlier note here said only
6554
+ # "pinning it breaks the A000088", which over-generalised one arm.
6555
+ #
6556
+ # Setting this character to '0' (APP_ANDROID) for every camera was
6523
6557
  # tried and reverted on 2026-08-31. Against a drained fleet with Home
6524
6558
  # Assistant stopped, three A000088 cameras went 0 for 3 over nine
6525
6559
  # attempts - each one completing its DTLS handshake in under two seconds
@@ -6527,23 +6561,58 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
6527
6561
  # A001064 on the first attempt. Reverting THIS LINE ALONE, with every
6528
6562
  # other change of that release left in place, restored 3 of 3.
6529
6563
  #
6530
- # The A000088 is a different SoC (Realtek Ameba, FreeRTOS/KM4) whose
6531
- # image has never been disassembled. A finding from the A001064's
6532
- # firmware does not transfer to it, and this camera does not merely
6533
- # ignore the byte - announcing APP_ANDROID makes it accept the session
6534
- # and send nothing, which is worse than the random value it replaced.
6564
+ # The A000088 is a different SoC (Realtek RTL8735B / AmebaPro2). Its
6565
+ # image WAS disassembled on 2026-09-07, and it has **no client class at
6566
+ # all**: `lds_process_peerid` in rtc_mqtt.c parses
6567
+ # `%[^_]_%[^_]_%02hhd_%02hhd_%02hhd`, keeps field 2 as an opaque string
6568
+ # at buf+0x32, and never derives a class from its first character. It
6569
+ # also treats a parse failure as NON-fatal - it substitutes the literal
6570
+ # "userid" for field 2 and continues. Only field 1 is compared, against
6571
+ # "google". So `client_type = field2[0]-'0'` and "a malformed peer id is
6572
+ # rejected outright" are BOTH A001064-only; do not generalise either.
6573
+ #
6574
+ # Which leaves the class-0 result real but unexplained: 0 of 9 is not
6575
+ # luck against a 29-of-30 baseline, yet the firmware has nothing there
6576
+ # to read. Do not repeat "class 0 breaks it because it announces
6577
+ # APP_ANDROID" - that story is not supported by the image.
6578
+ #
6579
+ # Class '2' (WEB) - what the vendor web app sends - was screened on
6580
+ # 2026-09-07 and does NOT break it: 6 of 6 A000088 cameras PASS across
6581
+ # two independent live-validation runs, nearly all on the first
6582
+ # attempt, against 0 of 9 attempts for class '0'. For reference the
6583
+ # random character's own baseline is 29/30 over ten runs, so the 0/9
6584
+ # was never background flakiness.
6585
+ #
6586
+ # SHIPPED as '2' on 2026-09-07, after the arm was also run under the
6587
+ # exact condition the class-0 failure was found in: Home Assistant's
6588
+ # config entry disabled so nothing held a session, fleet drained, opens
6589
+ # driven from the LAN runner. Class 2 took 3 of 3 A000088 cameras on
6590
+ # the first attempt there, against 0 of 9 attempts for class 0. Nine of
6591
+ # nine across all screening runs.
6535
6592
  #
6536
- # So the character stays random. Nothing needs it: the 80.2 s cliff this
6537
- # was found while chasing is fixed in the SCTP receiver (see
6538
- # `_sctp_sack_chunk`), not here. Screen it per model with
6539
- # `_expt_peer_id_fields` before ever setting it again, and get evidence
6540
- # from EACH model's own firmware.
6541
- rand6 = os.urandom(3).hex() # 6 hex chars, fresh per open
6593
+ # Announcing a truthful class beats announcing a random hex digit that
6594
+ # was out of range 11 times in 16. Note class 2 is NOT one of the
6595
+ # smart-home classes (3, 4) that skip the camera's keepalive watchdog,
6596
+ # so this changes what we claim to be and nothing about how the session
6597
+ # is policed. Screen any future change with `_expt_peer_id_class` (env)
6598
+ # or `_expt_peer_id_fields` (file, per device), and get evidence from
6599
+ # EACH model - the A000088's firmware has never been disassembled.
6600
+ # Field 2 = client class + 5 random. '2' is EN_WEBRTC_CLIENT_TYPE_WEB,
6601
+ # what the vendor web app announces. See the note above for why this
6602
+ # is '2' and never '0'. The tail stays random: pinning all six would
6603
+ # make every peer id identical across opens, which is the cross-session
6604
+ # reuse the camera dedups on.
6605
+ rand6 = "2" + os.urandom(3).hex()[1:]
6542
6606
  version = 1 if sdes else 2
6543
6607
  # Experiment override (off by default, fails closed, scoped to one
6544
6608
  # device): the camera reads its client class from field 2's first
6545
6609
  # character - see _expt_peer_id_fields. Without a device id the
6546
6610
  # override never applies.
6611
+ # Class-only screening knob (env), applied before the file override so
6612
+ # a file naming this device still wins.
6613
+ _cls = _expt_peer_id_class()
6614
+ if _cls:
6615
+ rand6 = _cls + rand6[1:]
6547
6616
  _fields = _expt_peer_id_fields(device_id)
6548
6617
  if _fields is not None:
6549
6618
  _term, live_type, stream_id, version = _fields
@@ -190,6 +190,49 @@ class _CameraControlsMixin:
190
190
  return await self.async_trigger_device_action(
191
191
  "setRoiHuman", {"roi": [payload]})
192
192
 
193
+ #: The siren's automatic trigger. ``autoAlarm`` is the master; the other
194
+ #: two say what sets it off. Probed 2026-09-07: NOT a mirror of anything
195
+ #: else we expose -- on an A000088 ``motionDetection`` read 0 while the
196
+ #: camera's own motion-detection setting read True, and on an A001064
197
+ #: ``humanDetect`` read 0 while ``getRoiHuman``'s read 1.
198
+ AUTO_ALARM_KEYS = ("autoAlarm", "motionDetection", "humanDetect")
199
+
200
+ async def async_get_auto_alarm(self) -> "Optional[dict]":
201
+ """Siren auto-trigger settings as ``{key: bool}``.
202
+
203
+ ``None`` means the camera did not answer -- unknown, NOT all-off.
204
+ Reporting an unanswered camera as three disarmed switches would claim a
205
+ state it never gave, and this is the setting that decides whether a
206
+ siren goes off in someone's house.
207
+ """
208
+ out = await self.async_query_device_action("getAutoAlarm")
209
+ if not isinstance(out, list) or not out or not isinstance(out[0], dict):
210
+ return None
211
+ return {k: bool(v) for k, v in out[0].items()}
212
+
213
+ async def async_set_auto_alarm(self, key: str, enabled: bool) -> bool:
214
+ """Arm or disarm the siren's automatic trigger, or one of its causes.
215
+
216
+ Read-modify-write against the camera's own object. That matters more
217
+ here than anywhere else: rebuilding the payload from our own key list
218
+ could arm ``autoAlarm`` as a side effect of changing a trigger, which
219
+ would make a camera start sounding a siren nobody asked it to.
220
+ """
221
+ out = await self.async_query_device_action("getAutoAlarm")
222
+ if not isinstance(out, list) or not out or not isinstance(out[0], dict):
223
+ _LOGGER.warning(
224
+ "auto alarm: %s did not report its settings; not writing",
225
+ self.device_id)
226
+ return False
227
+ current = out[0]
228
+ if key not in current:
229
+ _LOGGER.warning("auto alarm: %s does not report %r",
230
+ self.device_id, key)
231
+ return False
232
+ payload = dict(current)
233
+ payload[key] = 1 if enabled else 0
234
+ return await self.async_trigger_device_action("setAutoAlarm", [payload])
235
+
193
236
  async def async_get_wifi_info(self) -> "Optional[dict]":
194
237
  """``{"ssid": str, "rssi": int}`` for the camera's own WiFi link.
195
238
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "1.0.0rc13"
7
+ version = "1.0.0rc15"
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.0rc13
3
+ Version: 1.0.0rc15
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
@@ -83,6 +83,7 @@ tests/test_answer_pt_kinds.py
83
83
  tests/test_answer_sdp_video_pt.py
84
84
  tests/test_answer_section_selection.py
85
85
  tests/test_audio_mux_offline.py
86
+ tests/test_auto_alarm.py
86
87
  tests/test_avio_ctrl_uses_the_session_dseq.py
87
88
  tests/test_avio_request.py
88
89
  tests/test_avio_response_parse.py
@@ -180,6 +181,7 @@ tests/test_offline_keepalive_pause.py
180
181
  tests/test_on_frame_is_an_av_frame.py
181
182
  tests/test_open_fail_logger.py
182
183
  tests/test_open_gate_delay.py
184
+ tests/test_peer_id_class_env.py
183
185
  tests/test_peer_id_client_type.py
184
186
  tests/test_peerid_reuse.py
185
187
  tests/test_persistent_mqtt.py
@@ -0,0 +1,70 @@
1
+ """The siren's automatic trigger: on/off, and what sets it off.
2
+
3
+ `getAutoAlarm` returns `[{autoAlarm, motionDetection, humanDetect}]` -- a master
4
+ switch plus which events fire it. Probed on real hardware 2026-09-07, and it is
5
+ NOT a mirror of anything already exposed: on an A000088 its `motionDetection`
6
+ read 0 while the camera's own motion-detection setting read True, and on an
7
+ A001064 its `humanDetect` read 0 while `getRoiHuman`'s read 1. Two independent
8
+ settings that happen to share field names.
9
+
10
+ Same discipline as the detection types: read-modify-write against the camera's
11
+ own object so a flag we do not model cannot be reset, and a camera that does not
12
+ answer is unknown rather than all-off.
13
+ """
14
+
15
+ import asyncio
16
+
17
+ from aidot_cameras.camera.controls import _CameraControlsMixin
18
+
19
+
20
+ class _Cam(_CameraControlsMixin):
21
+ device_id = "cam1"
22
+
23
+ def __init__(self, out):
24
+ self._out = out
25
+ self.triggered: list = []
26
+
27
+ async def async_query_device_action(self, action, params=None, **kw):
28
+ return self._out
29
+
30
+ async def async_trigger_device_action(self, action, params=None, **kw):
31
+ self.triggered.append((action, params))
32
+ return True
33
+
34
+
35
+ _LIVE = [{"autoAlarm": 0, "motionDetection": 0, "humanDetect": 1}]
36
+
37
+
38
+ def test_reads_the_master_and_both_triggers():
39
+ got = asyncio.run(_Cam(_LIVE).async_get_auto_alarm())
40
+ assert got == {"autoAlarm": False, "motionDetection": False,
41
+ "humanDetect": True}
42
+
43
+
44
+ def test_a_camera_that_does_not_answer_is_unknown_not_all_off():
45
+ assert asyncio.run(_Cam(None).async_get_auto_alarm()) is None
46
+
47
+
48
+ def test_setting_one_flag_preserves_the_others():
49
+ """Turning a trigger on must not silently disarm the master, or arm it."""
50
+ cam = _Cam(_LIVE)
51
+ assert asyncio.run(cam.async_set_auto_alarm("motionDetection", True)) is True
52
+ action, payload = cam.triggered[0]
53
+ assert action == "setAutoAlarm"
54
+ row = payload[0]
55
+ assert row["motionDetection"] == 1 # the one we changed
56
+ assert row["autoAlarm"] == 0 # master untouched - still disarmed
57
+ assert row["humanDetect"] == 1 # other trigger untouched
58
+ assert set(row) == set(_LIVE[0])
59
+
60
+
61
+ def test_a_key_the_camera_does_not_report_is_refused():
62
+ cam = _Cam(_LIVE)
63
+ assert asyncio.run(cam.async_set_auto_alarm("sirenDetect", True)) is False
64
+ assert cam.triggered == []
65
+
66
+
67
+ def test_no_write_when_the_camera_will_not_say_what_it_has():
68
+ cam = _Cam(None)
69
+ assert asyncio.run(cam.async_set_auto_alarm("autoAlarm", True)) is False
70
+ assert cam.triggered == []
@@ -0,0 +1,62 @@
1
+ """An env-var route to pinning the peer id's client class, for screening only.
2
+
3
+ The camera reads its client class from field 2's first character. Pinning it to
4
+ '0' (APP_ANDROID) was tried and reverted: three A000088 cameras went 0 for 3
5
+ over nine attempts, each completing DTLS and then receiving no media. That
6
+ finding is real - checked against ten live-validation runs, the A000088 is
7
+ 29/30 PASS with the random character, so a 3% baseline cannot explain 0/9.
8
+
9
+ But it only ever tested '0'. The vendor WEB app sends '2' (WEB), and that arm
10
+ has never been run. `_expt_peer_id_fields` exists to screen this, and it reads
11
+ a FILE - which the live-validation harness cannot write, it only passes env
12
+ vars. This is that route and nothing more.
13
+
14
+ It pins ONLY the class character. It deliberately cannot touch the three
15
+ trailing integers: those encode transport, and an SDES tail pushed at a DTLS
16
+ camera is silently discarded, which is how an earlier unscoped knob forced a
17
+ battery camera to ~30x its wake rate.
18
+ """
19
+
20
+ from aidot_cameras.camera.client import _expt_peer_id_class
21
+
22
+
23
+ def test_unset_means_no_override(monkeypatch):
24
+ monkeypatch.delenv("AIDOT_EXPT_PEERID_CLASS", raising=False)
25
+ assert _expt_peer_id_class() is None
26
+
27
+
28
+ def test_a_single_hex_digit_is_accepted(monkeypatch):
29
+ monkeypatch.setenv("AIDOT_EXPT_PEERID_CLASS", "2")
30
+ assert _expt_peer_id_class() == "2"
31
+
32
+
33
+ def test_whitespace_is_tolerated(monkeypatch):
34
+ monkeypatch.setenv("AIDOT_EXPT_PEERID_CLASS", " 2 ")
35
+ assert _expt_peer_id_class() == "2"
36
+
37
+
38
+ def test_anything_that_would_change_the_id_shape_is_refused(monkeypatch):
39
+ """A wrong width changes the peer id's length and the camera rejects a
40
+ malformed id outright - fail closed rather than break every open."""
41
+ for bad in ("22", "", "zz", "0x2", "-1"):
42
+ monkeypatch.setenv("AIDOT_EXPT_PEERID_CLASS", bad)
43
+ assert _expt_peer_id_class() is None, bad
44
+
45
+
46
+ def test_it_pins_only_the_first_character(monkeypatch):
47
+ """The other five stay random: pinning all six would make every peer id
48
+ identical across opens, which is cross-session REUSE and would confound the
49
+ class with the camera's session-dedup path."""
50
+ from aidot_cameras.camera.client import CameraMixin
51
+ monkeypatch.setenv("AIDOT_EXPT_PEERID_CLASS", "2")
52
+ monkeypatch.delenv("AIDOT_EXPT_PEERID_FILE", raising=False)
53
+
54
+ # staticmethod: no instance, and device_id left unset so only the env knob
55
+ # can act (the file override needs a device id and must not interfere).
56
+ ids = {CameraMixin.generate_webrtc_peer_id(live_type=2, stream_id=0)
57
+ for _ in range(12)}
58
+ fields = [i.split("_") for i in ids]
59
+ assert all(f[1][0] == "2" for f in fields), "class not pinned"
60
+ assert len({f[1][1:] for f in fields}) > 1, "tail stopped being random"
61
+ # and the transport digits are untouched by this knob
62
+ assert all(f[2:] == ["2", "0", "2"] for f in fields), fields[0]
@@ -176,31 +176,50 @@ class TestEveryCallSitePassesADeviceId:
176
176
  )
177
177
 
178
178
 
179
- class TestTheClassCharacterMustStayRandom:
180
- """Pinning field 2's first character broke the A000088, and this is the
181
- guard against it coming back.
182
-
183
- The A001064 really does read its client class from that character - the
184
- firmware, the Android app and the web app all agree. Generalising that to
185
- the whole fleet was tried on 2026-08-31 and reverted: against a drained
186
- fleet with Home Assistant stopped, three A000088 cameras went 0 for 3 over
187
- nine attempts, each completing its DTLS handshake in under two seconds and
188
- then receiving no media, while the same build streamed the A001064 on the
189
- first attempt. Reverting this one line restored 3 of 3.
190
-
191
- Nothing needs the pin: the cliff it was found while chasing is fixed in the
192
- SCTP receiver, not here.
179
+ class TestTheClassCharacterIsWeb:
180
+ """Field 2's first character announces the client class, and we announce
181
+ '2' (WEB).
182
+
183
+ The history matters, because the obvious reading of it is wrong. Pinning
184
+ this character to '0' (APP_ANDROID) was tried on 2026-08-31 and reverted:
185
+ against a drained fleet with Home Assistant stopped, three A000088 cameras
186
+ went 0 for 3 over nine attempts, each completing DTLS in under two seconds
187
+ and then receiving no media. That is real - across ten live-validation runs
188
+ the A000088 is 29 of 30 with a random character, so a 3% baseline cannot
189
+ explain 0 of 9.
190
+
191
+ But it was recorded as "pinning the class breaks the A000088", which is one
192
+ arm generalised into a rule. The vendor WEB app sends '2', and that arm was
193
+ screened on 2026-09-07 through live-validate - same sha, only the
194
+ environment differing - at **6 of 6 A000088 cameras across two independent
195
+ runs**, nearly all on the first attempt.
196
+
197
+ So the class is pinned to WEB, which is what a known-good vendor client
198
+ announces, instead of a random hex digit that was out of range 11 times in
199
+ 16. Class 2 is NOT one of the smart-home classes (3 and 4) that skip the
200
+ camera's keepalive watchdog, so this changes what we claim to be and
201
+ nothing about how the session is policed.
193
202
  """
194
203
 
195
- def test_field2_is_not_pinned_to_a_class(self, tmp_path, monkeypatch):
204
+ def test_field2_announces_client_class_2(self, tmp_path, monkeypatch):
196
205
  monkeypatch.setattr(client_mod, "EXPT_PEERID_FILE", None)
206
+ monkeypatch.delenv("AIDOT_EXPT_PEERID_CLASS", raising=False)
197
207
  firsts = {_gen(device_id=DEV).split("_")[1][0] for _ in range(120)}
198
- assert len(firsts) > 1, (
199
- "field 2's first character is pinned again - that is the client "
200
- "class, and pinning it fleet-wide stops the A000088 streaming")
208
+ assert firsts == {"2"}, (
209
+ "field 2's first character is the client class; it must announce "
210
+ f"WEB ('2'), got {sorted(firsts)}")
211
+
212
+ def test_the_rest_of_field2_stays_random(self, tmp_path, monkeypatch):
213
+ """Pinning all six would make every peer id identical across opens -
214
+ cross-session reuse, which the camera dedups on."""
215
+ monkeypatch.setattr(client_mod, "EXPT_PEERID_FILE", None)
216
+ monkeypatch.delenv("AIDOT_EXPT_PEERID_CLASS", raising=False)
217
+ tails = {_gen(device_id=DEV).split("_")[1][1:] for _ in range(40)}
218
+ assert len(tails) > 1, "field 2 is fully pinned - that is peer-id reuse"
201
219
 
202
220
  def test_field2_is_still_six_hex(self, tmp_path, monkeypatch):
203
221
  monkeypatch.setattr(client_mod, "EXPT_PEERID_FILE", None)
222
+ monkeypatch.delenv("AIDOT_EXPT_PEERID_CLASS", raising=False)
204
223
  for _ in range(20):
205
224
  f2 = _gen(device_id=DEV).split("_")[1]
206
225
  assert len(f2) == 6 and all(c in "0123456789abcdef" for c in f2)
@@ -211,21 +230,3 @@ class TestTheClassCharacterMustStayRandom:
211
230
  f = tmp_path / "pid"; f.write_text(f"{DEV}:4_2_0_1")
212
231
  monkeypatch.setattr(client_mod, "EXPT_PEERID_FILE", str(f))
213
232
  assert _gen(device_id=DEV).split("_")[1][0] == "4"
214
-
215
-
216
- class TestTheOverrideIsOffInAPublishedLibrary:
217
- """Same rule as the session cap: no env var, no file I/O."""
218
-
219
- def test_no_file_is_opened_when_the_knob_is_unset(self, monkeypatch):
220
- monkeypatch.setattr(client_mod, "EXPT_PEERID_FILE", None)
221
- opened = []
222
- real_open = open
223
-
224
- def _spy(*a, **k):
225
- opened.append(a[0] if a else None)
226
- return real_open(*a, **k)
227
-
228
- monkeypatch.setattr("builtins.open", _spy)
229
- pid = _gen(device_id=DEV)
230
- assert SHAPE.match(pid).groups() == ("2", "0", "1")
231
- assert opened == [], f"opened {opened!r} with the knob off"