python-aidot-cameras 0.17.1__tar.gz → 0.17.3b2__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-0.17.1/python_aidot_cameras.egg-info → python_aidot_cameras-0.17.3b2}/PKG-INFO +38 -1
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/README.md +37 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/client.py +36 -3
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/lan_control.py +26 -2
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/sdes_open.py +424 -128
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/webrtc_open.py +51 -16
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/client.py +26 -6
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/device_client.py +61 -11
- python_aidot_cameras-0.17.3b2/aidot_cameras/lan_retry.py +198 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/pyproject.toml +1 -1
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2/python_aidot_cameras.egg-info}/PKG-INFO +38 -1
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/python_aidot_cameras.egg-info/SOURCES.txt +12 -1
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dormant_camera_cleanup.py +52 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_futile_keepalive.py +35 -4
- python_aidot_cameras-0.17.3b2/tests/test_key_restart_sdp.py +195 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_lan_control.py +61 -0
- python_aidot_cameras-0.17.3b2/tests/test_lan_retry_reaches_the_storming_devices.py +252 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_light_active_color_mode.py +15 -6
- python_aidot_cameras-0.17.3b2/tests/test_login_connect_deadline.py +113 -0
- python_aidot_cameras-0.17.3b2/tests/test_login_reconnect_backoff.py +83 -0
- python_aidot_cameras-0.17.3b2/tests/test_media_counted_only_when_plaintext.py +237 -0
- python_aidot_cameras-0.17.3b2/tests/test_no_key_material_in_logs.py +140 -0
- python_aidot_cameras-0.17.3b2/tests/test_sctp_init_ack_tsn.py +94 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_bridge_break_gate.py +74 -10
- python_aidot_cameras-0.17.3b2/tests/test_snapshot_cancellation.py +105 -0
- python_aidot_cameras-0.17.3b2/tests/test_srtp_rx_rekey.py +183 -0
- python_aidot_cameras-0.17.3b2/tests/test_srtp_tx_key_note.py +101 -0
- python_aidot_cameras-0.17.3b2/tests/test_webrtc_answer_race.py +101 -0
- python_aidot_cameras-0.17.1/tests/test_key_restart_sdp.py +0 -61
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/LICENSE +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/MANIFEST.in +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/__main__.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_upstream.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/ble_gateway.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/hwaccel.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/playback.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/protocol.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/sdes.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/tutk.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/setup.cfg +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_answer_sdp_video_pt.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_audio_mux_offline.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_avio_request.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_avio_response_parse.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_avio_response_router.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_battery_detection.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_battery_evidence_corroboration.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_battery_skip_turn.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_ble_gateway.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_bridge_state_sentinels.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_busy_backoff.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_constants_imports_resolve.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_control_verdicts.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_device_cmd_seq_match.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dtls_mux_terminal_write.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_dtls_stdout_producer.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_env_seams.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_hwaccel_probe.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_ice_peer_reflexive.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_idle_release_both_loops.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_live_validate_policy.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_media_socket_rcvbuf.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_media_wait_floor.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_no_self_referential_go2rtc.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_peerid_reuse.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_remb.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_resolution_persists.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_restart_cycle.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_scripts_importable.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_avio_dispatch.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_offer_video_pin.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_sctp_abort.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_serve_stderr_drain.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_teardown_exit_log.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_terminal_ack.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_terminal_ack_latency.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdp_narrowing_properties.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sdp_payload_type_narrowing.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_serve_loss_tolerance.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_shared_house_seam.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sprop_capture_gate.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_sprop_staleness.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_stream_slot_cap.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_turn_create_permission.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_video_profile_log.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_viewer_check_throttle.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_viewer_presence.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_viewer_present_push_mode.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_wait_or_event.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_wake_gate_camera_evidence.py +0 -0
- {python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/tests/test_wake_readiness_retry.py +0 -0
{python_aidot_cameras-0.17.1/python_aidot_cameras.egg-info → python_aidot_cameras-0.17.3b2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.3b2
|
|
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
|
|
@@ -66,6 +66,40 @@ Other battery models (A001108, A001360) are recognized in code with the same
|
|
|
66
66
|
battery handling. See [`docs/CAMERAS.md`](docs/CAMERAS.md#supported-cameras) for
|
|
67
67
|
the authoritative table and per-model notes.
|
|
68
68
|
|
|
69
|
+
## Local (LAN) control and account ownership
|
|
70
|
+
|
|
71
|
+
Local control over the devices' TCP:10000 channel - both the light/plug protocol
|
|
72
|
+
and this package's `CameraLanClient` - is accepted **only for the account that
|
|
73
|
+
owns the devices**. A member of a shared home is rejected at the device, even
|
|
74
|
+
though the cloud happily hands that member a full device list, complete with the
|
|
75
|
+
per-device `password` and `aesKey`.
|
|
76
|
+
|
|
77
|
+
The rejection is easy to misread, because the device answers with a code that
|
|
78
|
+
varies by firmware family and a message that blames the wrong thing:
|
|
79
|
+
|
|
80
|
+
| Model | Ack |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `LK.light.A001493` | 400 `not equal abort user id or password` |
|
|
83
|
+
| `LK.light.A001497`, `LK.plug.A001535` | 4354 `fail` |
|
|
84
|
+
| `LK.IPC.A000088` | 4352 `fail` |
|
|
85
|
+
|
|
86
|
+
The A001493's message is the honest one: it is the **user id** that does not
|
|
87
|
+
match, not the password. Every credential in the request can be correct and the
|
|
88
|
+
login will still be refused if the `userId` is not the owner's.
|
|
89
|
+
|
|
90
|
+
Verified 2026-08-08 against ten devices across four model families: every one
|
|
91
|
+
returns 200 for the owning account and one of the codes above for a shared-home
|
|
92
|
+
member.
|
|
93
|
+
|
|
94
|
+
**If local control never engages, check which account the integration is signed
|
|
95
|
+
in as before looking anywhere else.** A dedicated or secondary login - the sort
|
|
96
|
+
of thing you might use to avoid contending with a phone app over a rotating
|
|
97
|
+
refresh token - will control everything fine through the cloud and never once
|
|
98
|
+
log in locally.
|
|
99
|
+
|
|
100
|
+
Cameras' live video is unaffected either way: it is WebRTC signaled over cloud
|
|
101
|
+
MQTT and does not use this path.
|
|
102
|
+
|
|
69
103
|
## Library install
|
|
70
104
|
|
|
71
105
|
Install from PyPI (the simplest, recommended method):
|
|
@@ -178,6 +212,9 @@ audio, idle release, the sprop cache path) are documented in
|
|
|
178
212
|
| `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
179
213
|
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
180
214
|
| `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
|
|
215
|
+
| `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone (it is retried again the next time something asks for it). Applies to every device, camera or not - the devices that hit this are lights. | `6` |
|
|
216
|
+
| `AIDOT_LOGIN_RETRY_CAP_S` | Ceiling on the exponential delay between those retries. | `60` |
|
|
217
|
+
| `AIDOT_LOGIN_CONNECT_TIMEOUT_S` | Ceiling on one LAN connect+login attempt. A device that completes the TCP handshake and then stops answering is abandoned and its socket closed rather than parking the attempt forever. | `20` |
|
|
181
218
|
| `AIDOT_LIVESTREAM_PARAM` | **No-op (ignored).** Formerly requested the cloud `liveStreamParam` pre-connect for battery cameras. That call provisions the camera toward AWS KVS, so it sends its media there instead of to this library and the live view negotiates and then shows nothing - and battery cameras were the only ones it applied to. Setting it (or `start_keepalive(live_stream_param=True)`) logs one warning and changes nothing. | ignored |
|
|
182
219
|
|
|
183
220
|
### Security hardening
|
|
@@ -35,6 +35,40 @@ Other battery models (A001108, A001360) are recognized in code with the same
|
|
|
35
35
|
battery handling. See [`docs/CAMERAS.md`](docs/CAMERAS.md#supported-cameras) for
|
|
36
36
|
the authoritative table and per-model notes.
|
|
37
37
|
|
|
38
|
+
## Local (LAN) control and account ownership
|
|
39
|
+
|
|
40
|
+
Local control over the devices' TCP:10000 channel - both the light/plug protocol
|
|
41
|
+
and this package's `CameraLanClient` - is accepted **only for the account that
|
|
42
|
+
owns the devices**. A member of a shared home is rejected at the device, even
|
|
43
|
+
though the cloud happily hands that member a full device list, complete with the
|
|
44
|
+
per-device `password` and `aesKey`.
|
|
45
|
+
|
|
46
|
+
The rejection is easy to misread, because the device answers with a code that
|
|
47
|
+
varies by firmware family and a message that blames the wrong thing:
|
|
48
|
+
|
|
49
|
+
| Model | Ack |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `LK.light.A001493` | 400 `not equal abort user id or password` |
|
|
52
|
+
| `LK.light.A001497`, `LK.plug.A001535` | 4354 `fail` |
|
|
53
|
+
| `LK.IPC.A000088` | 4352 `fail` |
|
|
54
|
+
|
|
55
|
+
The A001493's message is the honest one: it is the **user id** that does not
|
|
56
|
+
match, not the password. Every credential in the request can be correct and the
|
|
57
|
+
login will still be refused if the `userId` is not the owner's.
|
|
58
|
+
|
|
59
|
+
Verified 2026-08-08 against ten devices across four model families: every one
|
|
60
|
+
returns 200 for the owning account and one of the codes above for a shared-home
|
|
61
|
+
member.
|
|
62
|
+
|
|
63
|
+
**If local control never engages, check which account the integration is signed
|
|
64
|
+
in as before looking anywhere else.** A dedicated or secondary login - the sort
|
|
65
|
+
of thing you might use to avoid contending with a phone app over a rotating
|
|
66
|
+
refresh token - will control everything fine through the cloud and never once
|
|
67
|
+
log in locally.
|
|
68
|
+
|
|
69
|
+
Cameras' live video is unaffected either way: it is WebRTC signaled over cloud
|
|
70
|
+
MQTT and does not use this path.
|
|
71
|
+
|
|
38
72
|
## Library install
|
|
39
73
|
|
|
40
74
|
Install from PyPI (the simplest, recommended method):
|
|
@@ -147,6 +181,9 @@ audio, idle release, the sprop cache path) are documented in
|
|
|
147
181
|
| `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
148
182
|
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
149
183
|
| `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
|
|
184
|
+
| `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone (it is retried again the next time something asks for it). Applies to every device, camera or not - the devices that hit this are lights. | `6` |
|
|
185
|
+
| `AIDOT_LOGIN_RETRY_CAP_S` | Ceiling on the exponential delay between those retries. | `60` |
|
|
186
|
+
| `AIDOT_LOGIN_CONNECT_TIMEOUT_S` | Ceiling on one LAN connect+login attempt. A device that completes the TCP handshake and then stops answering is abandoned and its socket closed rather than parking the attempt forever. | `20` |
|
|
150
187
|
| `AIDOT_LIVESTREAM_PARAM` | **No-op (ignored).** Formerly requested the cloud `liveStreamParam` pre-connect for battery cameras. That call provisions the camera toward AWS KVS, so it sends its media there instead of to this library and the live view negotiates and then shows nothing - and battery cameras were the only ones it applied to. Setting it (or `start_keepalive(live_stream_param=True)`) logs one warning and changes nothing. | ignored |
|
|
151
188
|
|
|
152
189
|
### Security hardening
|
{python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/client.py
RENAMED
|
@@ -398,6 +398,21 @@ _BUSY_BACKOFF_S = float(os.environ.get("AIDOT_BUSY_BACKOFF_S", "20"))
|
|
|
398
398
|
_FUTILE_KEEPALIVE_LIMIT = int(os.environ.get("AIDOT_FUTILE_KEEPALIVE_LIMIT", "5"))
|
|
399
399
|
|
|
400
400
|
|
|
401
|
+
def _next_no_media_streak(no_media_streak: int, healthy: bool) -> int:
|
|
402
|
+
"""The streak after a keepalive session that was (or was not) healthy.
|
|
403
|
+
|
|
404
|
+
The ceiling counts CONSECUTIVE failures, so a session that delivered media
|
|
405
|
+
resets it to zero. Without that reset the count is cumulative and a camera
|
|
406
|
+
that merely fails occasionally - five scattered no-media sessions spread
|
|
407
|
+
across a day, each one separated by sessions that worked - eventually trips
|
|
408
|
+
a limit meant for cameras that never deliver anything at all, and has its
|
|
409
|
+
background keepalive stopped for good.
|
|
410
|
+
"""
|
|
411
|
+
if healthy:
|
|
412
|
+
return 0
|
|
413
|
+
return no_media_streak + 1
|
|
414
|
+
|
|
415
|
+
|
|
401
416
|
def _should_abandon_keepalive(no_media_streak: int, *, is_battery: bool,
|
|
402
417
|
limit: int = _FUTILE_KEEPALIVE_LIMIT) -> bool:
|
|
403
418
|
"""Should the background keepalive stop reopening this camera?
|
|
@@ -2622,7 +2637,14 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
2622
2637
|
_session.wait_done(),
|
|
2623
2638
|
timeout=_sdes_snap_seconds + 80,
|
|
2624
2639
|
)
|
|
2625
|
-
except
|
|
2640
|
+
except _asyncio.CancelledError:
|
|
2641
|
+
# Not the same event as a timeout. A timeout means the
|
|
2642
|
+
# camera was slow and whatever landed in the temp file is
|
|
2643
|
+
# worth salvaging; a cancellation means the caller is gone,
|
|
2644
|
+
# so salvaging it (another ffmpeg, up to 15s) is work nobody
|
|
2645
|
+
# is waiting for and it stops shutdown from completing.
|
|
2646
|
+
raise
|
|
2647
|
+
except TimeoutError:
|
|
2626
2648
|
pass
|
|
2627
2649
|
finally:
|
|
2628
2650
|
await _session.stop()
|
|
@@ -3599,6 +3621,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
3599
3621
|
# Adaptive bookkeeping: a fast attempt that never delivered media
|
|
3600
3622
|
# latches the loop onto the full relay path for its remaining opens.
|
|
3601
3623
|
_healthy = session.last_media_monotonic > 0.0
|
|
3624
|
+
_no_media_streak = _next_no_media_streak(_no_media_streak, _healthy)
|
|
3602
3625
|
if _healthy:
|
|
3603
3626
|
# A session that delivered media is a real, cleanly-used session;
|
|
3604
3627
|
# the next open should be a fresh one rather than re-offering on a
|
|
@@ -3606,9 +3629,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
3606
3629
|
_loop_peer_id = self.generate_webrtc_peer_id(
|
|
3607
3630
|
live_type=2, stream_id=0, sdes=True)
|
|
3608
3631
|
_peer_reuses = 0
|
|
3609
|
-
_no_media_streak = 0
|
|
3610
3632
|
else:
|
|
3611
|
-
_no_media_streak += 1
|
|
3612
3633
|
if _should_abandon_keepalive(
|
|
3613
3634
|
_no_media_streak, is_battery=self.is_battery_camera):
|
|
3614
3635
|
# Every open so far has cost the camera a wake and returned
|
|
@@ -3622,6 +3643,18 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
3622
3643
|
self.device_id, _no_media_streak,
|
|
3623
3644
|
)
|
|
3624
3645
|
self._streaming_active = False
|
|
3646
|
+
# Same teardown the idle-release exit does. Leaving the
|
|
3647
|
+
# go2rtc stream registered points it at a serve port with
|
|
3648
|
+
# nothing listening, so a viewer attaching to a dormant
|
|
3649
|
+
# camera gets "connection refused" instead of a clean miss -
|
|
3650
|
+
# exactly what the idle-release path exists to avoid.
|
|
3651
|
+
self._cancel_keepalive_renew()
|
|
3652
|
+
try:
|
|
3653
|
+
await self._deregister_go2rtc()
|
|
3654
|
+
except Exception:
|
|
3655
|
+
_LOGGER.debug("camera %s: go2rtc deregister after "
|
|
3656
|
+
"abandon failed", self.device_id,
|
|
3657
|
+
exc_info=True)
|
|
3625
3658
|
return
|
|
3626
3659
|
if _use_fast and not _healthy and not _fast_failed:
|
|
3627
3660
|
_LOGGER.info(
|
{python_aidot_cameras-0.17.1 → python_aidot_cameras-0.17.3b2}/aidot_cameras/camera/lan_control.py
RENAMED
|
@@ -164,6 +164,26 @@ class CameraLanError(Exception):
|
|
|
164
164
|
"""A local control operation failed (connect, login, or protocol error)."""
|
|
165
165
|
|
|
166
166
|
|
|
167
|
+
class CameraLanLoginRejected(CameraLanError):
|
|
168
|
+
"""The device advertised local control and then refused our login.
|
|
169
|
+
|
|
170
|
+
Separate from its parent because the two mean opposite things to a caller.
|
|
171
|
+
A device that does not offer local control is ordinary and should be quiet;
|
|
172
|
+
a device that offers it, accepts the connection, decrypts our request with
|
|
173
|
+
its own key, and STILL answers with a non-200 ack is a defect - either in
|
|
174
|
+
what we send or in the device - and the caller should be able to say so out
|
|
175
|
+
loud rather than logging it at the same level as "not supported here".
|
|
176
|
+
|
|
177
|
+
``ack`` is the device's own code, because the refusals differ by family and
|
|
178
|
+
the number is the only handle on which one happened: A000088 cameras answer
|
|
179
|
+
4352, A001497 lights and A001535 plugs 4354, A001493 lights 400.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(self, message: str, ack: Any = None) -> None:
|
|
183
|
+
super().__init__(message)
|
|
184
|
+
self.ack = ack
|
|
185
|
+
|
|
186
|
+
|
|
167
187
|
class CameraLanClient:
|
|
168
188
|
"""Short-lived local device-control client for one camera.
|
|
169
189
|
|
|
@@ -291,9 +311,13 @@ class CameraLanClient:
|
|
|
291
311
|
) from exc
|
|
292
312
|
if ack != 200:
|
|
293
313
|
# The real device rejected our key-authenticated login; stop using
|
|
294
|
-
# the LAN path and revert to cloud.
|
|
314
|
+
# the LAN path and revert to cloud. Raised as the specific type so
|
|
315
|
+
# a caller can tell this apart from a device that never offered
|
|
316
|
+
# local control - see CameraLanLoginRejected.
|
|
295
317
|
self._eligible = False
|
|
296
|
-
raise
|
|
318
|
+
raise CameraLanLoginRejected(
|
|
319
|
+
f"{self.device_id}: login rejected ack={ack}", ack=ack
|
|
320
|
+
)
|
|
297
321
|
return (resp.get("payload") or {}).get("ascNumber", 1)
|
|
298
322
|
|
|
299
323
|
# -- public control ---------------------------------------------------- #
|