Skip to content

fix(ptz): stop-on-loss — ONVIF dead-man's-switch + VISCA halt-on-reconnect#115

Merged
TCVinNYC merged 1 commit into
mainfrom
fix/ptz-stop-on-loss
Jun 25, 2026
Merged

fix(ptz): stop-on-loss — ONVIF dead-man's-switch + VISCA halt-on-reconnect#115
TCVinNYC merged 1 commit into
mainfrom
fix/ptz-stop-on-loss

Conversation

@TCVinNYC

Copy link
Copy Markdown
Member

What / Why

A PTZ camera must not run away when its connection drops. This PR adds two backend-local safety mechanisms:

ONVIF dead-man's switch (onvif_ptz.py)

  • Sets Timeout=timedelta(seconds=1) on every ContinuousMove request so the camera self-stops if no new command arrives within ~1 s (connection loss, hung network). Uses Python datetime.timedelta which zeep accepts directly.
  • Wraps move_velocity in try/except with a throttled WARNING (one per 5 s) so a sustained transport outage logs once and never crashes the control loop.
  • stop() already had a bare except Exception: pass; unchanged.

VISCA halt-on-reconnect (visca_ip.py, visca_usb.py)

  • After a successful reconnect inside _send, always sends pantilt-stop + zoom-stop before retrying the pending command. A camera that was mid-pan when the link dropped halts rather than continuing a stale continuous-move. Sending stop to an already-stopped camera is a safe no-op.
  • No changes to reconnect.py or base.py; no changes to camera_worker.py / controller.py (those get the complementary heartbeat in a later PR).

Tests

12 new tests in tests/test_ptz_stop_on_loss.py:

  • ONVIF: Timeout field present and correct value; velocity fields intact; transport errors caught (move + stop); log throttle
  • VISCA IP: stop before move on reconnect (mid-move and already-stopped cases); ordering assertion
  • VISCA USB: same three tests for serial backend

No real hardware/network — all transports mocked (same style as test_ptz_reconnect.py).

Gate results

ruff check   — ✓ all checks passed
ruff format  — ✓ 4 files already formatted
mypy         — ✓ no issues (11 source files)
pytest       — ✓ 1218 passed in 30.62s
selftest     — ✓ all checks passed

🤖 Generated with Claude Code

…nnect

ONVIF: set Timeout=PT1S on every ContinuousMove so the camera self-stops
if no new command arrives (connection loss/hang). Wrap move_velocity and
stop in try/except with throttled WARNING so transport errors never crash
the control loop. Import time in the module so the throttle clock can be
patched in tests.

VISCA (IP + USB): always send a pantilt+zoom stop immediately after a
successful reconnect, before retrying the pending command. A camera that
was mid-pan when the link dropped halts instead of continuing a stale
continuous-move. Sending stop to an already-stopped camera is a safe no-op.

Tests: 12 new tests in tests/test_ptz_stop_on_loss.py covering all three
backends with mocked transports; all 1218 suite tests pass; ruff/mypy/selftest
green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@TCVinNYC TCVinNYC merged commit bd70d31 into main Jun 25, 2026
3 checks passed
@TCVinNYC TCVinNYC deleted the fix/ptz-stop-on-loss branch June 25, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant