python-aidot-cameras 0.11.9__tar.gz → 0.11.11__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.11.9/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.11}/PKG-INFO +1 -1
- python_aidot_cameras-0.11.11/aidot/__init__.py +54 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/protocol.py +38 -8
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/webrtc_open.py +5 -2
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/pyproject.toml +1 -1
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- python_aidot_cameras-0.11.11/tests/test_logging_caps.py +31 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_mqtt_session_reconnect.py +50 -0
- python_aidot_cameras-0.11.9/aidot/__init__.py +0 -46
- python_aidot_cameras-0.11.9/tests/test_logging_caps.py +0 -22
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/LICENSE +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/MANIFEST.in +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/README.md +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/__main__.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/__init__.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/aes_utils.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/__init__.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/client.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/constants.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/controls.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/go2rtc.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/lan_control.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/models.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/playback.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/sdes.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/sdes_open.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/tutk.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/camera/webrtc.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/client.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/const.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/credentials.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/device_client.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/discover.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/exceptions.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/g711.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/login_const.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/models/__init__.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/models/device_client_model.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/models/device_model.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/models/discover_model.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/py.typed +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/setup.cfg +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_wait_or_event.py +0 -0
{python_aidot_cameras-0.11.9/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.11}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.11
|
|
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
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""AiDot camera and device library."""
|
|
2
|
+
|
|
3
|
+
import logging as _logging
|
|
4
|
+
|
|
5
|
+
from .client import AidotClient
|
|
6
|
+
from .device_client import DeviceClient
|
|
7
|
+
from .discover import Discover
|
|
8
|
+
from .exceptions import (
|
|
9
|
+
AidotAuthFailed,
|
|
10
|
+
AidotAuthTokenExpired,
|
|
11
|
+
AidotCameraBusy,
|
|
12
|
+
AidotError,
|
|
13
|
+
AidotNotLogin,
|
|
14
|
+
AidotOSError,
|
|
15
|
+
AidotUserOrPassIncorrect,
|
|
16
|
+
HTTPError,
|
|
17
|
+
InvalidHost,
|
|
18
|
+
InvalidURL,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Cap the per-packet loggers that flood the log at DEBUG. When a user enables
|
|
22
|
+
# DEBUG on the parent ``aidot`` logger to diagnose the integration, these emit
|
|
23
|
+
# thousands of lines per second (in one capture, 99% of all lines) - and on a
|
|
24
|
+
# microSD Home Assistant host that log I/O can starve the recorder. Cap them at
|
|
25
|
+
# INFO so enabling ``aidot`` DEBUG stays useful; the diagnostically valuable
|
|
26
|
+
# DEBUG (DTLS, SCTP/DCEP, ICE connection state) still flows. This covers both the
|
|
27
|
+
# vendored aiortc RTP receiver/sender AND the external ``aioice`` package's
|
|
28
|
+
# per-STUN/TURN-packet loggers (aioice is a real dependency, not vendored, so it
|
|
29
|
+
# is not under ``aidot._vendor``). Respect an explicit level if the user set one.
|
|
30
|
+
for _chatty in (
|
|
31
|
+
"aidot._vendor.aiortc.rtcrtpreceiver",
|
|
32
|
+
"aidot._vendor.aiortc.rtcrtpsender",
|
|
33
|
+
"aioice.ice",
|
|
34
|
+
"aioice.turn",
|
|
35
|
+
):
|
|
36
|
+
_lg = _logging.getLogger(_chatty)
|
|
37
|
+
if _lg.level == _logging.NOTSET:
|
|
38
|
+
_lg.setLevel(_logging.INFO)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"AidotAuthFailed",
|
|
42
|
+
"AidotAuthTokenExpired",
|
|
43
|
+
"AidotCameraBusy",
|
|
44
|
+
"AidotClient",
|
|
45
|
+
"AidotError",
|
|
46
|
+
"AidotNotLogin",
|
|
47
|
+
"AidotOSError",
|
|
48
|
+
"AidotUserOrPassIncorrect",
|
|
49
|
+
"DeviceClient",
|
|
50
|
+
"Discover",
|
|
51
|
+
"HTTPError",
|
|
52
|
+
"InvalidHost",
|
|
53
|
+
"InvalidURL",
|
|
54
|
+
]
|
|
@@ -79,6 +79,13 @@ def _build_stun_binding_success_response(
|
|
|
79
79
|
|
|
80
80
|
_WEBRTC_TERMINAL_ACK_CODES = (-50002, -50015)
|
|
81
81
|
|
|
82
|
+
# If a post-connect MQTT drop stays disconnected longer than this (seconds) with
|
|
83
|
+
# no successful paho reconnect, the receive loop ends the session rather than
|
|
84
|
+
# polling a dead socket until the full duration deadline. Long enough to ride
|
|
85
|
+
# out an ordinary network blip + paho's auto-reconnect, short enough that a
|
|
86
|
+
# terminal drop (session taken over, revoked creds, broker gone) fails fast.
|
|
87
|
+
_MQTT_RECONNECT_GRACE = 20.0
|
|
88
|
+
|
|
82
89
|
|
|
83
90
|
def _terminal_webrtc_ack(msg: dict):
|
|
84
91
|
"""Return ``(code, desc)`` if ``msg`` is a webrtcResp carrying a TERMINAL ack
|
|
@@ -1188,7 +1195,8 @@ def _mqtt_session_sync(
|
|
|
1188
1195
|
|
|
1189
1196
|
msg_q = _queue.Queue()
|
|
1190
1197
|
conn_ev = threading.Event()
|
|
1191
|
-
status = {"connected": False, "rc": None, "rc_str": "", "error": None,
|
|
1198
|
+
status = {"connected": False, "rc": None, "rc_str": "", "error": None,
|
|
1199
|
+
"log": [], "disconnected_since": None}
|
|
1192
1200
|
|
|
1193
1201
|
# Build client - handle paho >=2.0 (VERSION2) and <2.0
|
|
1194
1202
|
try:
|
|
@@ -1217,6 +1225,9 @@ def _mqtt_session_sync(
|
|
|
1217
1225
|
status["rc"] = rc
|
|
1218
1226
|
status["rc_str"] = str(reason_code)
|
|
1219
1227
|
if rc == 0:
|
|
1228
|
+
# Successful (re)connect: a prior drop (if any) has recovered, so the
|
|
1229
|
+
# receive loop must not fail it fast - clear the disconnect marker.
|
|
1230
|
+
status["disconnected_since"] = None
|
|
1220
1231
|
# (Re)subscribe on EVERY successful connect. paho's loop_start
|
|
1221
1232
|
# auto-reconnects after a transient drop, but with clean_session the
|
|
1222
1233
|
# broker retains no subscriptions, so a reconnected client would be
|
|
@@ -1250,14 +1261,21 @@ def _mqtt_session_sync(
|
|
|
1250
1261
|
conn_ev.set()
|
|
1251
1262
|
msg_q.put(None) # sentinel: end the receive loop
|
|
1252
1263
|
return
|
|
1253
|
-
# Already connected once: this is a transient drop. paho's
|
|
1254
|
-
# auto-reconnects and _on_connect re-subscribes
|
|
1255
|
-
#
|
|
1256
|
-
# signaling (the non-persistent transport runs one session for
|
|
1257
|
-
# stream).
|
|
1258
|
-
#
|
|
1264
|
+
# Already connected once: this is (so far) a transient drop. paho's
|
|
1265
|
+
# loop_start auto-reconnects and _on_connect re-subscribes AND clears
|
|
1266
|
+
# disconnected_since, so a brief blip must NOT tear down a long-lived
|
|
1267
|
+
# stream's signaling (the non-persistent transport runs one session for
|
|
1268
|
+
# the whole stream). But a drop that never recovers - session taken over
|
|
1269
|
+
# (rc=142) when the account's persistent client reclaims the clientId,
|
|
1270
|
+
# revoked creds, broker gone - would otherwise leave the receive loop
|
|
1271
|
+
# polling a dead socket until the full duration deadline. Record when the
|
|
1272
|
+
# drop began; the loop ends the session once it has persisted past
|
|
1273
|
+
# _MQTT_RECONNECT_GRACE. (dict single-key set/get is atomic under the GIL,
|
|
1274
|
+
# so no lock is needed across the paho thread and the receive loop.)
|
|
1275
|
+
if status.get("disconnected_since") is None:
|
|
1276
|
+
status["disconnected_since"] = time.monotonic()
|
|
1259
1277
|
_LOGGER.debug(
|
|
1260
|
-
"_mqtt_session:
|
|
1278
|
+
"_mqtt_session: disconnect rc=%s (awaiting paho reconnect)",
|
|
1261
1279
|
reason_code,
|
|
1262
1280
|
)
|
|
1263
1281
|
|
|
@@ -1327,6 +1345,18 @@ def _mqtt_session_sync(
|
|
|
1327
1345
|
remaining = deadline - _time.monotonic()
|
|
1328
1346
|
if remaining <= 0:
|
|
1329
1347
|
break
|
|
1348
|
+
# End the session if a post-connect drop never recovered: a terminal
|
|
1349
|
+
# disconnect delivers no further messages, so without this the loop would
|
|
1350
|
+
# poll a dead socket (and the caller's camera signaling would silently
|
|
1351
|
+
# stall) until the full duration deadline.
|
|
1352
|
+
_ds = status.get("disconnected_since")
|
|
1353
|
+
if _ds is not None and (_time.monotonic() - _ds) > _MQTT_RECONNECT_GRACE:
|
|
1354
|
+
_LOGGER.warning(
|
|
1355
|
+
"_mqtt_session: MQTT disconnected >%.0fs with no reconnect "
|
|
1356
|
+
"(rc=%s) - ending session; camera signaling can no longer arrive",
|
|
1357
|
+
_MQTT_RECONNECT_GRACE, status.get("rc_str"),
|
|
1358
|
+
)
|
|
1359
|
+
break
|
|
1330
1360
|
try:
|
|
1331
1361
|
item = msg_q.get(timeout=min(remaining, 0.1))
|
|
1332
1362
|
except _queue.Empty:
|
|
@@ -1284,8 +1284,11 @@ class _WebRTCOpenMixin:
|
|
|
1284
1284
|
# ------------------------------------------------------------------ #
|
|
1285
1285
|
# aiortc peer connection (DTLS-SRTP path)
|
|
1286
1286
|
# ------------------------------------------------------------------ #
|
|
1287
|
-
|
|
1288
|
-
|
|
1287
|
+
# NOTE: do NOT force aioice to DEBUG here. It floods the log on every DTLS
|
|
1288
|
+
# open, and the package-init cap (aidot/__init__.py) holds aioice.ice /
|
|
1289
|
+
# aioice.turn at INFO anyway, so forcing DEBUG was both self-defeating and
|
|
1290
|
+
# noisy. To debug ICE, set the aioice logger level explicitly (that
|
|
1291
|
+
# overrides the NOTSET-guarded cap).
|
|
1289
1292
|
from aidot._vendor.aiortc import RTCConfiguration, RTCIceServer
|
|
1290
1293
|
|
|
1291
1294
|
def _sanitize_ice_uris(uris):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-aidot-cameras"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.11"
|
|
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"
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11/python_aidot_cameras.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.11
|
|
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
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Importing the library caps the chatty per-packet loggers.
|
|
2
|
+
|
|
3
|
+
aiortc's RTP receiver/sender log every media packet at DEBUG, and the external
|
|
4
|
+
aioice package logs every STUN/TURN packet. Enabling DEBUG on the parent ``aidot``
|
|
5
|
+
logger (the normal way to diagnose the integration) would otherwise unleash
|
|
6
|
+
thousands of lines per second - which on a microSD host can starve the recorder.
|
|
7
|
+
The package init caps those loggers at INFO so DEBUG on ``aidot`` stays useful.
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
import aidot # noqa: F401 - import triggers the logger caps
|
|
12
|
+
|
|
13
|
+
CAPPED = (
|
|
14
|
+
"aidot._vendor.aiortc.rtcrtpreceiver",
|
|
15
|
+
"aidot._vendor.aiortc.rtcrtpsender",
|
|
16
|
+
"aioice.ice",
|
|
17
|
+
"aioice.turn",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_packet_loggers_capped_to_info():
|
|
22
|
+
for name in CAPPED:
|
|
23
|
+
lg = logging.getLogger(name)
|
|
24
|
+
assert lg.level == logging.INFO, f"{name} not capped (level={lg.level})"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_aidot_debug_does_not_unleash_the_packet_firehose():
|
|
28
|
+
# Even with the parent 'aidot' logger at DEBUG, the per-packet DEBUG is off.
|
|
29
|
+
logging.getLogger("aidot").setLevel(logging.DEBUG)
|
|
30
|
+
for name in CAPPED:
|
|
31
|
+
assert not logging.getLogger(name).isEnabledFor(logging.DEBUG), f"{name} still emits DEBUG"
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_mqtt_session_reconnect.py
RENAMED
|
@@ -82,3 +82,53 @@ def test_disconnect_before_connect_ends_session(monkeypatch):
|
|
|
82
82
|
)
|
|
83
83
|
assert messages == []
|
|
84
84
|
assert status["connected"] is False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_terminal_disconnect_ends_session_before_deadline(monkeypatch):
|
|
88
|
+
"""A post-connect drop that never reconnects must end the session via the
|
|
89
|
+
reconnect grace, not poll a dead socket until the full duration deadline."""
|
|
90
|
+
monkeypatch.setattr(proto, "_MQTT_RECONNECT_GRACE", 0.15)
|
|
91
|
+
|
|
92
|
+
def driver(c):
|
|
93
|
+
c.on_connect(c, None, {}, 0) # connected
|
|
94
|
+
c.on_message(c, None, _FakeMsg("t/before", b"x")) # delivered pre-drop
|
|
95
|
+
time.sleep(0.02)
|
|
96
|
+
c.on_disconnect(c, None, 142) # terminal drop; NO reconnect
|
|
97
|
+
|
|
98
|
+
_install_fake_client(monkeypatch, driver)
|
|
99
|
+
|
|
100
|
+
t0 = time.monotonic()
|
|
101
|
+
messages, status = proto._mqtt_session_sync(
|
|
102
|
+
"wss://broker.example/mqtt", "u", "p", "cid",
|
|
103
|
+
["t/a"], [], 30.0, # 30s duration - must NOT wait this long
|
|
104
|
+
)
|
|
105
|
+
elapsed = time.monotonic() - t0
|
|
106
|
+
|
|
107
|
+
assert elapsed < 5.0, f"terminal disconnect should end fast, took {elapsed:.1f}s"
|
|
108
|
+
assert ("t/before", "x") in messages # pre-drop message still collected
|
|
109
|
+
assert status["disconnected_since"] is not None # the drop was recorded
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_reconnect_within_grace_keeps_session(monkeypatch):
|
|
113
|
+
"""A drop that reconnects within the grace clears disconnected_since, so the
|
|
114
|
+
receive loop must NOT fail the session fast (guards the transient path)."""
|
|
115
|
+
monkeypatch.setattr(proto, "_MQTT_RECONNECT_GRACE", 0.5)
|
|
116
|
+
outgoing = queue.Queue()
|
|
117
|
+
|
|
118
|
+
def driver(c):
|
|
119
|
+
c.on_connect(c, None, {}, 0)
|
|
120
|
+
c.on_disconnect(c, None, 7) # blip
|
|
121
|
+
time.sleep(0.05) # < grace
|
|
122
|
+
c.on_connect(c, None, {}, 0) # recovered
|
|
123
|
+
c.on_message(c, None, _FakeMsg("t/after", b"live"))
|
|
124
|
+
time.sleep(0.05)
|
|
125
|
+
outgoing.put_nowait(None) # clean stop
|
|
126
|
+
|
|
127
|
+
_install_fake_client(monkeypatch, driver)
|
|
128
|
+
|
|
129
|
+
messages, status = proto._mqtt_session_sync(
|
|
130
|
+
"wss://broker.example/mqtt", "u", "p", "cid",
|
|
131
|
+
["t/a"], [], 30.0, outgoing_queue=outgoing,
|
|
132
|
+
)
|
|
133
|
+
assert ("t/after", "live") in messages # survived the blip
|
|
134
|
+
assert status["disconnected_since"] is None # cleared on reconnect
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""AiDot camera and device library."""
|
|
2
|
-
|
|
3
|
-
import logging as _logging
|
|
4
|
-
|
|
5
|
-
from .client import AidotClient
|
|
6
|
-
from .device_client import DeviceClient
|
|
7
|
-
from .discover import Discover
|
|
8
|
-
from .exceptions import (
|
|
9
|
-
AidotAuthFailed,
|
|
10
|
-
AidotAuthTokenExpired,
|
|
11
|
-
AidotCameraBusy,
|
|
12
|
-
AidotError,
|
|
13
|
-
AidotNotLogin,
|
|
14
|
-
AidotOSError,
|
|
15
|
-
AidotUserOrPassIncorrect,
|
|
16
|
-
HTTPError,
|
|
17
|
-
InvalidHost,
|
|
18
|
-
InvalidURL,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
# The vendored aiortc RTP receiver/sender log EVERY media packet at DEBUG. When a
|
|
22
|
-
# user enables DEBUG on the parent ``aidot`` logger to diagnose the integration,
|
|
23
|
-
# that becomes thousands of lines per second and floods the logs (in one capture,
|
|
24
|
-
# 99% of all lines). Cap just those two per-packet loggers at INFO so enabling
|
|
25
|
-
# ``aidot`` DEBUG stays useful - the diagnostically valuable aiortc DEBUG (DTLS,
|
|
26
|
-
# ICE, SCTP/DCEP) still flows. Respect an explicit level if the user set one.
|
|
27
|
-
for _chatty in ("aidot._vendor.aiortc.rtcrtpreceiver", "aidot._vendor.aiortc.rtcrtpsender"):
|
|
28
|
-
_lg = _logging.getLogger(_chatty)
|
|
29
|
-
if _lg.level == _logging.NOTSET:
|
|
30
|
-
_lg.setLevel(_logging.INFO)
|
|
31
|
-
|
|
32
|
-
__all__ = [
|
|
33
|
-
"AidotAuthFailed",
|
|
34
|
-
"AidotAuthTokenExpired",
|
|
35
|
-
"AidotCameraBusy",
|
|
36
|
-
"AidotClient",
|
|
37
|
-
"AidotError",
|
|
38
|
-
"AidotNotLogin",
|
|
39
|
-
"AidotOSError",
|
|
40
|
-
"AidotUserOrPassIncorrect",
|
|
41
|
-
"DeviceClient",
|
|
42
|
-
"Discover",
|
|
43
|
-
"HTTPError",
|
|
44
|
-
"InvalidHost",
|
|
45
|
-
"InvalidURL",
|
|
46
|
-
]
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""Importing the library caps the vendored aiortc per-packet loggers.
|
|
2
|
-
|
|
3
|
-
aiortc's RTP receiver/sender log every media packet at DEBUG. Enabling DEBUG on
|
|
4
|
-
the parent ``aidot`` logger (the normal way to diagnose the integration) would
|
|
5
|
-
otherwise unleash thousands of lines per second. The package init caps just those
|
|
6
|
-
two loggers at INFO so DEBUG on ``aidot`` stays useful without the firehose.
|
|
7
|
-
"""
|
|
8
|
-
import logging
|
|
9
|
-
|
|
10
|
-
import aidot # noqa: F401 - import triggers the logger caps
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_vendored_aiortc_packet_loggers_capped_to_info():
|
|
14
|
-
for name in (
|
|
15
|
-
"aidot._vendor.aiortc.rtcrtpreceiver",
|
|
16
|
-
"aidot._vendor.aiortc.rtcrtpsender",
|
|
17
|
-
):
|
|
18
|
-
lg = logging.getLogger(name)
|
|
19
|
-
assert lg.level == logging.INFO, f"{name} not capped (level={lg.level})"
|
|
20
|
-
# Even with the parent 'aidot' logger at DEBUG, the per-packet DEBUG is off.
|
|
21
|
-
logging.getLogger("aidot").setLevel(logging.DEBUG)
|
|
22
|
-
assert not lg.isEnabledFor(logging.DEBUG), f"{name} still emits DEBUG"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/base.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/g711.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/g722.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/h264.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/opus.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/codecs/vpx.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/contrib/media.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/exceptions.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/jitterbuffer.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/mediastreams.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcdatachannel.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcicetransport.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcrtpreceiver.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/aidot/_vendor/aiortc/rtcrtpsender.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
|
|
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-0.11.9 → python_aidot_cameras-0.11.11}/aidot/models/device_client_model.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-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_deferred_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_device_login_guard.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_device_user_info_cache.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_dtls_not_ready_burst.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_dtls_skip_signaling_wait.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_highport_nomination.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_keyframe_prompter.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_live_stream_param.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_no_undefined_names.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_offline_keepalive_pause.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_post_merge_hardening.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_echo_wait_timeout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_fast_liveplay.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_ffmpeg_stderr_log.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_idle_release.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_sdes_open_cleanup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_security_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_serializable_login_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_stream_drain_ownership.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.9 → python_aidot_cameras-0.11.11}/tests/test_token_file_robustness.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|