From 3aadc88a2979596d4aa733386d01ff570af9bca1 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:37:07 -0500 Subject: [PATCH] turn on switch by default if pan and/or tilt capability is available --- frigate/api/camera.py | 37 +++++++++++++++++++ .../settings/wizard/Step3StreamConfig.tsx | 6 +-- web/src/types/cameraWizard.ts | 1 + 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/frigate/api/camera.py b/frigate/api/camera.py index 339ee33a16..d57321bfd7 100644 --- a/frigate/api/camera.py +++ b/frigate/api/camera.py @@ -591,6 +591,32 @@ async def _connect_onvif_camera( raise first_error +def _supports_continuous_pan_tilt(nodes) -> bool: + """Whether any PTZ node advertises continuous pan/tilt velocity. + + The web UI's directional controls issue ContinuousMove with a PanTilt + velocity, so continuous pan/tilt is what makes those controls usable. This + is intentionally narrower than ptz_supported, which is true for any device + exposing the ONVIF PTZ service - including zoom/focus-only varifocal lenses. + """ + for node in nodes or []: + spaces = getattr(node, "SupportedPTZSpaces", None) or ( + node.get("SupportedPTZSpaces") if isinstance(node, dict) else None + ) + if spaces is None: + continue + + continuous = getattr(spaces, "ContinuousPanTiltVelocitySpace", None) or ( + spaces.get("ContinuousPanTiltVelocitySpace") + if isinstance(spaces, dict) + else None + ) + if continuous: + return True + + return False + + @router.get( "/onvif/probe", dependencies=[Depends(require_role(["admin"]))], @@ -748,6 +774,7 @@ async def onvif_probe( # Check PTZ support and capabilities ptz_supported = False + pan_tilt_supported = False presets_count = 0 autotrack_supported = False @@ -781,6 +808,15 @@ async def onvif_probe( logger.debug(f"Failed to get presets: {e}") presets_count = 0 + # Check for real (continuous) pan/tilt, which the UI controls need + if ptz_supported: + try: + nodes = await ptz_service.GetNodes() + pan_tilt_supported = _supports_continuous_pan_tilt(nodes) + logger.debug(f"Continuous pan/tilt supported: {pan_tilt_supported}") + except Exception as e: + logger.debug(f"Failed to read PTZ nodes for pan/tilt support: {e}") + # Check for autotracking support - requires both FOV relative movement and MoveStatus if ptz_supported and first_profile_token and ptz_config_token: # First check for FOV relative movement support @@ -900,6 +936,7 @@ async def onvif_probe( "firmware_version": device_info["firmware_version"], "profiles_count": profiles_count, "ptz_supported": ptz_supported, + "pan_tilt_supported": pan_tilt_supported, "presets_count": presets_count, "autotrack_supported": autotrack_supported, } diff --git a/web/src/components/settings/wizard/Step3StreamConfig.tsx b/web/src/components/settings/wizard/Step3StreamConfig.tsx index 3c0a8ac406..aaa4e145b7 100644 --- a/web/src/components/settings/wizard/Step3StreamConfig.tsx +++ b/web/src/components/settings/wizard/Step3StreamConfig.tsx @@ -78,7 +78,7 @@ export default function Step3StreamConfig({ const onvif = wizardData.onvif; const ptzSupported = wizardData.probeResult?.ptz_supported === true; - const hasPresets = (wizardData.probeResult?.presets_count ?? 0) > 0; + const panTiltSupported = wizardData.probeResult?.pan_tilt_supported === true; const onvifInvalid = !!onvif?.enabled && (!onvif.host?.trim() || !onvif.port); // Seed the PTZ pane once from the successful ONVIF probe @@ -87,7 +87,7 @@ export default function Step3StreamConfig({ if (ptzSupported && wizardData.onvif === undefined) { onUpdate({ onvif: { - enabled: hasPresets, + enabled: panTiltSupported, host: wizardData.host ?? "", port: wizardData.onvifPort ?? 8000, user: wizardData.username ?? "", @@ -97,7 +97,7 @@ export default function Step3StreamConfig({ } }, [ ptzSupported, - hasPresets, + panTiltSupported, wizardData.onvif, wizardData.host, wizardData.onvifPort, diff --git a/web/src/types/cameraWizard.ts b/web/src/types/cameraWizard.ts index 26c537896e..47f4fdbad6 100644 --- a/web/src/types/cameraWizard.ts +++ b/web/src/types/cameraWizard.ts @@ -212,6 +212,7 @@ export type OnvifProbeResponse = { firmware_version?: string; profiles_count?: number; ptz_supported?: boolean; + pan_tilt_supported?: boolean; presets_count?: number; autotrack_supported?: boolean; move_status_supported?: boolean;