Last active
December 6, 2025 23:29
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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') | |
| # 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) | |
| # Create ptz service object | |
| print('Creating PTZ object') | |
| ptz = mycam.create_ptz_service() | |
| print('Created PTZ service object') | |
| # 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.") |
Author
You should open your own support discussion on the Frigate GitHub.
你应该在 Frigate 的 GitHub 仓库上发起你自己的支持讨论。
Okay, thanks.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
}
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!