├── CHANGES.txt ├── onvif ├── version.txt ├── __init__.py ├── exceptions.py ├── definition.py ├── cli.py └── client.py ├── setup.cfg ├── MANIFEST.in ├── examples ├── events.py ├── rotate_image.py ├── streaming.py ├── continuous_move.py └── AbsoluteMove.py ├── wsdl ├── include ├── xmlmime ├── r-2.xsd ├── rw-2.wsdl ├── types.xsd ├── bf-2.xsd ├── remotediscovery.wsdl ├── ws-addr.xsd ├── envelope ├── addressing ├── t-1.xsd ├── xml.xsd ├── replay.wsdl ├── ws-discovery.xsd ├── receiver.wsdl ├── imaging.wsdl └── bw-2.wsdl ├── .gitignore ├── LICENSE ├── setup.py ├── tests └── test.py └── README.rst /CHANGES.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /onvif/version.txt: -------------------------------------------------------------------------------- 1 | 0.2.12 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include onvif/version.txt 2 | include CHANGES.txt 3 | include LICENSE 4 | include README. 5 | include wsdl/* 6 | -------------------------------------------------------------------------------- /onvif/__init__.py: -------------------------------------------------------------------------------- 1 | from onvif.client import ONVIFService, ONVIFCamera, SERVICES 2 | from onvif.exceptions import ONVIFError, ERR_ONVIF_UNKNOWN, \ 3 | ERR_ONVIF_PROTOCOL, ERR_ONVIF_WSDL, ERR_ONVIF_BUILD 4 | #from onvif import cli 5 | 6 | __all__ = ( 'ONVIFService', 'ONVIFCamera', 'ONVIFError', 7 | 'ERR_ONVIF_UNKNOWN', 'ERR_ONVIF_PROTOCOL', 8 | 'ERR_ONVIF_WSDL', 'ERR_ONVIF_BUILD', 9 | 'SERVICES'#, 'cli' 10 | ) 11 | -------------------------------------------------------------------------------- /examples/events.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from onvif import ONVIFCamera 3 | __author__ = 'vahid' 4 | 5 | 6 | if __name__ == '__main__': 7 | mycam = ONVIFCamera('192.168.1.64', 80, 'admin', 'intflow3121') #, no_cache=True) 8 | event_service = mycam.create_events_service() 9 | print(event_service.GetEventProperties()) 10 | 11 | pullpoint = mycam.create_pullpoint_service() 12 | req = pullpoint.create_type('PullMessages') 13 | req.MessageLimit=100 14 | print(pullpoint.PullMessages(req)) 15 | -------------------------------------------------------------------------------- /wsdl/include: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | 56 | .idea 57 | .venv 58 | Pipfile 59 | Pipfile.lock 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Quatanium Co., Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /examples/rotate_image.py: -------------------------------------------------------------------------------- 1 | from onvif import ONVIFCamera 2 | 3 | def rotate_image_180(): 4 | ''' Rotate the image ''' 5 | 6 | # Create the media service 7 | mycam = ONVIFCamera('192.168.0.112', 80, 'admin', '12345') 8 | media_service = mycam.create_media_service() 9 | 10 | profiles = media_service.GetProfiles() 11 | 12 | # Use the first profile and Profiles have at least one 13 | token = profiles[0]._token 14 | 15 | # Get all video source configurations 16 | configurations_list = media_service.GetVideoSourceConfigurations() 17 | 18 | # Use the first profile and Profiles have at least one 19 | video_source_configuration = configurations_list[0] 20 | 21 | # Enable rotate 22 | video_source_configuration.Extension[0].Rotate[0].Mode[0] = 'OFF' 23 | 24 | # Create request type instance 25 | request = media_service.create_type('SetVideoSourceConfiguration') 26 | request.Configuration = video_source_configuration 27 | 28 | # ForcePersistence is obsolete and should always be assumed to be True 29 | request.ForcePersistence = True 30 | 31 | # Set the video source configuration 32 | media_service.SetVideoSourceConfiguration(request) 33 | 34 | if __name__ == '__main__': 35 | rotate_image_180() 36 | -------------------------------------------------------------------------------- /onvif/exceptions.py: -------------------------------------------------------------------------------- 1 | ''' Core exceptions raised by the ONVIF Client ''' 2 | 3 | #from suds import WebFault, MethodNotFound, PortNotFound, \ 4 | # ServiceNotFound, TypeNotFound, BuildError, \ 5 | # SoapHeadersNotPermitted 6 | #TODO: Translate these errors into ONVIFError instances, mimicking the original 'suds' behaviour 7 | #from zeep.exceptions import XMLSyntaxError, XMLParseError, UnexpectedElementError, \ 8 | # WsdlSyntaxError, TransportError, LookupError, NamespaceError, Fault, ValidationError, \ 9 | # SignatureVerificationFailed, IncompleteMessage, IncompleteOperation 10 | # Error codes setting 11 | # Error unknown, e.g, HTTP errors 12 | ERR_ONVIF_UNKNOWN = 1 13 | # Protocol error returned by WebService, 14 | # e.g:DataEncodingUnknown, MissingAttr, InvalidArgs, ... 15 | ERR_ONVIF_PROTOCOL = 2 16 | # Error about WSDL instance 17 | ERR_ONVIF_WSDL = 3 18 | # Error about Build 19 | ERR_ONVIF_BUILD = 4 20 | 21 | 22 | class ONVIFError(Exception): 23 | def __init__(self, err): 24 | # if isinstance(err, (WebFault, SoapHeadersNotPermitted) if with_soap_exc else WebFault): 25 | # self.reason = err.fault.Reason.Text 26 | # self.fault = err.fault 27 | # self.code = ERR_ONVIF_PROTOCOL 28 | # elif isinstance(err, (ServiceNotFound, PortNotFound, 29 | # MethodNotFound, TypeNotFound)): 30 | # self.reason = str(err) 31 | # self.code = ERR_ONVIF_PROTOCOL 32 | # elif isinstance(err, BuildError): 33 | # self.reason = str(err) 34 | # self.code = ERR_ONVIF_BUILD 35 | # else: 36 | self.reason = 'Unknown error: ' + str(err) 37 | self.code = ERR_ONVIF_UNKNOWN 38 | 39 | def __str__(self): 40 | return self.reason 41 | -------------------------------------------------------------------------------- /wsdl/xmlmime: -------------------------------------------------------------------------------- 1 | 2 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /onvif/definition.py: -------------------------------------------------------------------------------- 1 | SERVICES = { 2 | # Name namespace wsdl file binding name 3 | 'devicemgmt' : {'ns': 'http://www.onvif.org/ver10/device/wsdl', 'wsdl': 'devicemgmt.wsdl', 'binding' : 'DeviceBinding'}, 4 | 'media' : {'ns': 'http://www.onvif.org/ver10/media/wsdl', 'wsdl': 'media.wsdl', 'binding' : 'MediaBinding'}, 5 | 'ptz' : {'ns': 'http://www.onvif.org/ver20/ptz/wsdl', 'wsdl': 'ptz.wsdl', 'binding' : 'PTZBinding'}, 6 | 'imaging' : {'ns': 'http://www.onvif.org/ver20/imaging/wsdl', 'wsdl': 'imaging.wsdl', 'binding' : 'ImagingBinding'}, 7 | 'deviceio' : {'ns': 'http://www.onvif.org/ver10/deviceIO/wsdl', 'wsdl': 'deviceio.wsdl', 'binding' : 'DeviceIOBinding'}, 8 | 'events' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'EventBinding'}, 9 | 'pullpoint' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'PullPointSubscriptionBinding'}, 10 | 'notification' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'NotificationProducerBinding'}, 11 | 'subscription' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'SubscriptionManagerBinding'}, 12 | 'analytics' : {'ns': 'http://www.onvif.org/ver20/analytics/wsdl', 'wsdl': 'analytics.wsdl', 'binding' : 'AnalyticsEngineBinding'}, 13 | 'recording' : {'ns': 'http://www.onvif.org/ver10/recording/wsdl', 'wsdl': 'recording.wsdl', 'binding' : 'RecordingBinding'}, 14 | 'search' : {'ns': 'http://www.onvif.org/ver10/search/wsdl', 'wsdl': 'search.wsdl', 'binding' : 'SearchBinding'}, 15 | 'replay' : {'ns': 'http://www.onvif.org/ver10/replay/wsdl', 'wsdl': 'replay.wsdl', 'binding' : 'ReplayBinding'}, 16 | 'receiver' : {'ns': 'http://www.onvif.org/ver10/receiver/wsdl', 'wsdl': 'receiver.wsdl', 'binding' : 'ReceiverBinding'}, 17 | } 18 | 19 | # 20 | #NSMAP = { } 21 | #for name, item in SERVICES.items(): 22 | # NSMAP[item['ns']] = name 23 | -------------------------------------------------------------------------------- /examples/streaming.py: -------------------------------------------------------------------------------- 1 | from onvif import ONVIFCamera 2 | 3 | def media_profile_configuration(): 4 | ''' 5 | A media profile consists of configuration entities such as video/audio 6 | source configuration, video/audio encoder configuration, 7 | or PTZ configuration. This use case describes how to change one 8 | configuration entity which has been already added to the media profile. 9 | ''' 10 | 11 | # Create the media service 12 | mycam = ONVIFCamera('192.168.1.64', 80, 'admin', 'intflow3121') 13 | media_service = mycam.create_media_service() 14 | 15 | profiles = media_service.GetProfiles() 16 | 17 | # Use the first profile and Profiles have at least one 18 | token = profiles[0]._token 19 | 20 | # Get all video encoder configurations 21 | configurations_list = media_service.GetVideoEncoderConfigurations() 22 | 23 | # Use the first profile and Profiles have at least one 24 | video_encoder_configuration = configurations_list[0] 25 | 26 | # Get video encoder configuration options 27 | options = media_service.GetVideoEncoderConfigurationOptions({'ProfileToken':token}) 28 | 29 | # Setup stream configuration 30 | video_encoder_configuration.Encoding = 'H264' 31 | # Setup Resolution 32 | video_encoder_configuration.Resolution.Width = \ 33 | options.H264.ResolutionsAvailable[0].Width 34 | video_encoder_configuration.Resolution.Height = \ 35 | options.H264.ResolutionsAvailable[0].Height 36 | # Setup Quality 37 | video_encoder_configuration.Quality = options.QualityRange.Min 38 | # Setup FramRate 39 | video_encoder_configuration.RateControl.FrameRateLimit = \ 40 | options.H264.FrameRateRange.Min 41 | # Setup EncodingInterval 42 | video_encoder_configuration.RateControl.EncodingInterval = \ 43 | options.H264.EncodingIntervalRange.Min 44 | # Setup Bitrate 45 | video_encoder_configuration.RateControl.BitrateLimit = \ 46 | options.Extension.H264[0].BitrateRange[0].Min[0] 47 | 48 | # Create request type instance 49 | request = media_service.create_type('SetVideoEncoderConfiguration') 50 | request.Configuration = video_encoder_configuration 51 | # ForcePersistence is obsolete and should always be assumed to be True 52 | request.ForcePersistence = True 53 | 54 | # Set the video encoder configuration 55 | media_service.SetVideoEncoderConfiguration(request) 56 | 57 | if __name__ == '__main__': 58 | media_profile_configuration() 59 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Setup script for the onvif_zeep package.""" 2 | import os 3 | import sysconfig 4 | import shutil 5 | from setuptools import setup, find_packages 6 | from setuptools.command.install import install 7 | 8 | 9 | here = os.path.abspath(os.path.dirname(__file__)) 10 | version_path = os.path.join(here, 'onvif/version.txt') 11 | version = open(version_path).read().strip() 12 | 13 | requires = ['zeep >= 3.0.0'] 14 | 15 | CLASSIFIERS = [ 16 | 'Development Status :: 3 - Alpha', 17 | 'Environment :: Console', 18 | 'Intended Audience :: Customer Service', 19 | 'Intended Audience :: Developers', 20 | 'Intended Audience :: Education', 21 | 'Intended Audience :: Science/Research', 22 | 'Intended Audience :: Telecommunications Industry', 23 | 'Natural Language :: English', 24 | 'Operating System :: POSIX', 25 | 'Topic :: Software Development :: Libraries :: Python Modules', 26 | 'Topic :: Multimedia :: Sound/Audio', 27 | 'Topic :: Utilities', 28 | "Programming Language :: Python", 29 | "Programming Language :: Python :: 2", 30 | "Programming Language :: Python :: 2.7", 31 | "Programming Language :: Python :: 3", 32 | "Programming Language :: Python :: 3.5", 33 | ] 34 | 35 | 36 | class CustomInstallCommand(install): 37 | """Custom install command to handle WSDL files.""" 38 | def run(self): 39 | # Run regular installation first 40 | install.run(self) 41 | 42 | # Now manually copy the wsdl files to site-packages/wsdl 43 | wsdl_src_dir = 'wsdl' 44 | wsdl_dst_dir = os.path.join(sysconfig.get_paths()['purelib'], 'wsdl') 45 | 46 | os.makedirs(wsdl_dst_dir, exist_ok=True) 47 | 48 | for file in os.listdir(wsdl_src_dir): 49 | src_path = os.path.join(wsdl_src_dir, file) 50 | dst_path = os.path.join(wsdl_dst_dir, file) 51 | shutil.copyfile(src_path, dst_path) 52 | 53 | 54 | setup( 55 | name='onvif_zeep', 56 | version=version, 57 | description='Python Client for ONVIF Camera', 58 | long_description=open('README.rst', 'r').read(), 59 | author='Cherish Chen', 60 | author_email='sinchb128@gmail.com', 61 | maintainer='sinchb', 62 | maintainer_email='sinchb128@gmail.com', 63 | license='MIT', 64 | keywords=['ONVIF', 'Camera', 'IPC'], 65 | url='http://github.com/quatanium/python-onvif', 66 | zip_safe=False, 67 | packages=find_packages(exclude=['docs', 'examples', 'tests']), 68 | install_requires=requires, 69 | entry_points={ 70 | 'console_scripts': ['onvif-cli = onvif.cli:main'] 71 | }, 72 | cmdclass={ 73 | 'install': CustomInstallCommand, 74 | }, 75 | ) 76 | -------------------------------------------------------------------------------- /wsdl/r-2.xsd: -------------------------------------------------------------------------------- 1 | 2 | 17 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /wsdl/rw-2.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 25 | 26 | 27 | 28 | 33 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /wsdl/types.xsd: -------------------------------------------------------------------------------- 1 | 2 | 28 | 33 | 34 | 35 | 36 | 37 | Type used to reference logical and physical entities. 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | General datastructure referenced by a token. 49 | Should be used as extension base. 50 | 51 | 52 | 53 | 54 | A service-unique identifier of the item. 55 | 56 | 57 | 58 | 59 | 60 | 61 | Type used for names of logical and physical entities. 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Description is optional and the maximum length is device specific. 73 | If the length is more than maximum length, it is silently chopped to the maximum length 74 | supported by the device/service (which may be 0). 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*-coding=utf-8 3 | from __future__ import print_function, division 4 | import unittest 5 | 6 | from onvif import ONVIFCamera, ONVIFError 7 | 8 | CAM_HOST = '192.168.1.64' 9 | CAM_PORT = 80 10 | CAM_USER = 'admin' 11 | CAM_PASS = 'intflow3121' 12 | 13 | DEBUG = False 14 | 15 | 16 | def log(ret): 17 | if DEBUG: 18 | print(ret) 19 | 20 | 21 | class TestDevice(unittest.TestCase): 22 | 23 | # Class level cam. Run this test more efficiently.. 24 | cam = ONVIFCamera(CAM_HOST, CAM_PORT, CAM_USER, CAM_PASS) 25 | 26 | # ***************** Test Capabilities *************************** 27 | def test_GetWsdlUrl(self): 28 | self.cam.devicemgmt.GetWsdlUrl() 29 | 30 | def test_GetServices(self): 31 | """ 32 | Returns a collection of the devices 33 | services and possibly their available capabilities 34 | """ 35 | params = {'IncludeCapability': True} 36 | self.cam.devicemgmt.GetServices(params) 37 | params = self.cam.devicemgmt.create_type('GetServices') 38 | params.IncludeCapability = False 39 | self.cam.devicemgmt.GetServices(params) 40 | 41 | def test_GetServiceCapabilities(self): 42 | """Returns the capabilities of the device service.""" 43 | self.cam.devicemgmt.GetServiceCapabilities() 44 | 45 | def test_GetCapabilities(self): 46 | """ 47 | Provides a backward compatible interface for the base capabilities. 48 | """ 49 | categories = ['PTZ', 'Media', 'Imaging', 50 | 'Device', 'Analytics', 'Events'] 51 | self.cam.devicemgmt.GetCapabilities() 52 | for category in categories: 53 | self.cam.devicemgmt.GetCapabilities({'Category': category}) 54 | 55 | with self.assertRaises(ONVIFError): 56 | self.cam.devicemgmt.GetCapabilities({'Category': 'unknown'}) 57 | 58 | # *************** Test Network ********************************* 59 | def test_GetHostname(self): 60 | """ Get the hostname from a device """ 61 | self.cam.devicemgmt.GetHostname() 62 | 63 | def test_SetHostname(self): 64 | """ 65 | Set the hostname on a device 66 | A device shall accept strings formatted according to 67 | RFC 1123 section 2.1 or alternatively to RFC 952, 68 | other string shall be considered as invalid strings 69 | """ 70 | pre_host_name = self.cam.devicemgmt.GetHostname() 71 | 72 | self.cam.devicemgmt.SetHostname({'Name': 'testHostName'}) 73 | self.assertEqual(self.cam.devicemgmt.GetHostname().Name, 'testHostName') 74 | self.cam.devicemgmt.SetHostname({'Name': pre_host_name.Name}) 75 | 76 | def test_SetHostnameFromDHCP(self): 77 | """ Controls whether the hostname shall be retrieved from DHCP """ 78 | ret = self.cam.devicemgmt.SetHostnameFromDHCP(dict(FromDHCP=False)) 79 | self.assertTrue(isinstance(ret, bool)) 80 | 81 | def test_GetDNS(self): 82 | """ Gets the DNS setting from a device """ 83 | ret = self.cam.devicemgmt.GetDNS() 84 | self.assertTrue(hasattr(ret, 'FromDHCP')) 85 | if not ret.FromDHCP and len(ret.DNSManual) > 0: 86 | log(ret.DNSManual[0].Type) 87 | log(ret.DNSManual[0].IPv4Address) 88 | 89 | def test_SetDNS(self): 90 | """ Set the DNS settings on a device """ 91 | self.cam.devicemgmt.SetDNS(dict(FromDHCP=False)) 92 | 93 | def test_GetNTP(self): 94 | """ Get the NTP settings from a device """ 95 | ret = self.cam.devicemgmt.GetNTP() 96 | if not ret.FromDHCP: 97 | self.assertTrue(hasattr(ret, 'NTPManual')) 98 | log(ret.NTPManual) 99 | 100 | def test_SetNTP(self): 101 | """Set the NTP setting""" 102 | self.cam.devicemgmt.SetNTP(dict(FromDHCP=False)) 103 | 104 | def test_GetDynamicDNS(self): 105 | """Get the dynamic DNS setting""" 106 | ret = self.cam.devicemgmt.GetDynamicDNS() 107 | log(ret) 108 | 109 | def test_SetDynamicDNS(self): 110 | """ Set the dynamic DNS settings on a device """ 111 | self.cam.devicemgmt.GetDynamicDNS() 112 | self.cam.devicemgmt.SetDynamicDNS({'Type': 'NoUpdate', 'Name': None, 113 | 'TTL': None}) 114 | 115 | 116 | if __name__ == '__main__': 117 | unittest.main() 118 | -------------------------------------------------------------------------------- /wsdl/bf-2.xsd: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 26 | 29 | 31 | 32 | 33 | Get access to the xml: attribute groups for xml:lang as declared on 'schema' 34 | and 'documentation' below 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 48 | 50 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-onvif-zeep 2 | ============ 3 | 4 | ONVIF Client Implementation in Python 5 | 6 | Dependencies 7 | ------------ 8 | `zeep `_ >= 3.0.0 9 | 10 | Install python-onvif-zeep 11 | ------------------------- 12 | **From Source** 13 | 14 | You should clone this repository and run setup.py:: 15 | 16 | cd python-onvif-zeep && python setup.py install 17 | 18 | Alternatively, you can run:: 19 | 20 | pip install --upgrade onvif_zeep 21 | 22 | 23 | Getting Started 24 | --------------- 25 | 26 | Initialize an ONVIFCamera instance 27 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 28 | 29 | :: 30 | 31 | from onvif import ONVIFCamera 32 | mycam = ONVIFCamera('192.168.0.2', 80, 'user', 'passwd', '/etc/onvif/wsdl/') 33 | 34 | Now, an ONVIFCamera instance is available. By default, a devicemgmt service is also available if everything is OK. 35 | 36 | So, all operations defined in the WSDL document:: 37 | 38 | /etc/onvif/wsdl/devicemgmt.wsdl 39 | 40 | are available. 41 | 42 | Get information from your camera 43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 44 | :: 45 | 46 | # Get Hostname 47 | resp = mycam.devicemgmt.GetHostname() 48 | print 'My camera`s hostname: ' + str(resp.Name) 49 | 50 | # Get system date and time 51 | dt = mycam.devicemgmt.GetSystemDateAndTime() 52 | tz = dt.TimeZone 53 | year = dt.UTCDateTime.Date.Year 54 | hour = dt.UTCDateTime.Time.Hour 55 | 56 | Configure (Control) your camera 57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 | 59 | To configure your camera, there are two ways to pass parameters to service methods. 60 | 61 | **Dict** 62 | 63 | This is the simpler way:: 64 | 65 | params = {'Name': 'NewHostName'} 66 | device_service.SetHostname(params) 67 | 68 | **Type Instance** 69 | 70 | This is the recommended way. Type instance will raise an 71 | exception if you set an invalid (or non-existent) parameter. 72 | 73 | :: 74 | 75 | params = mycam.devicemgmt.create_type('SetHostname') 76 | params.Hostname = 'NewHostName' 77 | mycam.devicemgmt.SetHostname(params) 78 | 79 | time_params = mycam.devicemgmt.create_type('SetSystemDateAndTime') 80 | time_params.DateTimeType = 'Manual' 81 | time_params.DaylightSavings = True 82 | time_params.TimeZone.TZ = 'CST-8:00:00' 83 | time_params.UTCDateTime.Date.Year = 2014 84 | time_params.UTCDateTime.Date.Month = 12 85 | time_params.UTCDateTime.Date.Day = 3 86 | time_params.UTCDateTime.Time.Hour = 9 87 | time_params.UTCDateTime.Time.Minute = 36 88 | time_params.UTCDateTime.Time.Second = 11 89 | mycam.devicemgmt.SetSystemDateAndTime(time_params) 90 | 91 | Use other services 92 | ~~~~~~~~~~~~~~~~~~ 93 | ONVIF protocol has defined many services. 94 | You can find all the services and operations `here `_. 95 | ONVIFCamera has support methods to create new services:: 96 | 97 | # Create ptz service 98 | ptz_service = mycam.create_ptz_service() 99 | # Get ptz configuration 100 | mycam.ptz.GetConfiguration() 101 | # Another way 102 | # ptz_service.GetConfiguration() 103 | 104 | Or create an unofficial service:: 105 | 106 | xaddr = 'http://192.168.0.3:8888/onvif/yourservice' 107 | yourservice = mycam.create_onvif_service('service.wsdl', xaddr, 'yourservice') 108 | yourservice.SomeOperation() 109 | # Another way 110 | # mycam.yourservice.SomeOperation() 111 | 112 | ONVIF CLI 113 | --------- 114 | python-onvif also provides a command line interactive interface: onvif-cli. 115 | onvif-cli is installed automatically. 116 | 117 | Single command example 118 | ~~~~~~~~~~~~~~~~~~~~~~ 119 | 120 | :: 121 | 122 | $ onvif-cli devicemgmt GetHostname --user 'admin' --password '12345' --host '192.168.0.112' --port 80 123 | True: {'FromDHCP': True, 'Name': hision} 124 | $ onvif-cli devicemgmt SetHostname "{'Name': 'NewerHostname'}" --user 'admin' --password '12345' --host '192.168.0.112' --port 80 125 | True: {} 126 | 127 | Interactive mode 128 | ~~~~~~~~~~~~~~~~ 129 | 130 | :: 131 | 132 | $ onvif-cli -u 'admin' -a '12345' --host '192.168.0.112' --port 80 --wsdl /etc/onvif/wsdl/ 133 | ONVIF >>> cmd 134 | analytics devicemgmt events imaging media ptz 135 | ONVIF >>> cmd devicemgmt GetWsdlUrl 136 | True: http://www.onvif.org/ 137 | ONVIF >>> cmd devicemgmt SetHostname {'Name': 'NewHostname'} 138 | ONVIF >>> cmd devicemgmt GetHostname 139 | True: {'Name': 'NewHostName'} 140 | ONVIF >>> cmd devicemgmt SomeOperation 141 | False: No Operation: SomeOperation 142 | 143 | NOTE: Tab completion is supported for interactive mode. 144 | 145 | Batch mode 146 | ~~~~~~~~~~ 147 | 148 | :: 149 | 150 | $ vim batchcmds 151 | $ cat batchcmds 152 | cmd devicemgmt GetWsdlUrl 153 | cmd devicemgmt SetHostname {'Name': 'NewHostname', 'FromDHCP': True} 154 | cmd devicemgmt GetHostname 155 | $ onvif-cli --host 192.168.0.112 -u admin -a 12345 -w /etc/onvif/wsdl/ < batchcmds 156 | ONVIF >>> True: http://www.onvif.org/ 157 | ONVIF >>> True: {} 158 | ONVIF >>> True: {'FromDHCP': False, 'Name': NewHostname} 159 | 160 | References 161 | ---------- 162 | 163 | * `ONVIF Offical Website `_ 164 | 165 | * `Operations Index `_ 166 | 167 | * `ONVIF Develop Documents `_ 168 | 169 | * `Foscam Python Lib `_ 170 | -------------------------------------------------------------------------------- /wsdl/remotediscovery.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /examples/continuous_move.py: -------------------------------------------------------------------------------- 1 | import asyncio, sys, os 2 | from onvif import ONVIFCamera 3 | 4 | IP="192.168.1.64" # Camera IP address 5 | PORT=80 # Port 6 | USER="admin" # Username 7 | PASS="intflow3121" # Password 8 | 9 | 10 | XMAX = 1 11 | XMIN = -1 12 | YMAX = 1 13 | YMIN = -1 14 | moverequest = None 15 | ptz = None 16 | active = False 17 | 18 | def do_move(ptz, request): 19 | # Start continuous move 20 | global active 21 | if active: 22 | ptz.Stop({'ProfileToken': request.ProfileToken}) 23 | active = True 24 | ptz.ContinuousMove(request) 25 | 26 | def move_up(ptz, request): 27 | print ('move up...') 28 | request.Velocity.PanTilt.x = 0 29 | request.Velocity.PanTilt.y = YMAX 30 | do_move(ptz, request) 31 | 32 | def move_down(ptz, request): 33 | print ('move down...') 34 | request.Velocity.PanTilt.x = 0 35 | request.Velocity.PanTilt.y = YMIN 36 | do_move(ptz, request) 37 | 38 | def move_right(ptz, request): 39 | print ('move right...') 40 | request.Velocity.PanTilt.x = XMAX 41 | request.Velocity.PanTilt.y = 0 42 | do_move(ptz, request) 43 | 44 | def move_left(ptz, request): 45 | print ('move left...') 46 | request.Velocity.PanTilt.x = XMIN 47 | request.Velocity.PanTilt.y = 0 48 | do_move(ptz, request) 49 | 50 | 51 | def move_upleft(ptz, request): 52 | print ('move up left...') 53 | request.Velocity.PanTilt.x = XMIN 54 | request.Velocity.PanTilt.y = YMAX 55 | do_move(ptz, request) 56 | 57 | def move_upright(ptz, request): 58 | print ('move up left...') 59 | request.Velocity.PanTilt.x = XMAX 60 | request.Velocity.PanTilt.y = YMAX 61 | do_move(ptz, request) 62 | 63 | def move_downleft(ptz, request): 64 | print ('move down left...') 65 | request.Velocity.PanTilt.x = XMIN 66 | request.Velocity.PanTilt.y = YMIN 67 | do_move(ptz, request) 68 | 69 | def move_downright(ptz, request): 70 | print ('move down left...') 71 | request.Velocity.PanTilt.x = XMAX 72 | request.Velocity.PanTilt.y = YMIN 73 | do_move(ptz, request) 74 | 75 | def setup_move(): 76 | mycam = ONVIFCamera(IP, PORT, USER, PASS) 77 | # Create media service object 78 | media = mycam.create_media_service() 79 | 80 | # Create ptz service object 81 | global ptz 82 | ptz = mycam.create_ptz_service() 83 | 84 | # Get target profile 85 | media_profile = media.GetProfiles()[0] 86 | 87 | # Get PTZ configuration options for getting continuous move range 88 | request = ptz.create_type('GetConfigurationOptions') 89 | request.ConfigurationToken = media_profile.PTZConfiguration.token 90 | ptz_configuration_options = ptz.GetConfigurationOptions(request) 91 | 92 | global moverequest 93 | moverequest = ptz.create_type('ContinuousMove') 94 | moverequest.ProfileToken = media_profile.token 95 | if moverequest.Velocity is None: 96 | moverequest.Velocity = ptz.GetStatus({'ProfileToken': media_profile.token}).Position 97 | moverequest.Velocity.PanTilt.space = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].URI 98 | moverequest.Velocity.Zoom.space = ptz_configuration_options.Spaces.ContinuousZoomVelocitySpace[0].URI 99 | 100 | 101 | # Get range of pan and tilt 102 | # NOTE: X and Y are velocity vector 103 | global XMAX, XMIN, YMAX, YMIN 104 | XMAX = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max 105 | XMIN = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Min 106 | YMAX = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max 107 | YMIN = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Min 108 | 109 | 110 | def readin(): 111 | """Reading from stdin and displaying menu""" 112 | global moverequest, ptz 113 | 114 | selection = sys.stdin.readline().strip("\n") 115 | lov=[ x for x in selection.split(" ") if x != ""] 116 | if lov: 117 | 118 | if lov[0].lower() in ["u","up"]: 119 | move_up(ptz,moverequest) 120 | elif lov[0].lower() in ["d","do","dow","down"]: 121 | move_down(ptz,moverequest) 122 | elif lov[0].lower() in ["l","le","lef","left"]: 123 | move_left(ptz,moverequest) 124 | elif lov[0].lower() in ["l","le","lef","left"]: 125 | move_left(ptz,moverequest) 126 | elif lov[0].lower() in ["r","ri","rig","righ","right"]: 127 | move_right(ptz,moverequest) 128 | elif lov[0].lower() in ["ul"]: 129 | move_upleft(ptz,moverequest) 130 | elif lov[0].lower() in ["ur"]: 131 | move_upright(ptz,moverequest) 132 | elif lov[0].lower() in ["dl"]: 133 | move_downleft(ptz,moverequest) 134 | elif lov[0].lower() in ["dr"]: 135 | move_downright(ptz,moverequest) 136 | elif lov[0].lower() in ["s","st","sto","stop"]: 137 | ptz.Stop({'ProfileToken': moverequest.ProfileToken}) 138 | active = False 139 | else: 140 | print("What are you asking?\tI only know, 'up','down','left','right', 'ul' (up left), \n\t\t\t'ur' (up right), 'dl' (down left), 'dr' (down right) and 'stop'") 141 | 142 | print("") 143 | print("Your command: ", end='',flush=True) 144 | 145 | 146 | if __name__ == '__main__': 147 | setup_move() 148 | move_upleft(ptz,moverequest) 149 | #if os.name == 'nt': 150 | # loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows 151 | # asyncio.set_event_loop(loop) 152 | #else: 153 | # loop = asyncio.get_event_loop() 154 | 155 | #try: 156 | # loop.add_reader(sys.stdin,readin) 157 | # print("Use Ctrl-C to quit") 158 | # print("Your command: ", end='',flush=True) 159 | # loop.run_forever() 160 | #except: 161 | # pass 162 | #finally: 163 | # loop.remove_reader(sys.stdin) 164 | # loop.close() 165 | -------------------------------------------------------------------------------- /wsdl/ws-addr.xsd: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /wsdl/envelope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Prose in the spec does not specify that attributes are allowed on the Body element 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Fault reporting structure 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /onvif/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | '''ONVIF Client Command Line Interface''' 3 | from __future__ import print_function, division 4 | import re 5 | from cmd import Cmd 6 | from ast import literal_eval 7 | from argparse import ArgumentParser, REMAINDER 8 | 9 | from zeep.exceptions import LookupError as MethodNotFound 10 | from zeep.xsd import String as Text 11 | from onvif import ONVIFCamera, ONVIFService, ONVIFError 12 | from onvif.definition import SERVICES 13 | import os.path 14 | 15 | SUPPORTED_SERVICES = SERVICES.keys() 16 | 17 | class ThrowingArgumentParser(ArgumentParser): 18 | def error(self, message): 19 | usage = self.format_usage() 20 | raise ValueError("%s\n%s" % (message, usage)) 21 | 22 | def success(message): 23 | print('True: ' + str(message)) 24 | 25 | def error(message): 26 | print('False: ' + str(message)) 27 | 28 | class ONVIFCLI(Cmd): 29 | prompt = 'ONVIF >>> ' 30 | client = None 31 | cmd_parser = None 32 | 33 | def setup(self, args): 34 | ''' `args`: Instance of `argparse.ArgumentParser` ''' 35 | # Create onvif camera client 36 | self.client = ONVIFCamera(args.host, args.port, 37 | args.user, args.password, 38 | args.wsdl, encrypt=args.encrypt) 39 | 40 | 41 | # Create cmd argument parser 42 | self.create_cmd_parser() 43 | 44 | def create_cmd_parser(self): 45 | # Create parser to parse CMD, `params` is optional. 46 | cmd_parser = ThrowingArgumentParser(prog='ONVIF CMD', 47 | usage='CMD service operation [params]') 48 | cmd_parser.add_argument('service') 49 | cmd_parser.add_argument('operation') 50 | cmd_parser.add_argument('params', default='{}', nargs=REMAINDER) 51 | self.cmd_parser = cmd_parser 52 | 53 | def do_cmd(self, line): 54 | '''Usage: CMD service operation [parameters]''' 55 | try: 56 | args = self.cmd_parser.parse_args(line.split()) 57 | except ValueError as err: 58 | return error(err) 59 | 60 | # Check if args.service is valid 61 | if args.service not in SUPPORTED_SERVICES: 62 | return error('No Service: ' + args.service) 63 | 64 | args.params = ''.join(args.params) 65 | # params is optional 66 | if not args.params.strip(): 67 | args.params = '{}' 68 | 69 | # params must be a dictionary format string 70 | match = re.match(r"^.*?(\{.*\}).*$", args.params) 71 | if not match: 72 | return error('Invalid params') 73 | 74 | try: 75 | args.params = dict(literal_eval(match.group(1))) 76 | except ValueError as err: 77 | return error('Invalid params') 78 | 79 | try: 80 | # Get ONVIF service 81 | service = self.client.get_service(args.service) 82 | # Actually execute the command and get the response 83 | response = getattr(service, args.operation)(args.params) 84 | except MethodNotFound as err: 85 | return error('No Operation: %s' % args.operation) 86 | except Exception as err: 87 | return error(err) 88 | 89 | if isinstance(response, (Text, bool)): 90 | return success(response) 91 | # Try to convert instance to dictionary 92 | try: 93 | success(ONVIFService.to_dict(response)) 94 | except ONVIFError: 95 | error({}) 96 | 97 | def complete_cmd(self, text, line, begidx, endidx): 98 | # TODO: complete service operations 99 | # service.ws_client.service._ServiceSelector__services[0].ports[0].methods.keys() 100 | if not text: 101 | completions = SUPPORTED_SERVICES[:] 102 | else: 103 | completions = [ key for key in SUPPORTED_SERVICES 104 | if key.startswith(text) ] 105 | return completions 106 | 107 | def emptyline(self): 108 | return '' 109 | 110 | def do_EOF(self, line): 111 | return True 112 | 113 | def create_parser(): 114 | parser = ThrowingArgumentParser(description=__doc__) 115 | # Dealwith dependency for service, operation and params 116 | parser.add_argument('service', nargs='?', 117 | help='Service defined by ONVIF WSDL document') 118 | parser.add_argument('operation', nargs='?', default='', 119 | help='Operation to be execute defined' 120 | ' by ONVIF WSDL document') 121 | parser.add_argument('params', default='', nargs='?', 122 | help='JSON format params passed to the operation.' 123 | 'E.g., "{"Name": "NewHostName"}"') 124 | parser.add_argument('--host', required=True, 125 | help='ONVIF camera host, e.g. 192.168.2.123, ' 126 | 'www.example.com') 127 | parser.add_argument('--port', default=80, type=int, help='Port number for camera, default: 80') 128 | parser.add_argument('-u', '--user', required=True, 129 | help='Username for authentication') 130 | parser.add_argument('-a', '--password', required=True, 131 | help='Password for authentication') 132 | parser.add_argument('-w', '--wsdl', default=os.path.join(os.path.dirname(os.path.dirname(__file__)), "wsdl"), 133 | help='directory to store ONVIF WSDL documents') 134 | parser.add_argument('-e', '--encrypt', default='False', 135 | help='Encrypt password or not') 136 | parser.add_argument('-v', '--verbose', action='store_true', 137 | help='increase output verbosity') 138 | parser.add_argument('--cache-location', dest='cache_location', default='/tmp/onvif/', 139 | help='location to cache suds objects, default to /tmp/onvif/') 140 | parser.add_argument('--cache-duration', dest='cache_duration', 141 | help='how long will the cache be exist') 142 | 143 | return parser 144 | 145 | def main(): 146 | INTRO = __doc__ 147 | 148 | # Create argument parser 149 | parser = create_parser() 150 | try: 151 | args = parser.parse_args() 152 | except ValueError as err: 153 | print(str(err)) 154 | return 155 | # Also need parse configuration file. 156 | 157 | # Interactive command loop 158 | cli = ONVIFCLI(stdin=input) 159 | cli.setup(args) 160 | if args.service: 161 | cmd = ' '.join(['cmd', args.service, args.operation, args.params]) 162 | cli.onecmd(cmd) 163 | # Execute command specified and exit 164 | else: 165 | cli.cmdloop() 166 | 167 | if __name__ == '__main__': 168 | main() 169 | -------------------------------------------------------------------------------- /wsdl/addressing: -------------------------------------------------------------------------------- 1 | 2 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | If "Policy" elements from namespace "http://schemas.xmlsoap.org/ws/2002/12/policy#policy" are used, they must appear first (before any extensibility elements). 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /examples/AbsoluteMove.py: -------------------------------------------------------------------------------- 1 | import asyncio, sys, os 2 | from onvif import ONVIFCamera 3 | import time 4 | 5 | IP="192.168.1.64" # Camera IP address 6 | PORT=80 # Port 7 | USER="admin" # Username 8 | PASS="intflow3121" # Password 9 | 10 | 11 | XMAX = 1 12 | XMIN = -1 13 | XNOW = 0.5 14 | YMAX = 1 15 | YMIN = -1 16 | YNOW = 0.5 17 | Move = 0.1 18 | Velocity = 1 19 | Zoom = 0 20 | positionrequest = None 21 | ptz = None 22 | active = False 23 | ptz_configuration_options = None 24 | media_profile = None 25 | 26 | def do_move(ptz, request): 27 | 28 | global active 29 | if active: 30 | ptz.Stop({'ProfileToken': request.ProfileToken}) 31 | active = True 32 | ptz.AbsoluteMove(request) 33 | 34 | def move_up(ptz, request): 35 | 36 | if YNOW - Move <= -1: 37 | request.Position.PanTilt.y = YNOW 38 | else: 39 | request.Position.PanTilt.y = YNOW - Move 40 | 41 | 42 | do_move(ptz, request) 43 | 44 | def move_down(ptz, request): 45 | 46 | if YNOW + Move >= 1: 47 | request.Position.PanTilt.y = YNOW 48 | else: 49 | request.Position.PanTilt.y = YNOW + Move 50 | 51 | 52 | do_move(ptz, request) 53 | 54 | def move_right(ptz, request): 55 | 56 | if XNOW - Move >= -0.99: 57 | request.Position.PanTilt.x = XNOW - Move 58 | elif abs(XNOW + Move) >= 0.0: 59 | request.Position.PanTilt.x = abs(XNOW) - Move 60 | elif abs(XNOW) <= 0.01: 61 | request.Position.PanTilt.x = XNOW 62 | 63 | request.Position.PanTilt.y = YNOW 64 | do_move(ptz, request) 65 | 66 | def move_left(ptz, request): 67 | 68 | if XNOW + Move <= 1.0: 69 | request.Position.PanTilt.x = XNOW + Move 70 | elif XNOW <= 1.0 and XNOW > 0.99: 71 | request.Position.PanTilt.x = -XNOW 72 | elif XNOW < 0: 73 | request.Position.PanTilt.x = XNOW + Move 74 | elif XNOW <= -0.105556 and XNOW > -0.11: 75 | request.Position.PanTilt.x = XNOW 76 | 77 | request.Position.PanTilt.y = YNOW 78 | do_move(ptz, request) 79 | 80 | 81 | def move_upleft(ptz, request): 82 | 83 | if YNOW == -1: 84 | request.Position.PanTilt.y = YNOW 85 | else: 86 | request.Position.PanTilt.y = YNOW - Move 87 | if XNOW + Move <= 1.0: 88 | request.Position.PanTilt.x = XNOW + Move 89 | elif XNOW <= 1.0 and XNOW > 0.99: 90 | request.Position.PanTilt.x = -XNOW 91 | elif XNOW < 0: 92 | request.Position.PanTilt.x = XNOW + Move 93 | elif XNOW <= -0.105556 and XNOW > -0.11: 94 | request.Position.PanTilt.x = XNOW 95 | do_move(ptz, request) 96 | 97 | def move_upright(ptz, request): 98 | 99 | if YNOW == -1: 100 | request.Position.PanTilt.y = YNOW 101 | else: 102 | request.Position.PanTilt.y = YNOW - Move 103 | 104 | if XNOW - Move >= -0.99: 105 | request.Position.PanTilt.x = XNOW - Move 106 | elif abs(XNOW + Move) >= 0.0: 107 | request.Position.PanTilt.x = abs(XNOW) - Move 108 | elif abs(XNOW) <= 0.01: 109 | request.Position.PanTilt.x = XNOW 110 | 111 | do_move(ptz, request) 112 | 113 | def move_downleft(ptz, request): 114 | 115 | if YNOW - Move == 1: 116 | request.Position.PanTilt.y = YNOW 117 | else: 118 | request.Position.PanTilt.y = YNOW - Move 119 | 120 | if XNOW + Move <= 1.0: 121 | request.Position.PanTilt.x = XNOW + Move 122 | elif XNOW <= 1.0 and XNOW > 0.99: 123 | request.Position.PanTilt.x = -XNOW 124 | elif XNOW < 0: 125 | request.Position.PanTilt.x = XNOW + Move 126 | elif XNOW <= -0.105556 and XNOW > -0.11: 127 | request.Position.PanTilt.x = XNOW 128 | 129 | do_move(ptz, request) 130 | 131 | def move_downright(ptz, request): 132 | 133 | if YNOW == -1: 134 | request.Position.PanTilt.y = YNOW 135 | else: 136 | request.Position.PanTilt.y = YNOW - Move 137 | 138 | if XNOW - Move >= -0.99: 139 | request.Position.PanTilt.x = XNOW - Move 140 | elif abs(XNOW + Move) >= 0.0: 141 | request.Position.PanTilt.x = abs(XNOW) - Move 142 | elif abs(XNOW) <= 0.01: 143 | request.Position.PanTilt.x = XNOW 144 | 145 | do_move(ptz, request) 146 | 147 | def Zoom_in(ptz,request): 148 | 149 | if Zoom + Move >= 1.0: 150 | request.Position.Zoom = 1.0 151 | else: 152 | request.Position.Zoom = Zoom + Move 153 | do_move(ptz, request) 154 | 155 | def Zoom_out(ptz,request): 156 | 157 | if Zoom - Move <= 0.0: 158 | request.Position.Zoom = 0.0 159 | else: 160 | request.Position.Zoom = Zoom - Move 161 | do_move(ptz,request) 162 | 163 | def setup_move(): 164 | mycam = ONVIFCamera(IP, PORT, USER, PASS) 165 | # Create media service object 166 | media = mycam.create_media_service() 167 | 168 | # Create ptz service object 169 | global ptz , ptz_configuration_options, media_profile 170 | ptz = mycam.create_ptz_service() 171 | 172 | # Get target profile 173 | media_profile = media.GetProfiles()[0] 174 | 175 | 176 | request = ptz.create_type('GetConfigurationOptions') 177 | request.ConfigurationToken = media_profile.PTZConfiguration.token 178 | ptz_configuration_options = ptz.GetConfigurationOptions(request) 179 | 180 | request_configuration = ptz.create_type('GetConfiguration') 181 | request_configuration.PTZConfigurationToken = media_profile.PTZConfiguration.token 182 | ptz_configuration = ptz.GetConfiguration(request_configuration) 183 | 184 | request_setconfiguration = ptz.create_type('SetConfiguration') 185 | request_setconfiguration.PTZConfiguration = ptz_configuration 186 | 187 | global positionrequest 188 | 189 | positionrequest = ptz.create_type('AbsoluteMove') 190 | positionrequest.ProfileToken = media_profile.token 191 | 192 | if positionrequest.Position is None : 193 | positionrequest.Position = ptz.GetStatus({'ProfileToken': media_profile.token}).Position 194 | positionrequest.Position.PanTilt.space = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].URI 195 | positionrequest.Position.Zoom.space = ptz_configuration_options.Spaces.AbsoluteZoomPositionSpace[0].URI 196 | if positionrequest.Speed is None : 197 | positionrequest.Speed = ptz.GetStatus({'ProfileToken': media_profile.token}).Position 198 | positionrequest.Speed.PanTilt.space = ptz_configuration_options.Spaces.PanTiltSpeedSpace[0].URI 199 | 200 | def Get_Status(): 201 | # Get range of pan and tilt 202 | global XMAX, XMIN, YMAX, YMIN, XNOW, YNOW, Velocity, Zoom 203 | XMAX = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].XRange.Max 204 | XMIN = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].XRange.Min 205 | YMAX = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].YRange.Max 206 | YMIN = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].YRange.Min 207 | XNOW = ptz.GetStatus({'ProfileToken': media_profile.token}).Position.PanTilt.x 208 | YNOW = ptz.GetStatus({'ProfileToken': media_profile.token}).Position.PanTilt.y 209 | Velocity = ptz_configuration_options.Spaces.PanTiltSpeedSpace[0].XRange.Max 210 | Zoom = ptz.GetStatus({'ProfileToken': media_profile.token}).Position.Zoom.x 211 | 212 | 213 | def readin(): 214 | """Reading from stdin and displaying menu""" 215 | global positionrequest, ptz 216 | 217 | selection = sys.stdin.readline().strip("\n") 218 | lov=[ x for x in selection.split(" ") if x != ""] 219 | if lov: 220 | 221 | if lov[0].lower() in ["u","up"]: 222 | move_up(ptz,positionrequest) 223 | elif lov[0].lower() in ["d","do","dow","down"]: 224 | move_down(ptz,positionrequest) 225 | elif lov[0].lower() in ["l","le","lef","left"]: 226 | move_left(ptz,positionrequest) 227 | elif lov[0].lower() in ["l","le","lef","left"]: 228 | move_left(ptz,positionrequest) 229 | elif lov[0].lower() in ["r","ri","rig","righ","right"]: 230 | move_right(ptz,positionrequest) 231 | elif lov[0].lower() in ["ul"]: 232 | move_upleft(ptz,positionrequest) 233 | elif lov[0].lower() in ["ur"]: 234 | move_upright(ptz,positionrequest) 235 | elif lov[0].lower() in ["dl"]: 236 | move_downleft(ptz,positionrequest) 237 | elif lov[0].lower() in ["dr"]: 238 | move_downright(ptz,positionrequest) 239 | elif lov[0].lower() in ["s","st","sto","stop"]: 240 | ptz.Stop({'ProfileToken': positionrequest.ProfileToken}) 241 | active = False 242 | else: 243 | print("What are you asking?\tI only know, 'up','down','left','right', 'ul' (up left), \n\t\t\t'ur' (up right), 'dl' (down left), 'dr' (down right) and 'stop'") 244 | 245 | print("") 246 | print("Your command: ", end='',flush=True) 247 | 248 | # Test Define 249 | # def move(ptz, request): 250 | 251 | # request.Position.PanTilt.y = -1 252 | # request.Position.PanTilt.x = 0 253 | 254 | # do_move(ptz,request) 255 | 256 | if __name__ == '__main__': 257 | 258 | setup_move() 259 | # Get_Status() 260 | # Zoom_out(ptz,positionrequest) 261 | # Get_Status() 262 | # move(ptz,positionrequest) 263 | while True: 264 | if active == True: 265 | time.sleep(1) 266 | active = False 267 | else: 268 | Get_Status() 269 | 270 | move_up(ptz, positionrequest) -------------------------------------------------------------------------------- /wsdl/t-1.xsd: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | 71 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 97 | 99 | 100 | 101 | 102 | 103 | 104 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | TopicPathExpression ::= TopicPath ( '|' TopicPath )* 143 | TopicPath ::= RootTopic ChildTopicExpression* 144 | RootTopic ::= NamespacePrefix? ('//')? (NCName | '*') 145 | NamespacePrefix ::= NCName ':' 146 | ChildTopicExpression ::= '/' '/'? (QName | NCName | '*'| '.') 147 | 148 | 149 | 150 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | The pattern allows strings matching the following EBNF: 161 | ConcreteTopicPath ::= RootTopic ChildTopic* 162 | RootTopic ::= QName 163 | ChildTopic ::= '/' (QName | NCName) 164 | 165 | 166 | 167 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | The pattern allows strings matching the following EBNF: 178 | RootTopic ::= QName 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /wsdl/xml.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 |
11 |

About the XML namespace

12 | 13 |
14 |

15 | This schema document describes the XML namespace, in a form 16 | suitable for import by other schema documents. 17 |

18 |

19 | See 20 | http://www.w3.org/XML/1998/namespace.html and 21 | 22 | http://www.w3.org/TR/REC-xml for information 23 | about this namespace. 24 |

25 |

26 | Note that local names in this namespace are intended to be 27 | defined only by the World Wide Web Consortium or its subgroups. 28 | The names currently defined in this namespace are listed below. 29 | They should not be used with conflicting semantics by any Working 30 | Group, specification, or document instance. 31 |

32 |

33 | See further below in this document for more information about how to refer to this schema document from your own 35 | XSD schema documents and about the 36 | namespace-versioning policy governing this schema document. 37 |

38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 |
47 | 48 |

lang (as an attribute name)

49 |

50 | denotes an attribute whose value 51 | is a language code for the natural language of the content of 52 | any element; its value is inherited. This name is reserved 53 | by virtue of its definition in the XML specification.

54 | 55 |
56 |
57 |

Notes

58 |

59 | Attempting to install the relevant ISO 2- and 3-letter 60 | codes as the enumerated possible values is probably never 61 | going to be a realistic possibility. 62 |

63 |

64 | See BCP 47 at 65 | http://www.rfc-editor.org/rfc/bcp/bcp47.txt 66 | and the IANA language subtag registry at 67 | 68 | http://www.iana.org/assignments/language-subtag-registry 69 | for further information. 70 |

71 |

72 | The union allows for the 'un-declaration' of xml:lang with 73 | the empty string. 74 |

75 |
76 |
77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
88 | 89 | 90 | 91 | 92 |
93 | 94 |

space (as an attribute name)

95 |

96 | denotes an attribute whose 97 | value is a keyword indicating what whitespace processing 98 | discipline is intended for the content of the element; its 99 | value is inherited. This name is reserved by virtue of its 100 | definition in the XML specification.

101 | 102 |
103 |
104 |
105 | 106 | 107 | 108 | 109 | 110 | 111 |
112 | 113 | 114 | 115 |
116 | 117 |

base (as an attribute name)

118 |

119 | denotes an attribute whose value 120 | provides a URI to be used as the base for interpreting any 121 | relative URIs in the scope of the element on which it 122 | appears; its value is inherited. This name is reserved 123 | by virtue of its definition in the XML Base specification.

124 | 125 |

126 | See http://www.w3.org/TR/xmlbase/ 128 | for information about this attribute. 129 |

130 |
131 |
132 |
133 |
134 | 135 | 136 | 137 | 138 |
139 | 140 |

id (as an attribute name)

141 |

142 | denotes an attribute whose value 143 | should be interpreted as if declared to be of type ID. 144 | This name is reserved by virtue of its definition in the 145 | xml:id specification.

146 | 147 |

148 | See http://www.w3.org/TR/xml-id/ 150 | for information about this attribute. 151 |

152 |
153 |
154 |
155 |
156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 |
167 | 168 |

Father (in any context at all)

169 | 170 |
171 |

172 | denotes Jon Bosak, the chair of 173 | the original XML Working Group. This name is reserved by 174 | the following decision of the W3C XML Plenary and 175 | XML Coordination groups: 176 |

177 |
178 |

179 | In appreciation for his vision, leadership and 180 | dedication the W3C XML Plenary on this 10th day of 181 | February, 2000, reserves for Jon Bosak in perpetuity 182 | the XML name "xml:Father". 183 |

184 |
185 |
186 |
187 |
188 |
189 | 190 | 191 | 192 |
193 |

About this schema document

194 | 195 |
196 |

197 | This schema defines attributes and an attribute group suitable 198 | for use by schemas wishing to allow xml:base, 199 | xml:lang, xml:space or 200 | xml:id attributes on elements they define. 201 |

202 |

203 | To enable this, such a schema must import this schema for 204 | the XML namespace, e.g. as follows: 205 |

206 |
207 |           <schema . . .>
208 |            . . .
209 |            <import namespace="http://www.w3.org/XML/1998/namespace"
210 |                       schemaLocation="http://www.w3.org/2001/xml.xsd"/>
211 |      
212 |

213 | or 214 |

215 |
216 |            <import namespace="http://www.w3.org/XML/1998/namespace"
217 |                       schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
218 |      
219 |

220 | Subsequently, qualified reference to any of the attributes or the 221 | group defined below will have the desired effect, e.g. 222 |

223 |
224 |           <type . . .>
225 |            . . .
226 |            <attributeGroup ref="xml:specialAttrs"/>
227 |      
228 |

229 | will define a type which will schema-validate an instance element 230 | with any of those attributes. 231 |

232 |
233 |
234 |
235 |
236 | 237 | 238 | 239 |
240 |

Versioning policy for this schema document

241 |
242 |

243 | In keeping with the XML Schema WG's standard versioning 244 | policy, this schema document will persist at 245 | 246 | http://www.w3.org/2009/01/xml.xsd. 247 |

248 |

249 | At the date of issue it can also be found at 250 | 251 | http://www.w3.org/2001/xml.xsd. 252 |

253 |

254 | The schema document at that URI may however change in the future, 255 | in order to remain compatible with the latest version of XML 256 | Schema itself, or with the XML namespace itself. In other words, 257 | if the XML Schema or XML namespaces change, the version of this 258 | document at 259 | http://www.w3.org/2001/xml.xsd 260 | 261 | will change accordingly; the version at 262 | 263 | http://www.w3.org/2009/01/xml.xsd 264 | 265 | will not change. 266 |

267 |

268 | Previous dated (and unchanging) versions of this schema 269 | document are at: 270 |

271 | 281 |
282 |
283 |
284 |
285 | 286 |
287 | 288 | -------------------------------------------------------------------------------- /wsdl/replay.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | The capabilities for the replay service is returned in the Capabilities element. 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Indicator that the Device supports reverse playback as defined in the ONVIF Streaming Specification. 41 | 42 | 43 | 44 | 45 | The list contains two elements defining the minimum and maximum valid values supported as session timeout in seconds. 46 | 47 | 48 | 49 | 50 | Indicates support for RTP/RTSP/TCP. 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Specifies the connection parameters to be used for the stream. The URI that is returned may depend on these parameters. 63 | 64 | 65 | 66 | 67 | The identifier of the recording to be streamed. 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | The URI to which the client should connect in order to stream the recording. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | Description of the new replay configuration parameters. 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | The current replay configuration parameters. 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | Returns the capabilities of the replay service. The result is returned in a typed answer. 147 | 148 | 149 | 150 | 151 | 152 | Requests a URI that can be used to initiate playback of a recorded stream 153 | using RTSP as the control protocol. The URI is valid only as it is 154 | specified in the response. 155 | This operation is mandatory. 156 | 157 | 158 | 159 | 160 | 161 | 162 | Returns the current configuration of the replay service. 163 | This operation is mandatory. 164 | 165 | 166 | 167 | 168 | 169 | 170 | Changes the current configuration of the replay service. 171 | This operation is mandatory. 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /wsdl/ws-discovery.xsd: -------------------------------------------------------------------------------- 1 | 2 | 53 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 129 | 130 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 170 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 263 | 264 | 267 | 268 | 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /onvif/client.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | __version__ = '0.0.1' 3 | import datetime as dt 4 | import logging 5 | import os.path 6 | from threading import Thread, RLock 7 | from urllib import parse 8 | 9 | from zeep.client import Client, CachingClient, Settings 10 | from zeep.wsse.username import UsernameToken 11 | import zeep.helpers 12 | 13 | from onvif.exceptions import ONVIFError 14 | from onvif.definition import SERVICES 15 | 16 | logger = logging.getLogger('onvif') 17 | logging.basicConfig(level=logging.INFO) 18 | logging.getLogger('zeep.client').setLevel(logging.CRITICAL) 19 | 20 | 21 | # Ensure methods to raise an ONVIFError Exception 22 | # when some thing was wrong 23 | def safe_func(func): 24 | def wrapped(*args, **kwargs): 25 | try: 26 | return func(*args, **kwargs) 27 | except Exception as err: 28 | raise ONVIFError(err) 29 | return wrapped 30 | 31 | 32 | class UsernameDigestTokenDtDiff(UsernameToken): 33 | """ 34 | UsernameDigestToken class, with a time offset parameter that can be adjusted; 35 | This allows authentication on cameras without being time synchronized. 36 | Please note that using NTP on both end is the recommended solution, 37 | this should only be used in "safe" environments. 38 | """ 39 | def __init__(self, user, passw, dt_diff=None, **kwargs): 40 | super().__init__(user, passw, **kwargs) 41 | self.dt_diff = dt_diff # Date/time difference in datetime.timedelta 42 | 43 | def apply(self, envelope, headers): 44 | old_created = self.created 45 | if self.created is None: 46 | self.created = dt.datetime.now(dt.UTC) 47 | if self.dt_diff is not None: 48 | self.created += self.dt_diff 49 | result = super().apply(envelope, headers) 50 | self.created = old_created 51 | return result 52 | 53 | 54 | class ONVIFService(object): 55 | """ 56 | Python Implemention for ONVIF Service. 57 | Services List: 58 | DeviceMgmt DeviceIO Event AnalyticsDevice Display Imaging Media 59 | PTZ Receiver RemoteDiscovery Recording Replay Search Extension 60 | 61 | >>> from onvif import ONVIFService 62 | >>> device_service = ONVIFService('http://192.168.0.112/onvif/device_service', 63 | ... 'admin', 'foscam', 64 | ... '/etc/onvif/wsdl/devicemgmt.wsdl') 65 | >>> ret = device_service.GetHostname() 66 | >>> print ret.FromDHCP 67 | >>> print ret.Name 68 | >>> device_service.SetHostname(dict(Name='newhostname')) 69 | >>> ret = device_service.GetSystemDateAndTime() 70 | >>> print ret.DaylightSavings 71 | >>> print ret.TimeZone 72 | >>> dict_ret = device_service.to_dict(ret) 73 | >>> print dict_ret['TimeZone'] 74 | 75 | There are two ways to pass parameter to services methods 76 | 1. Dict 77 | params = {'Name': 'NewHostName'} 78 | device_service.SetHostname(params) 79 | 2. Type Instance 80 | params = device_service.create_type('SetHostname') 81 | params.Hostname = 'NewHostName' 82 | device_service.SetHostname(params) 83 | """ 84 | 85 | @safe_func 86 | def __init__(self, xaddr, user, passwd, url, 87 | encrypt=True, daemon=False, zeep_client=None, no_cache=False, 88 | dt_diff=None, binding_name='', transport=None): 89 | if not os.path.isfile(url): 90 | raise ONVIFError('%s doesn`t exist!' % url) 91 | 92 | self.url = url 93 | self.xaddr = xaddr 94 | wsse = UsernameDigestTokenDtDiff(user, passwd, dt_diff=dt_diff, use_digest=encrypt) 95 | # Create soap client 96 | if not zeep_client: 97 | ClientType = Client if no_cache else CachingClient 98 | settings = Settings() 99 | settings.strict = False 100 | settings.xml_huge_tree = True 101 | self.zeep_client = ClientType(wsdl=url, wsse=wsse, transport=transport, settings=settings) 102 | self.zeep_client.set_ns_prefix('tds', 'http://www.onvif.org/ver10/device/wsdl') 103 | self.zeep_client.set_ns_prefix('tev', 'http://www.onvif.org/ver10/events/wsdl') 104 | self.zeep_client.set_ns_prefix('timg', 'http://www.onvif.org/ver20/imaging/wsdl') 105 | self.zeep_client.set_ns_prefix('tmd', 'http://www.onvif.org/ver10/deviceIO/wsdl') 106 | self.zeep_client.set_ns_prefix('tptz', 'http://www.onvif.org/ver20/ptz/wsdl') 107 | self.zeep_client.set_ns_prefix('ttr', 'http://www.onvif.org/ver10/media/wsdl') 108 | self.zeep_client.set_ns_prefix('ter', 'http://www.onvif.org/ver10/error') 109 | else: 110 | self.zeep_client = zeep_client 111 | self.ws_client = self.zeep_client.create_service(binding_name, self.xaddr) 112 | 113 | # Set soap header for authentication 114 | self.user = user 115 | self.passwd = passwd 116 | # Indicate wether password digest is needed 117 | self.encrypt = encrypt 118 | self.daemon = daemon 119 | self.dt_diff = dt_diff 120 | self.create_type = lambda x: self.zeep_client.get_element('ns0:' + x)() 121 | 122 | @classmethod 123 | @safe_func 124 | def clone(cls, service, *args, **kwargs): 125 | clone_service = service.ws_client.clone() 126 | kwargs['ws_client'] = clone_service 127 | return ONVIFService(*args, **kwargs) 128 | 129 | @staticmethod 130 | @safe_func 131 | def to_dict(zeepobject): 132 | # Convert a WSDL Type instance into a dictionary 133 | return {} if zeepobject is None else zeep.helpers.serialize_object(zeepobject) 134 | 135 | def service_wrapper(self, func): 136 | @safe_func 137 | def wrapped(params=None, callback=None): 138 | def call(params=None, callback=None): 139 | # No params 140 | # print(params.__class__.__mro__) 141 | if params is None: 142 | params = {} 143 | else: 144 | params = ONVIFService.to_dict(params) 145 | try: 146 | ret = func(**params) 147 | except TypeError: 148 | ret = func(params) 149 | if callable(callback): 150 | callback(ret) 151 | return ret 152 | 153 | if self.daemon: 154 | th = Thread(target=call, args=(params, callback)) 155 | th.daemon = True 156 | th.start() 157 | else: 158 | return call(params, callback) 159 | return wrapped 160 | 161 | def __getattr__(self, name): 162 | """ 163 | Call the real onvif Service operations, 164 | See the official wsdl definition for the 165 | APIs detail(API name, request parameters, 166 | response parameters, parameter types, etc...) 167 | """ 168 | builtin = name.startswith('__') and name.endswith('__') 169 | if builtin: 170 | return self.__dict__[name] 171 | else: 172 | return self.service_wrapper(getattr(self.ws_client, name)) 173 | 174 | 175 | class ONVIFCamera(object): 176 | """ 177 | Python Implementation of an ONVIF compliant device. 178 | This class integrates ONVIF services 179 | 180 | adjust_time parameter allows authentication on cameras without being time synchronized. 181 | Please note that using NTP on both end is the recommended solution, 182 | this should only be used in "safe" environments. 183 | Also, this cannot be used on AXIS camera, as every request is authenticated, contrary to ONVIF standard 184 | 185 | >>> from onvif import ONVIFCamera 186 | >>> mycam = ONVIFCamera('192.168.0.112', 80, 'admin', '12345') 187 | >>> mycam.devicemgmt.GetServices(False) 188 | >>> media_service = mycam.create_media_service() 189 | >>> ptz_service = mycam.create_ptz_service() 190 | # Get PTZ Configuration: 191 | >>> ptz_service.GetConfiguration() 192 | """ 193 | 194 | # Class-level variables 195 | services_template = {'devicemgmt': None, 'ptz': None, 'media': None, 196 | 'imaging': None, 'events': None, 'analytics': None} 197 | use_services_template = {'devicemgmt': True, 'ptz': True, 'media': True, 198 | 'imaging': True, 'events': True, 'analytics': True} 199 | 200 | def __init__(self, host, port, user, passwd, 201 | wsdl_dir=os.path.join(os.path.dirname(os.path.dirname(__file__)), 202 | "wsdl"), 203 | encrypt=True, daemon=False, no_cache=False, adjust_time=False, event_pullpoint=True, 204 | transport=None, override_camera_address=False): 205 | os.environ.pop('http_proxy', None) 206 | os.environ.pop('https_proxy', None) 207 | self.host = host 208 | self.port = int(port) 209 | self.user = user 210 | self.passwd = passwd 211 | self.wsdl_dir = wsdl_dir 212 | self.encrypt = encrypt 213 | self.daemon = daemon 214 | self.no_cache = no_cache 215 | self.adjust_time = adjust_time 216 | self.event_pullpoint = event_pullpoint 217 | self.transport = transport 218 | self._override_camera_address = override_camera_address 219 | 220 | # Active service client container 221 | self.services = {} 222 | self.services_lock = RLock() 223 | 224 | # Set xaddrs 225 | self.update_xaddrs() 226 | 227 | self.to_dict = ONVIFService.to_dict 228 | 229 | def update_xaddrs(self): 230 | # Establish devicemgmt service first 231 | self.dt_diff = None 232 | self.devicemgmt = self.create_devicemgmt_service() 233 | if self.adjust_time: 234 | cdate = self.devicemgmt.GetSystemDateAndTime().UTCDateTime 235 | cam_date = dt.datetime(cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, 236 | cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second) 237 | self.dt_diff = cam_date - dt.datetime.utcnow() 238 | self.devicemgmt.dt_diff = self.dt_diff 239 | self.devicemgmt = self.create_devicemgmt_service() 240 | # Get XAddr of services on the device 241 | self.xaddrs = {} 242 | capabilities = self.devicemgmt.GetCapabilities({'Category': 'All'}) 243 | for name in capabilities: 244 | capability = capabilities[name] 245 | try: 246 | if name.lower() in SERVICES and capability is not None: 247 | ns = SERVICES[name.lower()]['ns'] 248 | self.xaddrs[ns] = capability['XAddr'] 249 | except Exception: 250 | logger.exception('Unexpected service type') 251 | 252 | with self.services_lock: 253 | try: 254 | self.event = self.create_events_service() 255 | if self.event_pullpoint: 256 | self.xaddrs['http://www.onvif.org/ver10/events/wsdl/PullPointSubscription'] = \ 257 | self.event.CreatePullPointSubscription().SubscriptionReference.Address._value_1 258 | except Exception: 259 | pass 260 | 261 | if self._override_camera_address: 262 | self.xaddrs = {service: self._replace_netloc_in_url(url, self.host, self.port) 263 | for service, url in self.xaddrs.items()} 264 | 265 | def update_url(self, host=None, port=None): 266 | changed = False 267 | if host and self.host != host: 268 | changed = True 269 | self.host = host 270 | if port and self.port != port: 271 | changed = True 272 | self.port = port 273 | 274 | if not changed: 275 | return 276 | 277 | self.devicemgmt = self.create_devicemgmt_service() 278 | self.capabilities = self.devicemgmt.GetCapabilities() 279 | 280 | with self.services_lock: 281 | for sname in self.services.keys(): 282 | xaddr = getattr(self.capabilities, sname.capitalize).XAddr 283 | self.services[sname].ws_client.set_options(location=xaddr) 284 | 285 | def get_service(self, name, create=True): 286 | service = getattr(self, name.lower(), None) 287 | if not service and create: 288 | return getattr(self, 'create_%s_service' % name.lower())() 289 | return service 290 | 291 | def get_definition(self, name, portType=None): 292 | """Returns xaddr and wsdl of specified service""" 293 | # Check if the service is supported 294 | if name not in SERVICES: 295 | raise ONVIFError('Unknown service %s' % name) 296 | wsdl_file = SERVICES[name]['wsdl'] 297 | ns = SERVICES[name]['ns'] 298 | 299 | binding_name = '{%s}%s' % (ns, SERVICES[name]['binding']) 300 | 301 | if portType: 302 | ns += '/' + portType 303 | 304 | wsdlpath = os.path.join(self.wsdl_dir, wsdl_file) 305 | if not os.path.isfile(wsdlpath): 306 | raise ONVIFError('No such file: %s' % wsdlpath) 307 | 308 | # XAddr for devicemgmt is fixed: 309 | if name == 'devicemgmt': 310 | xaddr = '%s:%s/onvif/device_service' % \ 311 | (self.host if (self.host.startswith('http://') or self.host.startswith('https://')) 312 | else 'http://%s' % self.host, self.port) 313 | return xaddr, wsdlpath, binding_name 314 | 315 | # Get other XAddr 316 | xaddr = self.xaddrs.get(ns) 317 | if not xaddr: 318 | raise ONVIFError("Device doesn't support service: %s" % name) 319 | 320 | return xaddr, wsdlpath, binding_name 321 | 322 | def create_onvif_service(self, name, portType=None, transport=None): 323 | """ 324 | Create ONVIF service client. 325 | 326 | :param name: service name, should be present as a key within 327 | the `SERVICES` dictionary declared within the `onvif.definition` module 328 | :param portType: 329 | :param transport: 330 | :return: 331 | """ 332 | """Create ONVIF service client""" 333 | 334 | name = name.lower() 335 | xaddr, wsdl_file, binding_name = self.get_definition(name, portType) 336 | 337 | with self.services_lock: 338 | if not transport: 339 | transport = self.transport 340 | 341 | service = ONVIFService(xaddr, self.user, self.passwd, 342 | wsdl_file, self.encrypt, 343 | self.daemon, no_cache=self.no_cache, 344 | dt_diff=self.dt_diff, 345 | binding_name=binding_name, 346 | transport=transport) 347 | 348 | self.services[name] = service 349 | 350 | setattr(self, name, service) 351 | if not self.services_template.get(name): 352 | self.services_template[name] = service 353 | 354 | return service 355 | 356 | def create_devicemgmt_service(self, transport=None): 357 | # The entry point for devicemgmt service is fixed. 358 | return self.create_onvif_service('devicemgmt', transport=transport) 359 | 360 | def create_media_service(self, transport=None): 361 | return self.create_onvif_service('media', transport=transport) 362 | 363 | def create_ptz_service(self, transport=None): 364 | return self.create_onvif_service('ptz', transport=transport) 365 | 366 | def create_imaging_service(self, transport=None): 367 | return self.create_onvif_service('imaging', transport=transport) 368 | 369 | def create_deviceio_service(self, transport=None): 370 | return self.create_onvif_service('deviceio', transport=transport) 371 | 372 | def create_events_service(self, transport=None): 373 | return self.create_onvif_service('events', transport=transport) 374 | 375 | def create_analytics_service(self, transport=None): 376 | return self.create_onvif_service('analytics', transport=transport) 377 | 378 | def create_recording_service(self, transport=None): 379 | return self.create_onvif_service('recording', transport=transport) 380 | 381 | def create_search_service(self, transport=None): 382 | return self.create_onvif_service('search', transport=transport) 383 | 384 | def create_replay_service(self, transport=None): 385 | return self.create_onvif_service('replay', transport=transport) 386 | 387 | def create_pullpoint_service(self, transport=None): 388 | return self.create_onvif_service('pullpoint', 389 | portType='PullPointSubscription', 390 | transport=transport) 391 | 392 | def create_receiver_service(self, transport=None): 393 | return self.create_onvif_service('receiver', transport=transport) 394 | 395 | def create_notification_service(self, transport=None): 396 | return self.create_onvif_service('notification', transport=transport) 397 | 398 | def create_subscription_service(self, transport=None): 399 | return self.create_onvif_service('subscription', transport=transport) 400 | 401 | @classmethod 402 | def _replace_netloc_in_url(self, url: str, host, port): 403 | parsed_url = parse.urlparse(url) 404 | return parse.urlunparse((parsed_url.scheme, 405 | f"{host}:{port}", 406 | parsed_url.path, 407 | parsed_url.params, 408 | parsed_url.query, 409 | parsed_url.fragment)) 410 | -------------------------------------------------------------------------------- /wsdl/receiver.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | The capabilities for the receiver service is returned in the Capabilities element. 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Indicates that the device can receive RTP multicast streams. 41 | 42 | 43 | 44 | 45 | Indicates that the device can receive RTP/TCP streams 46 | 47 | 48 | 49 | 50 | Indicates that the device can receive RTP/RTSP/TCP streams. 51 | 52 | 53 | 54 | 55 | The maximum number of receivers supported by the device. 56 | 57 | 58 | 59 | 60 | The maximum allowed length for RTSP URIs (Minimum and default value is 128 octet). 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | A list of all receivers that currently exist on the device. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | The token of the receiver to be retrieved. 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | The details of the receiver. 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | The initial configuration for the new receiver. 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | The details of the receiver that was created. 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | The token of the receiver to be deleted. 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | The token of the receiver to be configured. 151 | 152 | 153 | 154 | 155 | The new configuration for the receiver. 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | The token of the receiver to be changed. 173 | 174 | 175 | 176 | 177 | The new receiver mode. Options available are: 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | The token of the receiver to be queried. 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | Description of the current receiver state. 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | Returns the capabilities of the receiver service. The result is returned in a typed answer. 264 | 265 | 266 | 267 | 268 | 269 | Lists all receivers currently present on a device. This operation is mandatory. 270 | 271 | 272 | 273 | 274 | 275 | 276 | Retrieves the details of a specific receiver. This operation is mandatory. 277 | 278 | 279 | 280 | 281 | 282 | 283 | Creates a new receiver. This operation is mandatory, although the service may 284 | raise a fault if the receiver cannot be created. 285 | 286 | 287 | 288 | 289 | 290 | 291 | Deletes an existing receiver. A receiver may be deleted only if it is not 292 | currently in use; otherwise a fault shall be raised. 293 | This operation is mandatory. 294 | 295 | 296 | 297 | 298 | 299 | 300 | Configures an existing receiver. This operation is mandatory. 301 | 302 | 303 | 304 | 305 | 306 | 307 | Sets the mode of the receiver without affecting the rest of its configuration. 308 | This operation is mandatory. 309 | 310 | 311 | 312 | 313 | 314 | 315 | Determines whether the receiver is currently disconnected, connected or 316 | attempting to connect. 317 | This operation is mandatory. 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | -------------------------------------------------------------------------------- /wsdl/imaging.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | The capabilities for the imaging service is returned in the Capabilities element. 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Indicates whether or not Image Stabilization feature is supported. 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Reference token to the VideoSource for which the ImagingSettings. 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ImagingSettings for the VideoSource that was requested. 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Reference token to the VideoSource for which the imaging parameter options are requested. 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | Valid ranges for the imaging parameters that are categorized as device specific. 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | Reference to the VideoSource for the requested move (focus) operation. 121 | 122 | 123 | 124 | 125 | 126 | 127 | Content of the requested move (focus) operation. 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | Reference token to the VideoSource for the requested move options. 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | Valid ranges for the focus lens move options. 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | Reference token to the VideoSource where the focus movement should be stopped. 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | Reference token to the VideoSource where the imaging status should be requested. 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | Requested imaging status. 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | Returns the capabilities of the imaging service. The result is returned in a typed answer. 267 | 268 | 269 | 270 | 271 | Get the ImagingConfiguration for the requested VideoSource. 272 | 273 | 274 | 275 | 276 | Set the ImagingConfiguration for the requested VideoSource. 277 | 278 | 279 | 280 | 281 | This operation gets the valid ranges for the imaging parameters that have device specific ranges. 282 | This command is mandatory for all device implementing the imaging service. The command returns all supported parameters and their ranges 283 | such that these can be applied to the SetImagingSettings command.
284 | For read-only parameters which cannot be modified via the SetImagingSettings command only a single option or identical Min and Max values 285 | is provided.
286 | 287 | 288 |
289 | 290 | The Move command moves the focus lens in an absolute, a relative or in a continuous manner from its current position. 291 | The speed argument is optional for absolute and relative control, but required for continuous. If no speed argument is used, the default speed is used. 292 | Focus adjustments through this operation will turn off the autofocus. A device with support for remote focus control should support absolute, 293 | relative or continuous control through the Move operation. The supported MoveOpions are signalled via the GetMoveOptions command. 294 | At least one focus control capability is required for this operation to be functional.
295 | The move operation contains the following commands:
296 | Absolute – Requires position parameter and optionally takes a speed argument. A unitless type is used by default for focus positioning and speed. Optionally, if supported, the position may be requested in m-1 units.
297 | Relative – Requires distance parameter and optionally takes a speed argument. Negative distance means negative direction. 298 | Continuous – Requires a speed argument. Negative speed argument means negative direction. 299 |
300 | 301 | 302 |
303 | 304 | Imaging move operation options supported for the Video source. 305 | 306 | 307 | 308 | 309 | The Stop command stops all ongoing focus movements of the lense. A device with support for remote focus control as signalled via 310 | the GetMoveOptions supports this command.
The operation will not affect ongoing autofocus operation.
311 | 312 | 313 |
314 | 315 | Via this command the current status of the Move operation can be requested. Supported for this command is available if the support for the Move operation is signalled via GetMoveOptions. 316 | 317 | 318 | 319 |
320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 |
401 | -------------------------------------------------------------------------------- /wsdl/bw-2.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 17 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 70 | 71 | 72 | 73 | 75 | 76 | 77 | 78 | 80 | 81 | 82 | 83 | 85 | 86 | 87 | 88 | 90 | 91 | 92 | 93 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 103 | 105 | 106 | 107 | 108 | 110 | 111 | 112 | 116 | 117 | 119 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 129 | 130 | 131 | 132 | 134 | 135 | 136 | 137 | 139 | 140 | 141 | 142 | 144 | 145 | 146 | 150 | 151 | 153 | 154 | 155 | 156 | 158 | 159 | 160 | 161 | 163 | 164 | 165 | 166 | 170 | 171 | 173 | 174 | 175 | 176 | 178 | 179 | 180 | 181 | 183 | 184 | 185 | 189 | 190 | 192 | 193 | 194 | 195 | 197 | 198 | 199 | 200 | 202 | 203 | 204 | 208 | 209 | 211 | 212 | 213 | 214 | 216 | 217 | 218 | 219 | 221 | 222 | 223 | 227 | 228 | 230 | 231 | 232 | 233 | 235 | 236 | 237 | 238 | 240 | 241 | 242 | 246 | 247 | 249 | 250 | 251 | 252 | 254 | 255 | 256 | 257 | 259 | 260 | 261 | 265 | 266 | 268 | 269 | 270 | 271 | 273 | 274 | 275 | 276 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 295 | 297 | 299 | 301 | 303 | 305 | 307 | 309 | 311 | 313 | 315 | 317 | 318 | 319 | 320 | 321 | 322 | 324 | 326 | 328 | 330 | 332 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 342 | 344 | 346 | 348 | 349 | 350 | 351 | 353 | 355 | 357 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 371 | 373 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 383 | 385 | 387 | 390 | 391 | 392 | 394 | 396 | 398 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 410 | 412 | 414 | 417 | 418 | 419 | 421 | 423 | 425 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 436 | 438 | 439 | 440 | 441 | 442 | 444 | 446 | 447 | 448 | 449 | --------------------------------------------------------------------------------