python-aidot-cameras 1.0.0b33__tar.gz → 1.0.0b34__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.0b33/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0b34}/PKG-INFO +1 -1
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/client.py +223 -13
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/protocol.py +26 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/sdes.py +14 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/sdes_open.py +250 -29
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/webrtc_open.py +1 -1
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/pyproject.toml +1 -1
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/python_aidot_cameras.egg-info/SOURCES.txt +6 -0
- python_aidot_cameras-1.0.0b34/tests/test_peer_id_client_type.py +231 -0
- python_aidot_cameras-1.0.0b34/tests/test_sctp_reassembly.py +102 -0
- python_aidot_cameras-1.0.0b34/tests/test_sctp_sack.py +94 -0
- python_aidot_cameras-1.0.0b34/tests/test_sdes_ice_teardown.py +73 -0
- python_aidot_cameras-1.0.0b34/tests/test_sdes_session_cap.py +118 -0
- python_aidot_cameras-1.0.0b34/tests/test_stable_terminal_id.py +64 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/LICENSE +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/MANIFEST.in +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/README.md +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/__main__.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_upstream.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/ble_gateway.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/hwaccel.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/lan_control.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/playback.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/sd_events.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/tutk.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/client.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/cloud_auth.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/device_client.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/lan_retry.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/setup.cfg +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_answer_pt_kinds.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_answer_sdp_video_pt.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_answer_section_selection.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_audio_mux_offline.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_avio_request.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_avio_response_parse.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_avio_response_router.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_battery_detection.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_battery_evidence_corroboration.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_battery_skip_turn.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_bitrate_campaign_arms.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_ble_gateway.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_bridge_state_sentinels.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_busy_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_canary_log_names_the_camera.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_cloud_count.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_cloud_plan.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_constants_imports_resolve.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_control_verdicts.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_device_cmd_seq_match.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_direct_ts_serve.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dormant_camera_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_drain_is_cancellable.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_mux_terminal_write.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_resolution_ack.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_serve_ice_wait.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_stdout_producer.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_dtls_video_presence.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_env_seams.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_feature_probe_needs_a_live_session.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_futile_keepalive.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_gate_requires_a_decoded_frame.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_hwaccel_probe.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_ice_peer_reflexive.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_idle_release_both_loops.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_key_restart_sdp.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_late_repeats_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_light_active_color_mode.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_live_session_carries_talk.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_live_validate_policy.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_live_video_canary.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_local_only_controls.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_login_connect_deadline.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_login_reconnect_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_media_counted_only_when_plaintext.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_media_path_is_reported.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_media_socket_rcvbuf.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_media_wait_floor.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_mqtt_client_id_is_exact.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_mqtt_thread_not_orphaned.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_nack.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_no_key_material_in_logs.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_no_self_referential_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_offer_bandwidth_line.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_on_frame_is_an_av_frame.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_peerid_reuse.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_public_signature_matches_impl.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_quality_campaign_in_session.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_recent_recordings.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_remb.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_remb_goes_out_the_relay_aware_sender.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_resent_frames_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_resolution_persists.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_restart_cycle.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_scripts_importable.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sctp_init_ack_tsn.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sd_card_presence.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sd_event_decode.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sd_event_probe_payloads.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_avio_dispatch.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_bridge_break_gate.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_connection_mode.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_first_media_stall_report.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_nack_bridge.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_offer_video_order.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_offer_video_pin.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_sctp_abort.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_serve_stderr_drain.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_teardown_exit_log.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_terminal_ack_latency.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_tmmbr_bridge.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdp_narrowing_properties.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sdp_payload_type_narrowing.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_serve_input_timeout.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_serve_loss_tolerance.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_serve_pt_respects_the_pin.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_shared_house_seam.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_snapshot_cancellation.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_speak.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sprop_capture_gate.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sprop_staleness.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sprop_unstable_public.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_srtp_rx_rekey.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_srtp_tx_key_note.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stall_nudge_policy.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stall_reports_reach_the_artifact.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stream_slot_cap.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_talk.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_tap_to_mux_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_tmmbr.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_turn_create_permission.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_tutk_path_is_out_of_reach.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_validator_shares_one_token.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_video_profile_log.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_video_pts_dts.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_video_ts_unwrap.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_viewer_check_throttle.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_viewer_presence.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_viewer_present_push_mode.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_wait_or_event.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_wake_gate_camera_evidence.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/tests/test_wake_readiness_retry.py +0 -0
- {python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/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.0b34
|
|
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.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/client.py
RENAMED
|
@@ -52,7 +52,11 @@ from .sdes import SdesSession # re-exported (split into sdes.py); also the SDES
|
|
|
52
52
|
from .controls import _CameraControlsMixin
|
|
53
53
|
from .sd_listing import _CameraSdMixin
|
|
54
54
|
from .webrtc_open import _WebRTCOpenMixin
|
|
55
|
-
from .sdes_open import
|
|
55
|
+
from .sdes_open import (
|
|
56
|
+
_SdesOpenMixin,
|
|
57
|
+
_sdes_max_session_s,
|
|
58
|
+
_session_cap_reached,
|
|
59
|
+
)
|
|
56
60
|
from .protocol import ( # noqa: F401 - used here and/or by the webrtc_open mixin; kept re-exported for back-compat
|
|
57
61
|
_mqtt_timestamp,
|
|
58
62
|
ReconnectPacer,
|
|
@@ -71,6 +75,7 @@ from .protocol import ( # noqa: F401 - used here and/or by the webrtc_open mixi
|
|
|
71
75
|
_save_sprop,
|
|
72
76
|
_inject_sprop,
|
|
73
77
|
_sdes_serve_port,
|
|
78
|
+
sdes_ice_teardown,
|
|
74
79
|
_serve_host,
|
|
75
80
|
_warn_lan_serve,
|
|
76
81
|
_ServeRelay,
|
|
@@ -1120,6 +1125,118 @@ def _ack_matches_seq(msg, seq):
|
|
|
1120
1125
|
return code if isinstance(code, int) else False
|
|
1121
1126
|
|
|
1122
1127
|
|
|
1128
|
+
_TERMINAL_ID: "Optional[str]" = None
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
def _stable_terminal_id(seed: "Optional[str]" = None, width: int = 6) -> str:
|
|
1132
|
+
"""The six-hex terminal field of our peer ids. Stable, not per-open random.
|
|
1133
|
+
|
|
1134
|
+
The camera parses the peer id as ``<session>_<terminal_id>_%d_%d_%d`` and
|
|
1135
|
+
compares the terminal id against the sessions it already has -
|
|
1136
|
+
``rtc_session_check_same_terminal_id`` / "rtc session terminal id is same",
|
|
1137
|
+
with paired ``new_terminal_id`` / ``old_terminal_id`` logs. So the terminal
|
|
1138
|
+
id is how it recognises a client it has seen before.
|
|
1139
|
+
|
|
1140
|
+
We were minting six fresh random hex digits per open: 329 distinct terminal
|
|
1141
|
+
ids across 836 opens in 19 hours, so every reconnect looked like a brand-new
|
|
1142
|
+
client and the camera's "same terminal" path could never fire for us. A
|
|
1143
|
+
client that reconnects every couple of minutes should look like one client
|
|
1144
|
+
reconnecting.
|
|
1145
|
+
|
|
1146
|
+
Stable for the life of the process. ``seed`` makes it deterministic for
|
|
1147
|
+
tests and would allow deriving it from a durable identity later; without one
|
|
1148
|
+
it is random per process, which is already the property that matters.
|
|
1149
|
+
"""
|
|
1150
|
+
global _TERMINAL_ID
|
|
1151
|
+
if seed is not None:
|
|
1152
|
+
import hashlib
|
|
1153
|
+
return hashlib.sha256(seed.encode()).hexdigest()[:width]
|
|
1154
|
+
if _TERMINAL_ID is None:
|
|
1155
|
+
import os
|
|
1156
|
+
_TERMINAL_ID = os.urandom(16).hex()
|
|
1157
|
+
return _TERMINAL_ID[:width]
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
#: Measurement scaffolding, OFF unless an operator names a file - see the note
|
|
1161
|
+
#: on EXPT_CAP_FILE. A published library must not open a Home Assistant config
|
|
1162
|
+
#: path on the event loop, and with no env var set this knob does no I/O at all.
|
|
1163
|
+
EXPT_PEERID_FILE = os.environ.get("AIDOT_EXPT_PEERID_FILE")
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
def _expt_peer_id_fields(device_id=None, path=None):
|
|
1167
|
+
"""Experiment override for one device's peer-id fields, or None.
|
|
1168
|
+
|
|
1169
|
+
The camera parses the peer id with ``%[^_]_%[^_]_%d_%d_%d`` (`parse_peer_id`
|
|
1170
|
+
at 0x298ef8 in the A001064 image) and takes its client class from the FIRST
|
|
1171
|
+
CHARACTER of field 2: ``client_type = field2[0] - '0'``, against
|
|
1172
|
+
``EN_WEBRTC_CLIENT_TYPE_{APP_ANDROID,APP_IOS,WEB,ALEXA,GOOGLE_HOME}`` = 0..4.
|
|
1173
|
+
The vendor app hard-codes a leading ``'0'`` there; we generate six random hex
|
|
1174
|
+
digits, so we announce a uniformly random class and an out-of-range one 11
|
|
1175
|
+
times in 16. This exists to pin that character on the live camera.
|
|
1176
|
+
|
|
1177
|
+
Scoped to ONE device, like ``_sdes_max_session_s``. The scoping is not
|
|
1178
|
+
cosmetic: this override also sets the three trailing integers, and an
|
|
1179
|
+
unscoped file would push an SDES ``_2_0_1`` tail at the DTLS cameras, which
|
|
1180
|
+
silently discard a peer id with the wrong transport digit. An earlier
|
|
1181
|
+
unscoped experiment knob hit a battery camera and forced it to ~30x its
|
|
1182
|
+
normal wake rate.
|
|
1183
|
+
|
|
1184
|
+
File format, one line::
|
|
1185
|
+
|
|
1186
|
+
<device_id>:[<terminal>_]<live_type>_<stream_id>_<version>
|
|
1187
|
+
|
|
1188
|
+
Three integers set the integers only. A leading terminal also pins field 2,
|
|
1189
|
+
the field that carries the client class, and comes in two widths:
|
|
1190
|
+
|
|
1191
|
+
* **one hex character** - pins only field 2's FIRST character (the class)
|
|
1192
|
+
and leaves the other five random. This is the arm you almost always
|
|
1193
|
+
want: it reproduces the observed condition, "class is 4, tail varies",
|
|
1194
|
+
without also making every session's peer id identical.
|
|
1195
|
+
* **six hex characters** - pins the whole of field 2. Field 1 is already
|
|
1196
|
+
stable per process, so this makes every peer id byte-identical across
|
|
1197
|
+
opens, which is cross-session peer-id REUSE. The camera has a
|
|
1198
|
+
session-dedup path keyed on the peer id, so a result obtained this way
|
|
1199
|
+
cannot separate the client class from the reuse. Use it only when reuse
|
|
1200
|
+
is the thing under test.
|
|
1201
|
+
|
|
1202
|
+
FAILS CLOSED on anything it cannot parse, on a device mismatch, and on a
|
|
1203
|
+
caller that passes no device id. A malformed peer id is rejected by the
|
|
1204
|
+
camera outright, so a half-written override file must never reach the wire.
|
|
1205
|
+
"""
|
|
1206
|
+
if not device_id:
|
|
1207
|
+
return None
|
|
1208
|
+
path = path if path is not None else EXPT_PEERID_FILE
|
|
1209
|
+
if not path:
|
|
1210
|
+
return None
|
|
1211
|
+
try:
|
|
1212
|
+
with open(path) as _fh:
|
|
1213
|
+
raw = _fh.read().strip()
|
|
1214
|
+
except OSError:
|
|
1215
|
+
return None
|
|
1216
|
+
head, sep, tail = raw.partition(":")
|
|
1217
|
+
if not sep or head.strip() != device_id:
|
|
1218
|
+
return None
|
|
1219
|
+
parts = tail.strip().split("_")
|
|
1220
|
+
# Three fields sets the integers only; four also pins the terminal id, so
|
|
1221
|
+
# that field can be screened as well.
|
|
1222
|
+
terminal = None
|
|
1223
|
+
if len(parts) == 4:
|
|
1224
|
+
terminal, parts = parts[0], parts[1:]
|
|
1225
|
+
# One hex character (pin the class, keep the tail random) or exactly six
|
|
1226
|
+
# (pin all of field 2). Any other width would change the peer id's shape,
|
|
1227
|
+
# and the camera rejects a malformed peer id outright.
|
|
1228
|
+
if len(terminal) not in (1, 6) or any(c not in "0123456789abcdef" for c in terminal):
|
|
1229
|
+
return None
|
|
1230
|
+
elif len(parts) != 3:
|
|
1231
|
+
return None
|
|
1232
|
+
out = []
|
|
1233
|
+
for q in parts:
|
|
1234
|
+
if not q.isdigit():
|
|
1235
|
+
return None
|
|
1236
|
+
out.append(int(q))
|
|
1237
|
+
return (terminal, out[0], out[1], out[2])
|
|
1238
|
+
|
|
1239
|
+
|
|
1123
1240
|
class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
1124
1241
|
"""All camera/streaming methods, mixed into DeviceClient via inheritance."""
|
|
1125
1242
|
|
|
@@ -4029,7 +4146,8 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
4029
4146
|
# loop re-offers within one open attempt on one peer connection, so a
|
|
4030
4147
|
# late answer still belongs to the offer in flight.
|
|
4031
4148
|
_loop_peer_id = self.generate_webrtc_peer_id(live_type=2, stream_id=0,
|
|
4032
|
-
sdes=True
|
|
4149
|
+
sdes=True,
|
|
4150
|
+
device_id=self.device_id)
|
|
4033
4151
|
_peer_reuses = 0
|
|
4034
4152
|
|
|
4035
4153
|
# Wake-readiness retries. A battery camera that answers livePlayResp with
|
|
@@ -4052,7 +4170,8 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
4052
4170
|
self._serve_relay.set_backend(None)
|
|
4053
4171
|
if _peer_reuses >= _PEERID_MAX_REUSE:
|
|
4054
4172
|
_loop_peer_id = self.generate_webrtc_peer_id(
|
|
4055
|
-
live_type=2, stream_id=0, sdes=True
|
|
4173
|
+
live_type=2, stream_id=0, sdes=True,
|
|
4174
|
+
device_id=self.device_id)
|
|
4056
4175
|
_peer_reuses = 0
|
|
4057
4176
|
_peer_reuses += 1
|
|
4058
4177
|
_use_fast = self._adaptive_next_fast(_adaptive, _fast_failed)
|
|
@@ -4138,6 +4257,14 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
4138
4257
|
_started_at = time.monotonic()
|
|
4139
4258
|
_done = asyncio.ensure_future(session.wait_done())
|
|
4140
4259
|
_stalled = False
|
|
4260
|
+
_ice_dead = False
|
|
4261
|
+
# Measurement scaffolding, off by default and device-scoped: end a
|
|
4262
|
+
# session after its allotted media time so a pass costs the same
|
|
4263
|
+
# wall clock as a stall and a RATE can be sampled. Anchored on
|
|
4264
|
+
# media, never on the open.
|
|
4265
|
+
_cap_s = _sdes_max_session_s(self.device_id)
|
|
4266
|
+
_first_media_at = None
|
|
4267
|
+
_capped = False
|
|
4141
4268
|
_idle_release = False
|
|
4142
4269
|
# No-viewer release. Unlike the DTLS serve (which idle-releases when
|
|
4143
4270
|
# its mux pipe goes stale), an SDES keepalive otherwise reconnects
|
|
@@ -4167,6 +4294,26 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
4167
4294
|
):
|
|
4168
4295
|
_stalled = True
|
|
4169
4296
|
break
|
|
4297
|
+
if _cap_s:
|
|
4298
|
+
if _first_media_at is None:
|
|
4299
|
+
_first_media_at = session.last_media_monotonic
|
|
4300
|
+
if _session_cap_reached(_first_media_at,
|
|
4301
|
+
time.monotonic(), _cap_s):
|
|
4302
|
+
_capped = True
|
|
4303
|
+
_stalled = True
|
|
4304
|
+
break
|
|
4305
|
+
if sdes_ice_teardown(
|
|
4306
|
+
session.last_media_monotonic,
|
|
4307
|
+
session.last_ice_answer_monotonic,
|
|
4308
|
+
time.monotonic(),
|
|
4309
|
+
):
|
|
4310
|
+
# Media gone AND the camera's STUN answers gone: its ICE
|
|
4311
|
+
# agent has dropped the transport. Nudging cannot recover
|
|
4312
|
+
# that, so reopen now instead of serving ~25 s more of
|
|
4313
|
+
# nothing while the media watchdog runs down.
|
|
4314
|
+
_ice_dead = True
|
|
4315
|
+
_stalled = True
|
|
4316
|
+
break
|
|
4170
4317
|
if _idle_on and _serve_port is not None:
|
|
4171
4318
|
_present = await self._viewer_present(_serve_port)
|
|
4172
4319
|
if _present: # True -> a viewer is pulling; stay alive
|
|
@@ -4217,6 +4364,16 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
4217
4364
|
)
|
|
4218
4365
|
return
|
|
4219
4366
|
|
|
4367
|
+
if _capped:
|
|
4368
|
+
_LOGGER.info(
|
|
4369
|
+
"SDES %s: session cap %.0fs reached - ending session"
|
|
4370
|
+
" (measurement scaffolding; this session PASSED the"
|
|
4371
|
+
" 80.2s gate)", self.device_id, _cap_s)
|
|
4372
|
+
if _ice_dead:
|
|
4373
|
+
_LOGGER.info(
|
|
4374
|
+
"SDES %s: camera ICE transport gone (media and STUN answers"
|
|
4375
|
+
" both stopped) - reopening now rather than waiting out the"
|
|
4376
|
+
" media watchdog", self.device_id)
|
|
4220
4377
|
if _stalled:
|
|
4221
4378
|
# Say whether this will repeat forever. With idle-release off
|
|
4222
4379
|
# (<=0 idle window) the loop never asks whether a viewer is
|
|
@@ -4252,7 +4409,8 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
4252
4409
|
# the next open should be a fresh one rather than re-offering on a
|
|
4253
4410
|
# peerid the camera has already finished with.
|
|
4254
4411
|
_loop_peer_id = self.generate_webrtc_peer_id(
|
|
4255
|
-
live_type=2, stream_id=0, sdes=True
|
|
4412
|
+
live_type=2, stream_id=0, sdes=True,
|
|
4413
|
+
device_id=self.device_id)
|
|
4256
4414
|
_peer_reuses = 0
|
|
4257
4415
|
else:
|
|
4258
4416
|
if _should_abandon_keepalive(
|
|
@@ -6052,24 +6210,76 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
6052
6210
|
|
|
6053
6211
|
@staticmethod
|
|
6054
6212
|
def generate_webrtc_peer_id(
|
|
6055
|
-
live_type: int = 2, stream_id: int = 0, *, sdes: bool = False
|
|
6213
|
+
live_type: int = 2, stream_id: int = 0, *, sdes: bool = False,
|
|
6214
|
+
device_id: "Optional[str]" = None
|
|
6056
6215
|
) -> str:
|
|
6057
6216
|
"""Generate a peerId for a WebRTC connection.
|
|
6058
6217
|
|
|
6059
6218
|
Format observed in iOS app telemetry:
|
|
6060
6219
|
``{32-hex-session}_{6-hex-random}_{liveType}_{streamId}_{version}``
|
|
6061
6220
|
|
|
6062
|
-
The trailing
|
|
6063
|
-
``
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6221
|
+
The trailing digit is set per transport - ``1`` for SDES-SRTP cameras,
|
|
6222
|
+
``2`` for DTLS-SRTP ones - and empirically the cameras want it that way:
|
|
6223
|
+
an SDES camera silently discards a webrtcReq ending ``_2``.
|
|
6224
|
+
|
|
6225
|
+
The NAME is wrong and the 2025-03 telemetry reading behind it does not
|
|
6226
|
+
survive the decompiled app. ``KVSWebRTCChannel`` builds this field as
|
|
6227
|
+
``streamID = !isDefaultHD``, i.e. a stream index in {0, 1} derived from
|
|
6228
|
+
the quality setting - HD is 0, SD is 1. It is not a transport version
|
|
6229
|
+
and the app can never emit a ``2`` here. Field 3 is ``playCmd``
|
|
6230
|
+
(LIVE=0, SDCARD=1, PRE_LINK=2) and we send 2 = PRE_LINK for a live view;
|
|
6231
|
+
field 4 is a literal 0 in the app. All three are left alone for now:
|
|
6232
|
+
they are demonstrably not what the camera classifies a client by (that
|
|
6233
|
+
is field 2's first character - see ``_expt_peer_id_fields``), and
|
|
6234
|
+
changing a digit the cameras currently accept, on a guess, is how the
|
|
6235
|
+
stream stops working.
|
|
6068
6236
|
"""
|
|
6069
6237
|
import os
|
|
6070
|
-
|
|
6071
|
-
|
|
6238
|
+
# Field 1 is the install identity - the vendor app puts
|
|
6239
|
+
# DeviceIdUtils.getDeviceId(app) here and it is stable for the life of
|
|
6240
|
+
# the install. Field 2 is per-open random ("0" + 5 random chars in the
|
|
6241
|
+
# app), so it must NOT be pinned.
|
|
6242
|
+
session = _stable_terminal_id(width=32)
|
|
6243
|
+
# Field 2's first character is the client class on the A001064:
|
|
6244
|
+
# `parse_peer_id` (0x298ef8 in that image) does
|
|
6245
|
+
# `client_type = field2[0] - '0'` against
|
|
6246
|
+
# EN_WEBRTC_CLIENT_TYPE_{APP_ANDROID,APP_IOS,WEB,ALEXA,GOOGLE_HOME}=0..4,
|
|
6247
|
+
# the vendor Android app hard-codes '0' there and the vendor web app
|
|
6248
|
+
# sends '2'. That is all verified.
|
|
6249
|
+
#
|
|
6250
|
+
# **It is NOT verified for the rest of the fleet, and pinning it breaks
|
|
6251
|
+
# the A000088.** Setting this character to '0' for every camera was
|
|
6252
|
+
# tried and reverted on 2026-08-31. Against a drained fleet with Home
|
|
6253
|
+
# Assistant stopped, three A000088 cameras went 0 for 3 over nine
|
|
6254
|
+
# attempts - each one completing its DTLS handshake in under two seconds
|
|
6255
|
+
# and then receiving no media at all - while the same build streamed the
|
|
6256
|
+
# A001064 on the first attempt. Reverting THIS LINE ALONE, with every
|
|
6257
|
+
# other change of that release left in place, restored 3 of 3.
|
|
6258
|
+
#
|
|
6259
|
+
# The A000088 is a different SoC (Realtek Ameba, FreeRTOS/KM4) whose
|
|
6260
|
+
# image has never been disassembled. A finding from the A001064's
|
|
6261
|
+
# firmware does not transfer to it, and this camera does not merely
|
|
6262
|
+
# ignore the byte - announcing APP_ANDROID makes it accept the session
|
|
6263
|
+
# and send nothing, which is worse than the random value it replaced.
|
|
6264
|
+
#
|
|
6265
|
+
# So the character stays random. Nothing needs it: the 80.2 s cliff this
|
|
6266
|
+
# was found while chasing is fixed in the SCTP receiver (see
|
|
6267
|
+
# `_sctp_sack_chunk`), not here. Screen it per model with
|
|
6268
|
+
# `_expt_peer_id_fields` before ever setting it again, and get evidence
|
|
6269
|
+
# from EACH model's own firmware.
|
|
6270
|
+
rand6 = os.urandom(3).hex() # 6 hex chars, fresh per open
|
|
6072
6271
|
version = 1 if sdes else 2
|
|
6272
|
+
# Experiment override (off by default, fails closed, scoped to one
|
|
6273
|
+
# device): the camera reads its client class from field 2's first
|
|
6274
|
+
# character - see _expt_peer_id_fields. Without a device id the
|
|
6275
|
+
# override never applies.
|
|
6276
|
+
_fields = _expt_peer_id_fields(device_id)
|
|
6277
|
+
if _fields is not None:
|
|
6278
|
+
_term, live_type, stream_id, version = _fields
|
|
6279
|
+
if _term:
|
|
6280
|
+
# One character pins the class and keeps the tail random; six
|
|
6281
|
+
# pins the whole field. Never let the id change length.
|
|
6282
|
+
rand6 = _term if len(_term) == 6 else _term + rand6[1:]
|
|
6073
6283
|
return f"{session}_{rand6}_{live_type}_{stream_id}_{version}"
|
|
6074
6284
|
|
|
6075
6285
|
async def async_open_webrtc_stream(
|
{python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/protocol.py
RENAMED
|
@@ -271,6 +271,32 @@ def answer_inserted_a_section(*, shifted_sections: int,
|
|
|
271
271
|
return shifted_sections > 0 and unclaimed_answer_mids > 0
|
|
272
272
|
|
|
273
273
|
|
|
274
|
+
def sdes_ice_teardown(last_media: float, last_ice_answer: float, now: float,
|
|
275
|
+
media_gap: float = 3.0, ice_gap: float = 6.0) -> bool:
|
|
276
|
+
"""Whether the camera's ICE agent has torn the transport down.
|
|
277
|
+
|
|
278
|
+
The A001064 runs Leedarson's fork of the AWS KVS WebRTC SDK C, whose ICE
|
|
279
|
+
agent transitions CONNECTED -> DISCONNECTED -> FAILED and drops the
|
|
280
|
+
transport. Measured over four stock sessions: while the transport is alive
|
|
281
|
+
the camera answers our STUN binding requests roughly every 2.5 s and sends
|
|
282
|
+
its own keepalive indication; at the 80.2 s cliff the answers, the
|
|
283
|
+
indications and the media all stop in the same instant. A session that
|
|
284
|
+
survives the cliff keeps all three going.
|
|
285
|
+
|
|
286
|
+
Media stopping ALONE can be a pause the camera recovers from - that is what
|
|
287
|
+
the LIVING nudges are for. Media stopping AND the STUN answers stopping is
|
|
288
|
+
the transport being gone, and only a reopen can help. Acting on that
|
|
289
|
+
directly saves the ~30 s the media watchdog otherwise spends finding out.
|
|
290
|
+
|
|
291
|
+
Fails safe: either clock unstarted (0.0) returns False, so a session that
|
|
292
|
+
never carried media - or a build where the ICE timestamp is not plumbed
|
|
293
|
+
through - falls back to the ordinary watchdog rather than being torn down.
|
|
294
|
+
"""
|
|
295
|
+
if last_media <= 0.0 or last_ice_answer <= 0.0:
|
|
296
|
+
return False
|
|
297
|
+
return (now - last_media) > media_gap and (now - last_ice_answer) > ice_gap
|
|
298
|
+
|
|
299
|
+
|
|
274
300
|
def ice_wait_timeout(*, shifted_sections: int, default_timeout: float) -> float:
|
|
275
301
|
"""How long to give ICE, given what the camera's answer looked like.
|
|
276
302
|
|
{python_aidot_cameras-1.0.0b33 → python_aidot_cameras-1.0.0b34}/aidot_cameras/camera/sdes.py
RENAMED
|
@@ -49,6 +49,8 @@ class SdesSession(AvioRequestMixin):
|
|
|
49
49
|
cmd_chan=None,
|
|
50
50
|
talk_state=None,
|
|
51
51
|
media_progress=None,
|
|
52
|
+
|
|
53
|
+
ice_progress=None,
|
|
52
54
|
media_counts=None,
|
|
53
55
|
teardown_requested=None,
|
|
54
56
|
first_video_pt=None,
|
|
@@ -74,6 +76,13 @@ class SdesSession(AvioRequestMixin):
|
|
|
74
76
|
# that stopped sending (battery teardown ~49-72s) and reconnect - ffmpeg
|
|
75
77
|
# itself never exits on a dead UDP input, so wait_done() alone would hang.
|
|
76
78
|
self._media_progress = media_progress if media_progress is not None else [0.0]
|
|
79
|
+
# Mutable one-element list shared with the bridge thread: [0] holds the
|
|
80
|
+
# monotonic timestamp of the last STUN binding success or keepalive
|
|
81
|
+
# indication the camera sent us (0.0 until the first one). While the
|
|
82
|
+
# ICE transport is alive the camera answers about every 2.5 s; at the
|
|
83
|
+
# 80.2 s cliff the answers stop in the same instant media does, which is
|
|
84
|
+
# how a transport teardown is told apart from a media pause.
|
|
85
|
+
self._ice_progress = ice_progress if ice_progress is not None else [0.0]
|
|
77
86
|
# Shared with the bridge thread: [packets, bytes] forwarded to ffmpeg.
|
|
78
87
|
# See media_stats() - the SDES path never calls on_frame, so this is the
|
|
79
88
|
# in-process signal that media is actually flowing.
|
|
@@ -228,6 +237,11 @@ class SdesSession(AvioRequestMixin):
|
|
|
228
237
|
"""time.monotonic() of the last media packet forwarded, 0.0 if none yet."""
|
|
229
238
|
return self._media_progress[0]
|
|
230
239
|
|
|
240
|
+
@property
|
|
241
|
+
def last_ice_answer_monotonic(self) -> float:
|
|
242
|
+
"""time.monotonic() of the camera's last STUN answer, 0.0 if none yet."""
|
|
243
|
+
return self._ice_progress[0]
|
|
244
|
+
|
|
231
245
|
def media_stats(self) -> dict:
|
|
232
246
|
"""Snapshot of media actually forwarded to ffmpeg by the bridge.
|
|
233
247
|
|