Last active
March 20, 2025 18:44
-
-
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.") |
Looks like your camera's ONVIF implementation doesn't support a the GetServiceCapabilities call.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm getting the following for all my cameras:
Connected to ONVIF camera
Created media service object
Media profiles
[{
'Name': 'MainStream',
'VideoSourceConfiguration': {
'Name': 'VideoSourceMain',
'UseCount': 2,
'SourceToken': 'VideoSourceMain',
'Bounds': {
'x': 0,
'y': 0,
'width': 3840,
'height': 2160
},
'_value_1': None,
'Extension': None,
'token': 'VideoSourceMain',
'_attr_1': {
}
},
'AudioSourceConfiguration': {
'Name': 'AudioMainName',
'UseCount': 2,
'SourceToken': 'AudioMainSrcToken',
'_value_1': None,
'token': 'AudioMainToken',
'_attr_1': {
}
},
'VideoEncoderConfiguration': {
'Name': 'VideoEncodeMain',
'UseCount': 1,
'Encoding': 'H264',
'Resolution': {
'Width': 3840,
'Height': 2160
},
'Quality': 100.0,
'RateControl': {
'FrameRateLimit': 20,
'EncodingInterval': 1,
'BitrateLimit': 0
},
'MPEG4': {
'GovLength': 0,
'Mpeg4Profile': 'SP'
},
'H264': {
'GovLength': 20,
'H264Profile': 'High'
},
'Multicast': {
'Address': {
'Type': 'IPv4',
'IPv4Address': '192.168.10.61',
'IPv6Address': None
},
'Port': 0,
'TTL': 0,
'AutoStart': False,
'_value_1': None,
'_attr_1': None
},
'SessionTimeout': datetime.timedelta(seconds=720),
'_value_1': None,
'token': 'VideoEncodeMain',
'_attr_1': {
}
},
'AudioEncoderConfiguration': {
'Name': 'AudioMain',
'UseCount': 2,
'Encoding': 'G711',
'Bitrate': 64000,
'SampleRate': 16000,
'Multicast': {
'Address': {
'Type': 'IPv4',
'IPv4Address': '192.168.10.61',
'IPv6Address': None
},
'Port': 80,
'TTL': 1,
'AutoStart': False,
'_value_1': None,
'_attr_1': None
},
'SessionTimeout': datetime.timedelta(microseconds=60000),
'_value_1': None,
'token': 'G711',
'_attr_1': {
}
},
'VideoAnalyticsConfiguration': {
'Name': 'VideoAnalyticsName',
'UseCount': 3,
'AnalyticsEngineConfiguration': {
'AnalyticsModule': [
{
'Parameters': {
'SimpleItem': [
{
'Name': 'Sensitivity',
'Value': '80'
}
],
'ElementItem': [
{
'_value_1': <Element {http://www.onvif.org/ver10/schema}CellLayout at 0x7f02b19844c0>,
'Name': 'Layout'
}
],
'Extension': None,
'_attr_1': None
},
'Name': 'MyCellMotionModule',
'Type': 'tt:CellMotionEngine'
}
],
'Extension': None,
'_attr_1': None
},
'RuleEngineConfiguration': {
'Rule': [
{
'Parameters': {
'SimpleItem': [
{
'Name': 'MinCount',
'Value': '5'
},
{
'Name': 'AlarmOnDelay',
'Value': '100'
},
{
'Name': 'AlarmOffDelay',
'Value': '100'
},
{
'Name': 'ActiveCells',
'Value': '0P8A8A=='
}
],
'ElementItem': [],
'Extension': None,
'_attr_1': None
},
'Name': 'MyMotionDetectorRule',
'Type': 'tt:CellMotionDetector'
}
],
'Extension': None,
'_attr_1': None
},
'_value_1': None,
'token': 'VideoAnalyticsToken',
'_attr_1': {
}
},
'PTZConfiguration': {
'Name': 'ptz0',
'UseCount': 2,
'NodeToken': 'ptz0',
'DefaultAbsolutePantTiltPositionSpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'DefaultAbsoluteZoomPositionSpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace',
'DefaultRelativePanTiltTranslationSpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace',
'DefaultRelativeZoomTranslationSpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/TranslationGenericSpace',
'DefaultContinuousPanTiltVelocitySpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace',
'DefaultContinuousZoomVelocitySpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/VelocityGenericSpace',
'DefaultPTZSpeed': {
'PanTilt': {
'x': 1.0,
'y': 1.0,
'space': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace'
},
'Zoom': {
'x': 1.0,
'space': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/ZoomGenericSpeedSpace'
}
},
'DefaultPTZTimeout': datetime.timedelta(seconds=60),
'PanTiltLimits': {
'Range': {
'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
},
'YRange': {
'Min': -1.0,
'Max': 1.0
}
}
},
'ZoomLimits': {
'Range': {
'URI': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
}
}
},
'Extension': None,
'token': 'ptz0',
'_attr_1': {
}
},
'MetadataConfiguration': None,
'Extension': None,
'token': 'MainStream',
'fixed': False,
'_attr_1': {
}
}, {
'Name': 'SubStream',
'VideoSourceConfiguration': {
'Name': 'VideoSourceMain',
'UseCount': 2,
'SourceToken': 'VideoSourceMain',
'Bounds': {
'x': 0,
'y': 0,
'width': 720,
'height': 480
},
'_value_1': None,
'Extension': None,
'token': 'VideoSourceMain',
'_attr_1': {
}
},
'AudioSourceConfiguration': {
'Name': 'AudioMainName',
'UseCount': 2,
'SourceToken': 'AudioMainSrcToken',
'_value_1': None,
'token': 'AudioMainToken',
'_attr_1': {
}
},
'VideoEncoderConfiguration': {
'Name': 'VideoEncodeSub',
'UseCount': 1,
'Encoding': 'H264',
'Resolution': {
'Width': 720,
'Height': 480
},
'Quality': 100.0,
'RateControl': {
'FrameRateLimit': 25,
'EncodingInterval': 1,
'BitrateLimit': 2048
},
'MPEG4': {
'GovLength': 0,
'Mpeg4Profile': 'SP'
},
'H264': {
'GovLength': 20,
'H264Profile': 'High'
},
'Multicast': {
'Address': {
'Type': 'IPv4',
'IPv4Address': '192.168.10.61',
'IPv6Address': None
},
'Port': 0,
'TTL': 0,
'AutoStart': False,
'_value_1': None,
'_attr_1': None
},
'SessionTimeout': datetime.timedelta(seconds=720),
'_value_1': None,
'token': 'VideoEncodeSub',
'_attr_1': {
}
},
'AudioEncoderConfiguration': {
'Name': 'AudioMain',
'UseCount': 2,
'Encoding': 'G711',
'Bitrate': 64000,
'SampleRate': 16000,
'Multicast': {
'Address': {
'Type': 'IPv4',
'IPv4Address': '192.168.10.61',
'IPv6Address': None
},
'Port': 80,
'TTL': 1,
'AutoStart': False,
'_value_1': None,
'_attr_1': None
},
'SessionTimeout': datetime.timedelta(microseconds=60000),
'_value_1': None,
'token': 'G711',
'_attr_1': {
}
},
'VideoAnalyticsConfiguration': {
'Name': 'VideoAnalyticsName',
'UseCount': 3,
'AnalyticsEngineConfiguration': {
'AnalyticsModule': [
{
'Parameters': {
'SimpleItem': [
{
'Name': 'Sensitivity',
'Value': '80'
}
],
'ElementItem': [
{
'_value_1': <Element {http://www.onvif.org/ver10/schema}CellLayout at 0x7f02b1989c40>,
'Name': 'Layout'
}
],
'Extension': None,
'_attr_1': None
},
'Name': 'MyCellMotionModule',
'Type': 'tt:CellMotionEngine'
}
],
'Extension': None,
'_attr_1': None
},
'RuleEngineConfiguration': {
'Rule': [
{
'Parameters': {
'SimpleItem': [
{
'Name': 'MinCount',
'Value': '5'
},
{
'Name': 'AlarmOnDelay',
'Value': '100'
},
{
'Name': 'AlarmOffDelay',
'Value': '100'
},
{
'Name': 'ActiveCells',
'Value': '0P8A8A=='
}
],
'ElementItem': [],
'Extension': None,
'_attr_1': None
},
'Name': 'MyMotionDetectorRule',
'Type': 'tt:CellMotionDetector'
}
],
'Extension': None,
'_attr_1': None
},
'_value_1': None,
'token': 'VideoAnalyticsToken',
'_attr_1': {
}
},
'PTZConfiguration': {
'Name': 'ptz0',
'UseCount': 2,
'NodeToken': 'ptz0',
'DefaultAbsolutePantTiltPositionSpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'DefaultAbsoluteZoomPositionSpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace',
'DefaultRelativePanTiltTranslationSpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace',
'DefaultRelativeZoomTranslationSpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/TranslationGenericSpace',
'DefaultContinuousPanTiltVelocitySpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace',
'DefaultContinuousZoomVelocitySpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/VelocityGenericSpace',
'DefaultPTZSpeed': {
'PanTilt': {
'x': 1.0,
'y': 1.0,
'space': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace'
},
'Zoom': {
'x': 1.0,
'space': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/ZoomGenericSpeedSpace'
}
},
'DefaultPTZTimeout': datetime.timedelta(seconds=60),
'PanTiltLimits': {
'Range': {
'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
},
'YRange': {
'Min': -1.0,
'Max': 1.0
}
}
},
'ZoomLimits': {
'Range': {
'URI': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
}
}
},
'Extension': None,
'token': 'ptz0',
'_attr_1': {
}
},
'MetadataConfiguration': None,
'Extension': None,
'token': 'SubStream',
'fixed': False,
'_attr_1': {
}
}]
Chosen token
SubStream
Creating PTZ object
Created PTZ service object
PTZ configurations:
{
'Name': 'ptz0',
'UseCount': 2,
'NodeToken': 'ptz0',
'DefaultAbsolutePantTiltPositionSpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'DefaultAbsoluteZoomPositionSpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace',
'DefaultRelativePanTiltTranslationSpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace',
'DefaultRelativeZoomTranslationSpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/TranslationGenericSpace',
'DefaultContinuousPanTiltVelocitySpace': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace',
'DefaultContinuousZoomVelocitySpace': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/VelocityGenericSpace',
'DefaultPTZSpeed': {
'PanTilt': {
'x': 1.0,
'y': 1.0,
'space': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace'
},
'Zoom': {
'x': 1.0,
'space': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/ZoomGenericSpeedSpace'
}
},
'DefaultPTZTimeout': datetime.timedelta(seconds=60),
'PanTiltLimits': {
'Range': {
'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
},
'YRange': {
'Min': -1.0,
'Max': 1.0
}
}
},
'ZoomLimits': {
'Range': {
'URI': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
}
}
},
'Extension': None,
'token': 'ptz0',
'_attr_1': {
}
}
PTZ configuration options:
{
'Spaces': {
'AbsolutePanTiltPositionSpace': [
{
'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
},
'YRange': {
'Min': -1.0,
'Max': 1.0
}
}
],
'AbsoluteZoomPositionSpace': [
{
'URI': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/PositionGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
}
}
],
'RelativePanTiltTranslationSpace': [
{
'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/TranslationGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
},
'YRange': {
'Min': -1.0,
'Max': 1.0
}
}
],
'RelativeZoomTranslationSpace': [
{
'URI': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/TranslationGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
}
}
],
'ContinuousPanTiltVelocitySpace': [
{
'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/VelocityGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
},
'YRange': {
'Min': -1.0,
'Max': 1.0
}
}
],
'ContinuousZoomVelocitySpace': [
{
'URI': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/VelocityGenericSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
}
}
],
'PanTiltSpeedSpace': [
{
'URI': 'http://www.onvif.org/ver10/tptz/PanTiltSpaces/GenericSpeedSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
}
}
],
'ZoomSpeedSpace': [
{
'URI': 'http://www.onvif.org/ver10/tptz/ZoomSpaces/ZoomGenericSpeedSpace',
'XRange': {
'Min': -1.0,
'Max': 1.0
}
}
],
'Extension': None,
'_attr_1': None
},
'PTZTimeout': {
'Min': datetime.timedelta(seconds=1),
'Max': datetime.timedelta(seconds=60)
},
'_value_1': None,
'PTControlDirection': None,
'Extension': None,
'_attr_1': None
}
PTZ service capabilities:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/onvif/client.py", line 23, in wrapped
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/onvif/client.py", line 153, in wrapped
return call(params, callback)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/onvif/client.py", line 140, in call
ret = func(**params)
^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/zeep/proxy.py", line 46, in call
return self._proxy._binding.send(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/zeep/wsdl/bindings/soap.py", line 135, in send
return self.process_reply(client, operation_obj, response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/zeep/wsdl/bindings/soap.py", line 229, in process_reply
return self.process_error(doc, operation)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/zeep/wsdl/bindings/soap.py", line 391, in process_error
raise Fault(
zeep.exceptions.Fault
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/sdansro/./fovtest.py", line 65, in
service_capabilities = ptz.GetServiceCapabilities(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/onvif/client.py", line 26, in wrapped
raise ONVIFError(err)
onvif.exceptions.ONVIFError: Unknown error: