python-aidot-cameras 0.11.0__tar.gz → 0.11.1__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.0/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.1}/PKG-INFO +1 -1
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/client.py +58 -3
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/pyproject.toml +1 -1
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
- python_aidot_cameras-0.11.1/tests/test_offline_keepalive_pause.py +112 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/LICENSE +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/MANIFEST.in +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/README.md +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/__init__.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/__main__.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/__init__.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/aes_utils.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/__init__.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/constants.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/controls.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/go2rtc.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/lan_control.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/models.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/playback.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/protocol.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/sdes.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/sdes_open.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/tutk.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/webrtc.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/camera/webrtc_open.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/client.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/const.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/credentials.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/device_client.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/discover.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/exceptions.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/g711.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/login_const.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/models/__init__.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/models/device_client_model.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/models/device_model.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/models/discover_model.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/py.typed +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/setup.cfg +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_wait_or_event.py +0 -0
{python_aidot_cameras-0.11.0/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.1}/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.1
|
|
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
|
|
@@ -86,6 +86,13 @@ from .protocol import ( # noqa: F401 - re-exported for device_client / back-com
|
|
|
86
86
|
|
|
87
87
|
_LOGGER = logging.getLogger(__name__)
|
|
88
88
|
|
|
89
|
+
# Offline keepalive pause (see CameraMixin._backoff_or_offline_pause): while the
|
|
90
|
+
# cloud explicitly reports a device offline, failed-open retries re-check the
|
|
91
|
+
# flag every RECHECK seconds and only probe a real open every PROBE seconds,
|
|
92
|
+
# instead of consuming an open-gate slot on the normal reconnect cadence.
|
|
93
|
+
_OFFLINE_RECHECK_S = float(os.environ.get("AIDOT_OFFLINE_RECHECK_S", "30"))
|
|
94
|
+
_OFFLINE_PROBE_S = float(os.environ.get("AIDOT_OFFLINE_PROBE_S", "600"))
|
|
95
|
+
|
|
89
96
|
# Strong refs to fire-and-forget tasks: asyncio only keeps weak refs, so a
|
|
90
97
|
# discarded task can be garbage-collected mid-flight. Discarded on completion.
|
|
91
98
|
_BG_TASKS: set = set()
|
|
@@ -761,8 +768,56 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
761
768
|
self.status.update_from_camera_attributes(props)
|
|
762
769
|
if device.get("online") is not None:
|
|
763
770
|
self.status.online = bool(device.get("online"))
|
|
771
|
+
# The cloud explicitly reported reachability - only then may the
|
|
772
|
+
# keepalive loops trust an offline flag (see
|
|
773
|
+
# _backoff_or_offline_pause; DeviceStatusData.online defaults to
|
|
774
|
+
# False, which must not read as "offline").
|
|
775
|
+
self._cloud_online_explicit = True
|
|
764
776
|
return self.status
|
|
765
777
|
|
|
778
|
+
async def _backoff_or_offline_pause(self, delay: float) -> None:
|
|
779
|
+
"""Sleep a reconnect backoff, extended while the device is cloud-offline.
|
|
780
|
+
|
|
781
|
+
Normal case (device online, or the cloud never reported an online
|
|
782
|
+
flag): plain ``asyncio.sleep(delay)`` - behavior unchanged.
|
|
783
|
+
|
|
784
|
+
When an open just failed AND the cloud has explicitly reported the
|
|
785
|
+
device offline (``update_status_from_device`` saw ``online: false``),
|
|
786
|
+
hold here instead of retrying on the pacer cadence: re-check the flag
|
|
787
|
+
every ``AIDOT_OFFLINE_RECHECK_S`` (default 30 s) and resume the moment
|
|
788
|
+
it flips back on, or fall through after ``AIDOT_OFFLINE_PROBE_S``
|
|
789
|
+
(default 600 s) so one probe open still runs in case the cloud flag is
|
|
790
|
+
stale. This keeps a dead/unpowered camera from consuming an open-gate
|
|
791
|
+
slot every backoff cycle and starving live cameras' cold opens
|
|
792
|
+
(observed live: two unpowered A000088s cycling 30 s open attempts
|
|
793
|
+
pushed a healthy camera's cold open past two minutes).
|
|
794
|
+
"""
|
|
795
|
+
if self.status.online or not getattr(self, "_cloud_online_explicit", False):
|
|
796
|
+
await asyncio.sleep(delay)
|
|
797
|
+
return
|
|
798
|
+
_LOGGER.info(
|
|
799
|
+
"keepalive[%s]: device is cloud-offline - pausing open retries "
|
|
800
|
+
"(recheck %.0fs, probe %.0fs)",
|
|
801
|
+
self.device_id, _OFFLINE_RECHECK_S, _OFFLINE_PROBE_S,
|
|
802
|
+
)
|
|
803
|
+
_t0 = time.monotonic()
|
|
804
|
+
while (_elapsed := time.monotonic() - _t0) < _OFFLINE_PROBE_S:
|
|
805
|
+
await asyncio.sleep(
|
|
806
|
+
min(_OFFLINE_RECHECK_S, max(0.1, _OFFLINE_PROBE_S - _elapsed))
|
|
807
|
+
)
|
|
808
|
+
if not getattr(self, "_streaming_active", False):
|
|
809
|
+
return
|
|
810
|
+
if self.status.online:
|
|
811
|
+
_LOGGER.info(
|
|
812
|
+
"keepalive[%s]: device back online - resuming opens",
|
|
813
|
+
self.device_id,
|
|
814
|
+
)
|
|
815
|
+
return
|
|
816
|
+
_LOGGER.debug(
|
|
817
|
+
"keepalive[%s]: offline probe interval elapsed - probing one open",
|
|
818
|
+
self.device_id,
|
|
819
|
+
)
|
|
820
|
+
|
|
766
821
|
# -- Camera helpers ------------------------------------------------------ #
|
|
767
822
|
|
|
768
823
|
@property
|
|
@@ -2560,7 +2615,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
2560
2615
|
self.device_id, _delay, exc,
|
|
2561
2616
|
)
|
|
2562
2617
|
try:
|
|
2563
|
-
await
|
|
2618
|
+
await self._backoff_or_offline_pause(_delay)
|
|
2564
2619
|
except asyncio.CancelledError:
|
|
2565
2620
|
return
|
|
2566
2621
|
continue
|
|
@@ -2735,7 +2790,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
2735
2790
|
self.device_id, _delay, exc,
|
|
2736
2791
|
)
|
|
2737
2792
|
try:
|
|
2738
|
-
await
|
|
2793
|
+
await self._backoff_or_offline_pause(_delay)
|
|
2739
2794
|
except asyncio.CancelledError:
|
|
2740
2795
|
return
|
|
2741
2796
|
continue
|
|
@@ -3231,7 +3286,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
3231
3286
|
self.device_id, _delay, exc,
|
|
3232
3287
|
)
|
|
3233
3288
|
try:
|
|
3234
|
-
await
|
|
3289
|
+
await self._backoff_or_offline_pause(_delay)
|
|
3235
3290
|
except asyncio.CancelledError:
|
|
3236
3291
|
return
|
|
3237
3292
|
continue
|
|
@@ -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.1"
|
|
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.0 → python_aidot_cameras-0.11.1/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.1
|
|
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
|
|
@@ -91,6 +91,7 @@ tests/test_live_stream_param.py
|
|
|
91
91
|
tests/test_motion_poll.py
|
|
92
92
|
tests/test_narrow_pc_ice.py
|
|
93
93
|
tests/test_no_undefined_names.py
|
|
94
|
+
tests/test_offline_keepalive_pause.py
|
|
94
95
|
tests/test_open_gate_delay.py
|
|
95
96
|
tests/test_persistent_mqtt.py
|
|
96
97
|
tests/test_persistent_mqtt_robustness.py
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Offline keepalive pause: failed-open retries hold while the device is
|
|
2
|
+
cloud-offline (CameraMixin._backoff_or_offline_pause) instead of consuming an
|
|
3
|
+
open-gate slot on the normal reconnect cadence."""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
|
|
9
|
+
import aidot.camera.client as camera_client
|
|
10
|
+
from aidot.camera.client import CameraMixin
|
|
11
|
+
from aidot.device_client import DeviceStatusData
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _stub(online: bool, explicit: bool, streaming: bool = True) -> SimpleNamespace:
|
|
15
|
+
s = SimpleNamespace(
|
|
16
|
+
status=DeviceStatusData(),
|
|
17
|
+
device_id="test-device",
|
|
18
|
+
_streaming_active=streaming,
|
|
19
|
+
)
|
|
20
|
+
s.status.online = online
|
|
21
|
+
if explicit:
|
|
22
|
+
s._cloud_online_explicit = True
|
|
23
|
+
return s
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _run(coro):
|
|
27
|
+
return asyncio.run(coro)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_online_device_sleeps_plain_delay():
|
|
31
|
+
stub = _stub(online=True, explicit=True)
|
|
32
|
+
t0 = time.monotonic()
|
|
33
|
+
_run(CameraMixin._backoff_or_offline_pause(stub, 0.05))
|
|
34
|
+
took = time.monotonic() - t0
|
|
35
|
+
assert 0.04 <= took < 1.0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_unreported_online_state_never_pauses(monkeypatch):
|
|
39
|
+
# DeviceStatusData.online defaults to False; without the explicit marker
|
|
40
|
+
# that must NOT read as "offline" (a long pause here would slow every
|
|
41
|
+
# camera whose device dict was never merged).
|
|
42
|
+
monkeypatch.setattr(camera_client, "_OFFLINE_PROBE_S", 60.0)
|
|
43
|
+
stub = _stub(online=False, explicit=False)
|
|
44
|
+
t0 = time.monotonic()
|
|
45
|
+
_run(CameraMixin._backoff_or_offline_pause(stub, 0.05))
|
|
46
|
+
took = time.monotonic() - t0
|
|
47
|
+
assert took < 1.0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_offline_device_holds_then_probes(monkeypatch):
|
|
51
|
+
# Explicitly offline: the pause holds past the plain delay and falls
|
|
52
|
+
# through after the probe interval so one probe open still runs.
|
|
53
|
+
monkeypatch.setattr(camera_client, "_OFFLINE_RECHECK_S", 0.05)
|
|
54
|
+
monkeypatch.setattr(camera_client, "_OFFLINE_PROBE_S", 0.3)
|
|
55
|
+
stub = _stub(online=False, explicit=True)
|
|
56
|
+
t0 = time.monotonic()
|
|
57
|
+
_run(CameraMixin._backoff_or_offline_pause(stub, 0.01))
|
|
58
|
+
took = time.monotonic() - t0
|
|
59
|
+
assert took >= 0.3 # held for the whole probe interval
|
|
60
|
+
assert took < 2.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_offline_device_resumes_when_back_online(monkeypatch):
|
|
64
|
+
monkeypatch.setattr(camera_client, "_OFFLINE_RECHECK_S", 0.05)
|
|
65
|
+
monkeypatch.setattr(camera_client, "_OFFLINE_PROBE_S", 30.0)
|
|
66
|
+
stub = _stub(online=False, explicit=True)
|
|
67
|
+
|
|
68
|
+
async def scenario():
|
|
69
|
+
async def flip():
|
|
70
|
+
await asyncio.sleep(0.15)
|
|
71
|
+
stub.status.online = True
|
|
72
|
+
|
|
73
|
+
t0 = time.monotonic()
|
|
74
|
+
await asyncio.gather(
|
|
75
|
+
CameraMixin._backoff_or_offline_pause(stub, 0.01), flip()
|
|
76
|
+
)
|
|
77
|
+
return time.monotonic() - t0
|
|
78
|
+
|
|
79
|
+
took = _run(scenario())
|
|
80
|
+
# resumed within a couple of recheck ticks of the flip, far short of PROBE
|
|
81
|
+
assert 0.15 <= took < 2.0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_offline_pause_exits_when_streaming_stops(monkeypatch):
|
|
85
|
+
monkeypatch.setattr(camera_client, "_OFFLINE_RECHECK_S", 0.05)
|
|
86
|
+
monkeypatch.setattr(camera_client, "_OFFLINE_PROBE_S", 30.0)
|
|
87
|
+
stub = _stub(online=False, explicit=True)
|
|
88
|
+
|
|
89
|
+
async def scenario():
|
|
90
|
+
async def stop():
|
|
91
|
+
await asyncio.sleep(0.15)
|
|
92
|
+
stub._streaming_active = False
|
|
93
|
+
|
|
94
|
+
t0 = time.monotonic()
|
|
95
|
+
await asyncio.gather(
|
|
96
|
+
CameraMixin._backoff_or_offline_pause(stub, 0.01), stop()
|
|
97
|
+
)
|
|
98
|
+
return time.monotonic() - t0
|
|
99
|
+
|
|
100
|
+
took = _run(scenario())
|
|
101
|
+
assert took < 2.0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_update_status_sets_explicit_marker():
|
|
105
|
+
stub = SimpleNamespace(status=DeviceStatusData())
|
|
106
|
+
CameraMixin.update_status_from_device(stub, {"online": False})
|
|
107
|
+
assert stub.status.online is False
|
|
108
|
+
assert getattr(stub, "_cloud_online_explicit", False) is True
|
|
109
|
+
|
|
110
|
+
stub2 = SimpleNamespace(status=DeviceStatusData())
|
|
111
|
+
CameraMixin.update_status_from_device(stub2, {"name": "no online key"})
|
|
112
|
+
assert getattr(stub2, "_cloud_online_explicit", False) is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/base.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/g711.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/g722.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/h264.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/opus.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/codecs/vpx.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/contrib/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/contrib/media.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/exceptions.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/jitterbuffer.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/mediastreams.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcconfiguration.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcdatachannel.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcdtlstransport.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcicetransport.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcrtpparameters.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcrtpreceiver.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcrtpsender.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/aidot/_vendor/aiortc/rtcsctptransport.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
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/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
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_deferred_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_device_login_guard.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_device_user_info_cache.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_dtls_not_ready_burst.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/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.0 → python_aidot_cameras-0.11.1}/tests/test_highport_nomination.py
RENAMED
|
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.0 → python_aidot_cameras-0.11.1}/tests/test_no_undefined_names.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_persistent_mqtt_robustness.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_post_merge_hardening.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_echo_wait_timeout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.0 → python_aidot_cameras-0.11.1}/tests/test_sdes_fast_liveplay.py
RENAMED
|
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.0 → python_aidot_cameras-0.11.1}/tests/test_security_hardening.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
|