python-aidot-cameras 1.0.0b31__tar.gz → 1.0.0b32__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.
- {python_aidot_cameras-1.0.0b31/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0b32}/PKG-INFO +1 -1
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/client.py +38 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sdes_open.py +104 -10
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/pyproject.toml +1 -1
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/python_aidot_cameras.egg-info/SOURCES.txt +2 -0
- python_aidot_cameras-1.0.0b32/tests/test_serve_input_timeout.py +65 -0
- python_aidot_cameras-1.0.0b32/tests/test_stall_nudge_policy.py +51 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/LICENSE +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/MANIFEST.in +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/README.md +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/__main__.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_upstream.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/ble_gateway.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/hwaccel.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/lan_control.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/playback.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/protocol.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sd_events.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sdes.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/tutk.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/webrtc_open.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/client.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/cloud_auth.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/device_client.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/lan_retry.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/setup.cfg +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_answer_sdp_video_pt.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_answer_section_selection.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_audio_mux_offline.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_avio_request.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_avio_response_parse.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_avio_response_router.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_battery_detection.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_battery_evidence_corroboration.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_battery_skip_turn.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_bitrate_campaign_arms.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_ble_gateway.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_bridge_state_sentinels.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_busy_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_canary_log_names_the_camera.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_cloud_count.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_cloud_plan.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_constants_imports_resolve.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_control_verdicts.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_device_cmd_seq_match.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_direct_ts_serve.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dormant_camera_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_drain_is_cancellable.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_mux_terminal_write.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_resolution_ack.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_serve_ice_wait.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_stdout_producer.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_dtls_video_presence.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_env_seams.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_feature_probe_needs_a_live_session.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_futile_keepalive.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_gate_requires_a_decoded_frame.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_hwaccel_probe.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_ice_peer_reflexive.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_idle_release_both_loops.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_key_restart_sdp.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_late_repeats_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_light_active_color_mode.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_live_session_carries_talk.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_live_validate_policy.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_live_video_canary.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_local_only_controls.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_login_connect_deadline.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_login_reconnect_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_media_counted_only_when_plaintext.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_media_path_is_reported.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_media_socket_rcvbuf.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_media_wait_floor.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_mqtt_client_id_is_exact.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_mqtt_thread_not_orphaned.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_nack.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_no_key_material_in_logs.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_no_self_referential_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_offer_bandwidth_line.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_on_frame_is_an_av_frame.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_peerid_reuse.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_public_signature_matches_impl.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_quality_campaign_in_session.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_recent_recordings.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_remb.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_remb_goes_out_the_relay_aware_sender.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_resent_frames_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_resolution_persists.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_restart_cycle.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_scripts_importable.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sctp_init_ack_tsn.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sd_card_presence.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sd_event_decode.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sd_event_probe_payloads.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_avio_dispatch.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_bridge_break_gate.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_connection_mode.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_first_media_stall_report.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_nack_bridge.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_offer_video_order.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_offer_video_pin.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_sctp_abort.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_serve_stderr_drain.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_teardown_exit_log.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_terminal_ack_latency.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_tmmbr_bridge.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdp_narrowing_properties.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sdp_payload_type_narrowing.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_serve_loss_tolerance.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_serve_pt_respects_the_pin.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_shared_house_seam.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_snapshot_cancellation.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_speak.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sprop_capture_gate.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sprop_staleness.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sprop_unstable_public.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_srtp_rx_rekey.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_srtp_tx_key_note.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stall_reports_reach_the_artifact.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stream_slot_cap.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_talk.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_tap_to_mux_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_tmmbr.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_turn_create_permission.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_tutk_path_is_out_of_reach.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_validator_shares_one_token.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_video_profile_log.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_video_pts_dts.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_video_ts_unwrap.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_viewer_check_throttle.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_viewer_presence.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_viewer_present_push_mode.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_wait_or_event.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_wake_gate_camera_evidence.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_wake_readiness_retry.py +0 -0
- {python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/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.
|
|
3
|
+
Version: 1.0.0b32
|
|
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
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/client.py
RENAMED
|
@@ -566,6 +566,36 @@ _SERVE_REORDER_QUEUE = int(os.environ.get("AIDOT_SERVE_REORDER_QUEUE", "500"))
|
|
|
566
566
|
#: failure this is meant to prevent.
|
|
567
567
|
_SERVE_MAX_DELAY_US = int(os.environ.get("AIDOT_SERVE_MAX_DELAY_US", "500000"))
|
|
568
568
|
|
|
569
|
+
#: How long (seconds) the serve ffmpeg waits with NO packets on any RTP socket
|
|
570
|
+
#: before its SDP input errors out ("Error during demuxing: Operation timed
|
|
571
|
+
#: out") and the serve dies. ffmpeg's own default is 10 s (READ_PACKET_TIMEOUT_S,
|
|
572
|
+
#: applied through the sdp demuxer's ``listen_timeout`` option). Measured on an
|
|
573
|
+
#: A001064 (2026-08-24/25): the camera episodically stops transmitting for
|
|
574
|
+
#: >=10 s mid-session, and every reopen afterwards succeeds immediately, so the
|
|
575
|
+
#: link recovers on a 10-20 s scale. A mains camera therefore gets 30 s to ride
|
|
576
|
+
#: the gap out; a battery camera keeps ffmpeg's 10 s default, because its stops
|
|
577
|
+
#: are real sleeps that only a reopen (with its wake) ends - waiting longer just
|
|
578
|
+
#: freezes the picture. ``AIDOT_SERVE_INPUT_TIMEOUT_S`` overrides both.
|
|
579
|
+
_SERVE_INPUT_TIMEOUT_MAINS_S = 30
|
|
580
|
+
_SERVE_INPUT_TIMEOUT_BATTERY_S = 10
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _resolve_serve_input_timeout_s(is_battery: bool) -> int:
|
|
584
|
+
"""Seconds the serve's SDP input tolerates total silence before erroring.
|
|
585
|
+
|
|
586
|
+
Pure policy (unit-testable): the env override wins for every camera; else
|
|
587
|
+
mains rides out a transmit gap and battery keeps the fast default.
|
|
588
|
+
"""
|
|
589
|
+
_env = os.environ.get("AIDOT_SERVE_INPUT_TIMEOUT_S")
|
|
590
|
+
if _env:
|
|
591
|
+
try:
|
|
592
|
+
return max(1, int(_env))
|
|
593
|
+
except ValueError:
|
|
594
|
+
pass
|
|
595
|
+
return (_SERVE_INPUT_TIMEOUT_BATTERY_S if is_battery
|
|
596
|
+
else _SERVE_INPUT_TIMEOUT_MAINS_S)
|
|
597
|
+
|
|
598
|
+
|
|
569
599
|
#: How long to wait after a camera answers -50002 / -50015 ("no free session").
|
|
570
600
|
#: Both loops used to wait _MAX_DELAY (300s) on the belief that the camera
|
|
571
601
|
#: "releases slowly". Measured 2026-08-07 on an A001064: reopening 2s after a
|
|
@@ -630,6 +660,7 @@ def _build_sdes_serve_cmd(
|
|
|
630
660
|
audio_gain_db: float = -8.0,
|
|
631
661
|
push_video_only: bool = False,
|
|
632
662
|
video_decoder: Optional[list] = None,
|
|
663
|
+
input_timeout_s: Optional[int] = None,
|
|
633
664
|
) -> list:
|
|
634
665
|
"""Build the ffmpeg argv for the SDES bridge serve.
|
|
635
666
|
|
|
@@ -778,6 +809,13 @@ def _build_sdes_serve_cmd(
|
|
|
778
809
|
# before giving up on a gap rather than stalling the pipeline.
|
|
779
810
|
"-reorder_queue_size", str(_SERVE_REORDER_QUEUE),
|
|
780
811
|
"-max_delay", str(_SERVE_MAX_DELAY_US),
|
|
812
|
+
# Input silence tolerance: the sdp demuxer's ``listen_timeout`` doubles
|
|
813
|
+
# as its packet-read timeout (READ_PACKET_TIMEOUT_S, ffmpeg default
|
|
814
|
+
# 10 s). See _resolve_serve_input_timeout_s for the mains/battery
|
|
815
|
+
# policy. Input option, so it must sit before -i; None keeps ffmpeg's
|
|
816
|
+
# default (no behavior change for callers that do not opt in).
|
|
817
|
+
*(["-listen_timeout", str(int(input_timeout_s))]
|
|
818
|
+
if input_timeout_s is not None else []),
|
|
781
819
|
"-i", sdp_path,
|
|
782
820
|
*dest_args,
|
|
783
821
|
]
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sdes_open.py
RENAMED
|
@@ -1218,6 +1218,42 @@ def _classify_ffmpeg_exit(rc: int, teardown_requested: bool) -> int:
|
|
|
1218
1218
|
return logging.WARNING
|
|
1219
1219
|
|
|
1220
1220
|
|
|
1221
|
+
#: Mid-session stall nudge (see _stall_nudge_due): enabled by default;
|
|
1222
|
+
#: ``AIDOT_SDES_STALL_NUDGE=0`` turns it off and
|
|
1223
|
+
#: ``AIDOT_SDES_STALL_NUDGE_AFTER_S`` moves the trigger point.
|
|
1224
|
+
_STALL_NUDGE_ENABLED = os.environ.get("AIDOT_SDES_STALL_NUDGE", "1") != "0"
|
|
1225
|
+
_STALL_NUDGE_AFTER_S = float(
|
|
1226
|
+
os.environ.get("AIDOT_SDES_STALL_NUDGE_AFTER_S", "2.5"))
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
def _stall_nudge_due(
|
|
1230
|
+
*,
|
|
1231
|
+
silence_s: float,
|
|
1232
|
+
nudges_sent: int,
|
|
1233
|
+
since_last_nudge_s: float,
|
|
1234
|
+
stall_after_s: float = 2.5,
|
|
1235
|
+
max_nudges: int = 3,
|
|
1236
|
+
interval_s: float = 2.0,
|
|
1237
|
+
) -> bool:
|
|
1238
|
+
"""Whether the bridge should re-send AVIO LIVING for a mid-session stall.
|
|
1239
|
+
|
|
1240
|
+
The A001064 episodically stops transmitting mid-session with no teardown
|
|
1241
|
+
signal; every reopen afterwards succeeds immediately, so the camera is
|
|
1242
|
+
awake and answering handshakes seconds after it stopped (measured
|
|
1243
|
+
2026-08-24/25). LIVING is the message that starts media on a fresh
|
|
1244
|
+
session, so re-sending it on the live session is the cheapest possible
|
|
1245
|
+
revive attempt. Bounded (a few sends, spaced out) so a camera that is
|
|
1246
|
+
truly asleep or out of range is not spammed - the serve's input timeout
|
|
1247
|
+
and the keepalive reopen remain the unchanged fallback. Pure function so
|
|
1248
|
+
the policy is unit-testable without a live bridge.
|
|
1249
|
+
"""
|
|
1250
|
+
if silence_s <= stall_after_s:
|
|
1251
|
+
return False
|
|
1252
|
+
if nudges_sent >= max_nudges:
|
|
1253
|
+
return False
|
|
1254
|
+
return since_last_nudge_s >= interval_s
|
|
1255
|
+
|
|
1256
|
+
|
|
1221
1257
|
def _bridge_should_break(rc, teardown_requested: bool) -> bool:
|
|
1222
1258
|
"""Whether the bridge observe loop should end on this poll() result.
|
|
1223
1259
|
|
|
@@ -1623,7 +1659,7 @@ class _SdesOpenMixin:
|
|
|
1623
1659
|
SDP answer, writes it to a temp file, and launches ffmpeg to receive and
|
|
1624
1660
|
record the SRTP stream.
|
|
1625
1661
|
"""
|
|
1626
|
-
from .client import CameraMixin, _build_sdes_serve_cmd, _ffmpeg_path, _spawn_bg # lazy: break client<->sdes_open cycle
|
|
1662
|
+
from .client import CameraMixin, _build_sdes_serve_cmd, _ffmpeg_path, _resolve_serve_input_timeout_s, _spawn_bg # lazy: break client<->sdes_open cycle
|
|
1627
1663
|
import base64
|
|
1628
1664
|
import subprocess
|
|
1629
1665
|
|
|
@@ -4099,6 +4135,9 @@ class _SdesOpenMixin:
|
|
|
4099
4135
|
_trigger_peer = None # camera's real addr when relayed
|
|
4100
4136
|
_sdes_probe_received = False # True after first 0xC8 probe from camera
|
|
4101
4137
|
_last_hb_ts = 0.0 # time of last AVIO HEARTBEAT send
|
|
4138
|
+
_stall_nudges_sent = 0 # LIVING re-sends for the CURRENT stall
|
|
4139
|
+
_stall_last_nudge = 0.0 # wall time of the last stall nudge
|
|
4140
|
+
_stall_active = False # media had started, then went silent
|
|
4102
4141
|
# One-shot guard for the "ffmpeg exited" log below: while the held
|
|
4103
4142
|
# proc keeps reporting the same stale exit code across a
|
|
4104
4143
|
# teardown-window skip (see _bridge_should_break), only the first
|
|
@@ -4537,12 +4576,17 @@ class _SdesOpenMixin:
|
|
|
4537
4576
|
except Exception as _dw_e:
|
|
4538
4577
|
_status(f"SDES DC: DCEP_WAIT LIVING err: {_dw_e}")
|
|
4539
4578
|
|
|
4540
|
-
#
|
|
4541
|
-
#
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4579
|
+
# One LIVING wire-build, shared by the pre-probe retrigger
|
|
4580
|
+
# below and the mid-session stall nudge: resend the AVIO
|
|
4581
|
+
# LIVING(5376) that starts media, encrypted then plaintext
|
|
4582
|
+
# exactly like the original trigger.
|
|
4583
|
+
# Default-arg binding (not closure capture): re-defined each
|
|
4584
|
+
# tick, so the CURRENT trigger socket/addrs are bound at
|
|
4585
|
+
# definition time and B023 cannot bite.
|
|
4586
|
+
def _resend_avio_living(
|
|
4587
|
+
_tb=_trigger_bs,
|
|
4588
|
+
_tsrc=_trigger_bsrc,
|
|
4589
|
+
_tpeer=_trigger_peer) -> None:
|
|
4546
4590
|
import struct as _st_re2
|
|
4547
4591
|
import random as _r_re2
|
|
4548
4592
|
_re_ts = int(_time_br.time() * 1000)
|
|
@@ -4568,15 +4612,63 @@ class _SdesOpenMixin:
|
|
|
4568
4612
|
_rsz = len(_rp)
|
|
4569
4613
|
try:
|
|
4570
4614
|
_br_send_to_cam(
|
|
4571
|
-
|
|
4615
|
+
_tb,
|
|
4572
4616
|
bytes([0xC8, 0x00, _rsz >> 8, _rsz & 0xFF]) + _rp,
|
|
4573
|
-
|
|
4574
|
-
|
|
4617
|
+
_tsrc,
|
|
4618
|
+
_tpeer,
|
|
4575
4619
|
)
|
|
4576
4620
|
except Exception:
|
|
4577
4621
|
_LOGGER.debug("camera %s: swallowed exception in %s", getattr(self, "device_id", "?"), '_bridge_fn', exc_info=True)
|
|
4622
|
+
|
|
4623
|
+
# Periodic retrigger: resend AVIO LIVING every 2s until probe
|
|
4624
|
+
# received (camera acknowledged our trigger).
|
|
4625
|
+
if (_avio_living_sent
|
|
4626
|
+
and not _sdes_probe_received
|
|
4627
|
+
and _trigger_bs is not None
|
|
4628
|
+
and _time_br.time() - _last_trigger_ts >= 2.0):
|
|
4629
|
+
_resend_avio_living()
|
|
4578
4630
|
_last_trigger_ts = _time_br.time()
|
|
4579
4631
|
|
|
4632
|
+
# Mid-session stall nudge: the camera episodically stops
|
|
4633
|
+
# transmitting with no teardown signal (A001064, measured
|
|
4634
|
+
# 2026-08-24/25), the serve's input then times out and the
|
|
4635
|
+
# session dies - yet the camera answers a FRESH handshake
|
|
4636
|
+
# immediately afterwards. So before letting it die, re-send
|
|
4637
|
+
# the LIVING that starts media. The AVIO HEARTBEAT alone
|
|
4638
|
+
# does not do this: heartbeats kept flowing right through
|
|
4639
|
+
# measured stalls while the media stayed silent. Bounded by
|
|
4640
|
+
# _stall_nudge_due; the input timeout and the keepalive
|
|
4641
|
+
# reopen remain the unchanged fallback.
|
|
4642
|
+
if (_STALL_NUDGE_ENABLED
|
|
4643
|
+
and _sdes_probe_received
|
|
4644
|
+
and _trigger_bs is not None
|
|
4645
|
+
and _media_progress[0] > 0.0):
|
|
4646
|
+
_stall_silence = (
|
|
4647
|
+
_time_br.monotonic() - _media_progress[0])
|
|
4648
|
+
if _stall_silence <= _STALL_NUDGE_AFTER_S:
|
|
4649
|
+
if _stall_active:
|
|
4650
|
+
_stall_active = False
|
|
4651
|
+
_stall_nudges_sent = 0
|
|
4652
|
+
# Ask for an immediate IDR so recovery is not
|
|
4653
|
+
# hostage to the 30s PLI cadence.
|
|
4654
|
+
_bridge_fn._last_pli_ts = 0.0
|
|
4655
|
+
_status(
|
|
4656
|
+
"SDES: media resumed after stall"
|
|
4657
|
+
" - PLI re-armed")
|
|
4658
|
+
elif _stall_nudge_due(
|
|
4659
|
+
silence_s=_stall_silence,
|
|
4660
|
+
nudges_sent=_stall_nudges_sent,
|
|
4661
|
+
since_last_nudge_s=(
|
|
4662
|
+
_time_br.time() - _stall_last_nudge),
|
|
4663
|
+
stall_after_s=_STALL_NUDGE_AFTER_S):
|
|
4664
|
+
_stall_active = True
|
|
4665
|
+
_stall_nudges_sent += 1
|
|
4666
|
+
_stall_last_nudge = _time_br.time()
|
|
4667
|
+
_status(
|
|
4668
|
+
f"SDES: no media for {_stall_silence:.1f}s"
|
|
4669
|
+
f" - LIVING nudge {_stall_nudges_sent}/3")
|
|
4670
|
+
_resend_avio_living()
|
|
4671
|
+
|
|
4580
4672
|
for _bs in _rl:
|
|
4581
4673
|
try:
|
|
4582
4674
|
_bpkt, _bsrc = _bs.recvfrom(4096)
|
|
@@ -6278,6 +6370,8 @@ class _SdesOpenMixin:
|
|
|
6278
6370
|
audio_gain_db=self._resolve_sdes_audio_gain_db(),
|
|
6279
6371
|
push_video_only=_push_video_only,
|
|
6280
6372
|
video_decoder=_video_decoder,
|
|
6373
|
+
input_timeout_s=_resolve_serve_input_timeout_s(
|
|
6374
|
+
bool(getattr(self, "is_battery_camera", False))),
|
|
6281
6375
|
)
|
|
6282
6376
|
# Audio matters even more than video here. A multi-PT m-line makes ffmpeg
|
|
6283
6377
|
# bind the depacketizer to the FIRST payload type and silently discard the
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-aidot-cameras"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0b32"
|
|
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.
|
|
3
|
+
Version: 1.0.0b32
|
|
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
|
|
@@ -218,6 +218,7 @@ tests/test_sdp_payload_type_narrowing.py
|
|
|
218
218
|
tests/test_security_hardening.py
|
|
219
219
|
tests/test_self_peer_is_an_address_not_an_ip.py
|
|
220
220
|
tests/test_serializable_login_info.py
|
|
221
|
+
tests/test_serve_input_timeout.py
|
|
221
222
|
tests/test_serve_loss_tolerance.py
|
|
222
223
|
tests/test_serve_pt_respects_the_pin.py
|
|
223
224
|
tests/test_serve_relay.py
|
|
@@ -232,6 +233,7 @@ tests/test_sprop_unstable_public.py
|
|
|
232
233
|
tests/test_sprop_write_is_reported_truthfully.py
|
|
233
234
|
tests/test_srtp_rx_rekey.py
|
|
234
235
|
tests/test_srtp_tx_key_note.py
|
|
236
|
+
tests/test_stall_nudge_policy.py
|
|
235
237
|
tests/test_stall_report_says_what_the_answer_carried.py
|
|
236
238
|
tests/test_stall_report_says_whether_the_trigger_was_answered.py
|
|
237
239
|
tests/test_stall_report_separates_silence_from_undecryptable.py
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Unit tests for the serve's SDP-input silence tolerance.
|
|
2
|
+
|
|
3
|
+
The sdp demuxer's ``listen_timeout`` doubles as its packet-read timeout
|
|
4
|
+
(ffmpeg READ_PACKET_TIMEOUT_S, default 10 s): 10 s with no packets on any RTP
|
|
5
|
+
socket errors the input ("Error during demuxing: Operation timed out") and the
|
|
6
|
+
serve dies. Measured on an A001064, the camera episodically stops
|
|
7
|
+
transmitting for >=10 s mid-session and the link recovers on a 10-20 s scale,
|
|
8
|
+
so a mains camera rides the gap out with a longer window while a battery
|
|
9
|
+
camera keeps the fast default (its stops are real sleeps that only a reopen
|
|
10
|
+
ends).
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
16
|
+
|
|
17
|
+
from aidot_cameras.camera.client import ( # noqa: E402
|
|
18
|
+
_SERVE_INPUT_TIMEOUT_BATTERY_S,
|
|
19
|
+
_SERVE_INPUT_TIMEOUT_MAINS_S,
|
|
20
|
+
_build_sdes_serve_cmd as build,
|
|
21
|
+
_resolve_serve_input_timeout_s as resolve,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_mains_gets_the_long_window():
|
|
26
|
+
assert resolve(False) == _SERVE_INPUT_TIMEOUT_MAINS_S == 30
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_battery_keeps_ffmpeg_default():
|
|
30
|
+
# 10 is ffmpeg's own READ_PACKET_TIMEOUT_S: passing it changes nothing for
|
|
31
|
+
# battery cameras, whose mid-session stops are real sleeps.
|
|
32
|
+
assert resolve(True) == _SERVE_INPUT_TIMEOUT_BATTERY_S == 10
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_env_override_wins_for_both(monkeypatch):
|
|
36
|
+
monkeypatch.setenv("AIDOT_SERVE_INPUT_TIMEOUT_S", "45")
|
|
37
|
+
assert resolve(False) == 45
|
|
38
|
+
assert resolve(True) == 45
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_env_override_garbage_falls_back(monkeypatch):
|
|
42
|
+
monkeypatch.setenv("AIDOT_SERVE_INPUT_TIMEOUT_S", "soon")
|
|
43
|
+
assert resolve(False) == _SERVE_INPUT_TIMEOUT_MAINS_S
|
|
44
|
+
assert resolve(True) == _SERVE_INPUT_TIMEOUT_BATTERY_S
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_env_override_is_floored_at_one_second(monkeypatch):
|
|
48
|
+
monkeypatch.setenv("AIDOT_SERVE_INPUT_TIMEOUT_S", "0")
|
|
49
|
+
assert resolve(False) == 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_builder_places_listen_timeout_before_input():
|
|
53
|
+
cmd = build(sdp_path="/x.sdp", rtsp_push_url="rtsp://127.0.0.1:8554/cam",
|
|
54
|
+
input_timeout_s=30)
|
|
55
|
+
i = cmd.index("-listen_timeout")
|
|
56
|
+
assert cmd[i + 1] == "30"
|
|
57
|
+
# Input option: must precede -i, or ffmpeg reads it as an output option.
|
|
58
|
+
assert i < cmd.index("-i")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_builder_default_is_unchanged_behavior():
|
|
62
|
+
# None -> no flag at all, so callers that do not opt in keep ffmpeg's own
|
|
63
|
+
# default (and the argv stays byte-identical to pre-change builds).
|
|
64
|
+
cmd = build(sdp_path="/x.sdp", rtsp_push_url="rtsp://127.0.0.1:8554/cam")
|
|
65
|
+
assert "-listen_timeout" not in cmd
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Unit tests for _stall_nudge_due - the mid-session stall-nudge policy.
|
|
2
|
+
|
|
3
|
+
The A001064 episodically stops transmitting mid-session with no teardown
|
|
4
|
+
signal, yet answers a fresh handshake immediately afterwards. The bridge
|
|
5
|
+
therefore re-sends the AVIO LIVING that starts media before letting the
|
|
6
|
+
serve's input timeout kill the session. This policy decides when: bounded
|
|
7
|
+
sends, spaced out, and never before a real stall threshold - the input
|
|
8
|
+
timeout and the keepalive reopen remain the fallback.
|
|
9
|
+
"""
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
14
|
+
|
|
15
|
+
from aidot_cameras.camera.sdes_open import _stall_nudge_due as due # noqa: E402
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_no_nudge_while_media_flows():
|
|
19
|
+
assert not due(silence_s=0.4, nudges_sent=0, since_last_nudge_s=99.0)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_no_nudge_at_or_below_threshold():
|
|
23
|
+
assert not due(silence_s=2.5, nudges_sent=0, since_last_nudge_s=99.0)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_first_nudge_past_threshold():
|
|
27
|
+
assert due(silence_s=2.6, nudges_sent=0, since_last_nudge_s=99.0)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_nudges_are_spaced():
|
|
31
|
+
# 1 s after the previous nudge: too soon; 2 s: due again.
|
|
32
|
+
assert not due(silence_s=5.0, nudges_sent=1, since_last_nudge_s=1.0)
|
|
33
|
+
assert due(silence_s=5.0, nudges_sent=1, since_last_nudge_s=2.0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_nudges_are_bounded():
|
|
37
|
+
# After max_nudges the camera is presumed truly asleep/out of range and
|
|
38
|
+
# the fallback path (input timeout -> reopen) takes over unmolested.
|
|
39
|
+
assert not due(silence_s=60.0, nudges_sent=3, since_last_nudge_s=99.0)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_threshold_is_parameterised():
|
|
43
|
+
assert due(silence_s=1.1, nudges_sent=0, since_last_nudge_s=99.0,
|
|
44
|
+
stall_after_s=1.0)
|
|
45
|
+
assert not due(silence_s=1.1, nudges_sent=0, since_last_nudge_s=99.0,
|
|
46
|
+
stall_after_s=2.5)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_max_is_parameterised():
|
|
50
|
+
assert due(silence_s=60.0, nudges_sent=3, since_last_nudge_s=99.0,
|
|
51
|
+
max_nudges=5)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/rtp.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/_vendor/aiortc/sdp.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/ble_gateway.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/constants.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/controls.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/go2rtc.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/hwaccel.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/lan_control.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/models.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/playback.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/protocol.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sd_events.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sd_listing.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sd_playback.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/sdes.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/tutk.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/webrtc.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/camera/webrtc_open.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/credentials.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/aidot_cameras/device_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_answer_sdp_video_pt.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_audio_mux_offline.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_avio_response_parse.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_avio_response_router.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_battery_detection.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_battery_skip_turn.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_bitrate_campaign_arms.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0b31 → python_aidot_cameras-1.0.0b32}/tests/test_bridge_state_sentinels.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|