Skip to content

Instantly share code, notes, and snippets.

@hawkeye217
Last active December 6, 2025 23:29
Show Gist options
  • Select an option

  • Save hawkeye217/152a1d4ba80760dac95d46e143d37112 to your computer and use it in GitHub Desktop.

Select an option

Save hawkeye217/152a1d4ba80760dac95d46e143d37112 to your computer and use it in GitHub Desktop.
Check if an ONVIF-capable IP PTZ camera supports RelativeMove with FOV
# This script can help you determine if your PTZ is capable of
# working with Frigate NVR's autotracker.
#
# Cameras with a "YES" printed for each parameter at the end of
# the output will likely be supported by Frigate.
#
# Make sure you're using python3 with the onvif-zeep package
# Update the values for your camera below, then run:
# pip3 install onvif-zeep
# python3 ./fovtest.py
from onvif import ONVIFCamera
# UPDATE the IP address, ONVIF port, "admin" and "password" with your camera's details.
mycam = ONVIFCamera('192.168.1.100', 80, 'admin', 'password', '/etc/onvif/wsdl/')
print('Connected to ONVIF camera')
# Create media service object
media = mycam.create_media_service()
print('Created media service object')
print
# Get target profile
media_profiles = media.GetProfiles()
print('Media profiles')
print(media_profiles)
for key, onvif_profile in enumerate(media_profiles):
if (
not onvif_profile.VideoEncoderConfiguration
or onvif_profile.VideoEncoderConfiguration.Encoding != "H264"
):
continue
# Configure PTZ options
if onvif_profile.PTZConfiguration:
if onvif_profile.PTZConfiguration.DefaultContinuousPanTiltVelocitySpace is not None:
media_profile = onvif_profile
token = media_profile.token
print('Chosen token')
print(token)
print
# Create ptz service object
print('Creating PTZ object')
ptz = mycam.create_ptz_service()
print('Created PTZ service object')
print
# Get PTZ configuration options for getting option ranges
request = ptz.create_type("GetConfigurations")
configs = ptz.GetConfigurations(request)[0]
print('PTZ configurations:')
print(configs)
print()
request = ptz.create_type('GetConfigurationOptions')
request.ConfigurationToken = media_profile.PTZConfiguration.token
ptz_configuration_options = ptz.GetConfigurationOptions(request)
print('PTZ configuration options:')
print(ptz_configuration_options)
print()
print('PTZ service capabilities:')
request = ptz.create_type('GetServiceCapabilities')
service_capabilities = ptz.GetServiceCapabilities(request)
print(service_capabilities)
print()
print('PTZ status:')
request = ptz.create_type("GetStatus")
request.ProfileToken = token
status = ptz.GetStatus(request)
print(status)
pantilt_space_id = next(
(
i
for i, space in enumerate(
ptz_configuration_options.Spaces.RelativePanTiltTranslationSpace
)
if "TranslationSpaceFov" in space["URI"]
),
None,
)
zoom_space_id = next(
(
i
for i, space in enumerate(
ptz_configuration_options.Spaces.RelativeZoomTranslationSpace
)
if "TranslationGenericSpace" in space["URI"]
),
None,
)
def find_by_key(dictionary, target_key):
if target_key in dictionary:
return dictionary[target_key]
else:
for value in dictionary.values():
if isinstance(value, dict):
result = find_by_key(value, target_key)
if result is not None:
return result
return None
if find_by_key(vars(service_capabilities), "MoveStatus"):
print("YES - GetServiceCapabilities shows that the camera supports MoveStatus.")
else:
print("NO - GetServiceCapabilities shows that the camera does not support MoveStatus.")
# there doesn't seem to be an onvif standard with this optional parameter
# some cameras can report MoveStatus with or without PanTilt or Zoom attributes
pan_tilt_status = getattr(status.MoveStatus, "PanTilt", None)
zoom_status = getattr(status.MoveStatus, "Zoom", None)
if pan_tilt_status is not None and pan_tilt_status == "IDLE" and (
zoom_status is None or zoom_status == "IDLE"
):
print("YES - MoveStatus is reporting IDLE.")
# if it's not an attribute, see if MoveStatus even exists in the status result
if pan_tilt_status is None:
pan_tilt_status = getattr(status.MoveStatus, "MoveStatus", None)
# we're unsupported
if pan_tilt_status is None or (isinstance(pan_tilt_status, str) and pan_tilt_status not in [
"IDLE",
"MOVING",
]):
print("NO - MoveStatus not reporting IDLE or MOVING.")
if pantilt_space_id is not None and configs.DefaultRelativePanTiltTranslationSpace is not None:
print("YES - RelativeMove Pan/Tilt (FOV) is supported.")
else:
print("NO - RelativeMove Pan/Tilt is unsupported.")
if zoom_space_id is not None:
print("YES - RelativeMove Zoom is supported.")
else:
print("NO - RelativeMove Zoom is unsupported.")
@ProxMoxBox
Copy link

ProxMoxBox commented May 23, 2025

When I run the script I get the following response, does that mean my camera wouldn't work, or does it mean code not working prpoperly? I didn't get any yes or no;s.

Traceback (most recent call last):
  File "/home/manny/Downloads/./fovtest.py", line 15, in <module>
    mycam = ONVIFCamera('http://10.69.69.10', 80, 'myuser', 'mypw', '/etc/onvif/wsdl/')
  File "/usr/local/lib/python3.10/dist-packages/onvif/client.py", line 216, in __init__
    self.update_xaddrs()
  File "/usr/local/lib/python3.10/dist-packages/onvif/client.py", line 223, in update_xaddrs
    self.devicemgmt  = self.create_devicemgmt_service()
  File "/usr/local/lib/python3.10/dist-packages/onvif/client.py", line 333, in create_devicemgmt_service
    return self.create_onvif_service('devicemgmt', from_template)
  File "/usr/local/lib/python3.10/dist-packages/onvif/client.py", line 312, in create_onvif_service
    xaddr, wsdl_file, binding_name = self.get_definition(name, portType)
  File "/usr/local/lib/python3.10/dist-packages/onvif/client.py", line 292, in get_definition
    raise ONVIFError('No such file: %s' % wsdlpath)
onvif.exceptions.ONVIFError: Unknown error: No such file: /etc/onvif/wsdl/devicemgmt.wsdl```

When running this on Windows, I worked around this by editing this in the fovtest.py:
mycam = ONVIFCamera('http://10.69.69.10', 80, 'myuser', 'mypw', '/etc/onvif/wsdl/')
to
mycam = ONVIFCamera('http://10.69.69.10', 80, 'myuser', 'mypw', '/wsdl/')

I then copied the wsdl folder as well as the fovtest.py to the root of my C drive and just ran the fovtest.py from there.
Hopefully this can help more people report back.

Unfortunately my super el cheapo Aliexpress PTZ cam doesn't support RelativeMove PanTilt (nor was I expecting it to of course! :D), but surprisingly it did support RelativeMove Zoom :)

At least now I know that when I invest in a decent PT(Z), I can run the script and quickly find out so that I can start to experiment with this feature in Frigate as I've been dying to try this as well as the upcoming LPR. Great job Frigate team!
Was thinking of an Amcrest PTZ which 'should' be supported according to the Frigate wiki but if anyone has any pointers it would be much appreciated.

@gardar
Copy link

gardar commented May 27, 2025

How about implementing a wsdl path detection with something like this?

import os
from onvif import ONVIFCamera
import onvif as onvif_pkg

# 1) Try the 'wsdl' folder inside the onvif package itself
pkg_dir   = os.path.dirname(onvif_pkg.__file__)
wsdl_dir1 = os.path.join(pkg_dir, 'wsdl')

# 2) Try the sibling 'wsdl' folder next to the onvif package
site_pkg  = os.path.dirname(pkg_dir)
wsdl_dir2 = os.path.join(site_pkg, 'wsdl')

# 3) Fallback to /etc/onvif/wsdl
candidates = [wsdl_dir1, wsdl_dir2, '/etc/onvif/wsdl']

for d in candidates:
    if os.path.isdir(d):
        wsdl_dir = d
        break
else:
    raise FileNotFoundError(
        "Could not find WSDL files; looked in:\n  " +
        "\n  ".join(candidates)
    )
# UPDATE the IP address, ONVIF port, "admin" and "password" with your camera's details.
mycam = ONVIFCamera('192.168.1.100', 80, 'admin', 'password', wsdl_dir)

@hyacin75
Copy link

hyacin75 commented Jun 2, 2025

It doesn't look like this low-end Dahua camera supports relative movement. It's missing two necessary firmware features to support Frigate's autotracking.

That page that is floating around that shows supported cameras, and shows Dahua with all green and no notes, should really be destroyed or updated 😞

Can't imagine how many people are spending how much money on cameras just to find out they won't work. 😢

I'm already on my second and now considering a 3rd, but am absolutely terrified I'm going to end up in the same boat, and even further in the hole.

@eporsche
Copy link

eporsche commented Oct 28, 2025

I created a small wrapper for the ieGeek Baby1t which also does not support relative movement. The wrapper translates relative movement to continuous movement (which works). Maybe this can also work for other low-end devices. Try it out here:
https://github.com/eporsche/frigate-to-baby1t-onvif-proxy

Currently missing is the zoom functionality and the "automatic follow". For "automatic follow" to work - frigate expects some "home" profile, wich will center the camera. This has not been implemented yet.

@hawkeye217
Copy link
Author

You may be interested in this post: blakeblackshear/frigate#16960

@tovy14
Copy link

tovy14 commented Oct 31, 2025

sorry to bump this thread, i know it is unrelated, but after upgrading the Dahua H4C cameras, the PTZ is not working anymore from Frigate. i see the traffic is automatically redirect to port 443 for the request, i guess i would have to disable https on the camera, but just found it a little odd. also read that bitdefender flagged over the summer this type of camera for some security issues exactly for the onvif protocol. any help would be appreciated

getting this when running fovtest:

onvif.exceptions.ONVIFError: Unknown error: HTTPSConnectionPool(host='192.168.88.240', port=443): Max retries exceeded with url: /onvif/device_service (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)')))

frigate logs:

2025-10-31 13:43:14.672268102  [2025-10-31 13:43:14] frigate.ptz.onvif              ERROR   : Onvif connection failed for living_room: 'NoneType' object has no attribute 'getroottree'
2025-10-31 13:43:14.672768682  [2025-10-31 13:43:14] frigate.ptz.onvif              WARNING : ONVIF initialization failed for living_room
2025-10-31 13:43:14.673413797  [2025-10-31 13:43:14] frigate.ptz.onvif              DEBUG   : Could not initialize ONVIF for living_room```


UPDATE: managed to get PTZ working again in frigate by disabling the HTTPS for the camera under Settings -> System -> Safety -> HTTPS

@hawkeye217
Copy link
Author

You probably could have also set tls_insecure under onvif in your Frigate config for that camera.

@tovy14
Copy link

tovy14 commented Nov 1, 2025

You probably could have also set tls_insecure under onvif in your Frigate config for that camera.

The more you know, thank you the suggestion, I am exposing thr cameras to the www, so not having https is not a big deal for me.

UPDATE: Even with tls_insecure the ptz functionality is not working, seems like deactivating HTTPS resolves it for me

@Yunlliang
Copy link

  'PanTiltSpeedSpace': [
        {
            'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace',
            'XRange': {
                'Min': 0.0,
                'Max': 1.0
            }
        }
    ],
    'ZoomSpeedSpace': [
        {
            'URI': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/ZoomGenericSpeedSpace',
            'XRange': {
                'Min': 0.0,
                'Max': 1.0
            }
        }
    ],
    'Extension': None,
    '_attr_1': None
},
'PTZTimeout': {
    'Min': datetime.timedelta(seconds=1),
    'Max': datetime.timedelta(seconds=600)
},
'_value_1': None,
'PTControlDirection': None,
'Extension': None,
'_attr_1': None

}

PTZ service capabilities:
{
'_value_1': None,
'EFlip': False,
'Reverse': False,
'GetCompatibleConfigurations': True,
'_attr_1': {
'MoveStatus': 'true',
'StatusPosition': 'true'
}
}

PTZ status:
{
'Position': {
'PanTilt': {
'x': 0.318389,
'y': -0.290667,
'space': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace'
},
'Zoom': {
'x': 0.0,
'space': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace'
}
},
'MoveStatus': {
'PanTilt': 'IDLE',
'Zoom': 'IDLE'
},
'Error': 'NO error',
'UtcTime': datetime.datetime(2025, 11, 7, 12, 50, 18, tzinfo=<isodate.tzinfo.Utc object at 0x7f9e2e816cf0>),
'_value_1': None,
'_attr_1': None
}
YES - GetServiceCapabilities shows that the camera supports MoveStatus.
YES - MoveStatus is reporting IDLE.
YES - RelativeMove Pan/Tilt (FOV) is supported.
YES - RelativeMove Zoom is supported.
(onvif_env) yunliang@debian-odoo:~$
#############################
Hello, I tested my ONVIF PTZ camera using fovtest.py, and the PTZ control capabilities work correctly. The camera reports:

RelativeMove is supported (Pan, Tilt, and Zoom)

GetServiceCapabilities shows support for MoveStatus and StatusPosition

GetStatus returns valid position values, and MoveStatus transitions correctly

PanTiltSpeedSpace and ZoomSpeedSpace range from 0.0 to 1.0

PTZTimeout is valid

So the ONVIF PTZ control itself is working correctly.

However, when running Frigate PTZ autotracking calibration, Frigate runs from 0% to 100%, the camera moves during calibration, but the result ends with:

Autotracking calibration failed.

It looks like Frigate is unable to compute FOV calibration values from the returned position data.

Possible causes I’m considering:

The camera’s coordinate space for Pan/Tilt may not match the expected range.

The FOV math may assume normalized -1.0~+1.0 coordinates, but the camera returns a different scaling.

The PTZ status space:
space = http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace
may require special handling.

I confirmed that:

/ptz/relative_move works correctly

Move status transitions IDLE → MOVING → IDLE normally

No ONVIF permissions issues

I can provide full logs or a video if helpful.

Thanks in advance for your help!

@hawkeye217
Copy link
Author

You should open your own support discussion on the Frigate GitHub.

@Yunlliang
Copy link

你应该在 Frigate 的 GitHub 仓库上发起你自己的支持讨论。

Okay, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment