From 1b6f20b02c571f4fdb88a4e25dd4a3e86c8f6249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Wed, 21 Jun 2017 16:23:51 +0200 Subject: [PATCH 01/39] Replace SOAP client suds by zeep for Python3 compatibility - first working version --- onvif/client.py | 214 ++++++++++++++++++++++++-------------------- onvif/definition.py | 34 +++---- 2 files changed, 135 insertions(+), 113 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index d6cf53a..4094fd8 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -1,26 +1,27 @@ +from __future__ import print_function, division __version__ = '0.0.1' - import os.path from types import InstanceType -import urlparse -import urllib +#import urlparse +#import urllib from threading import Thread, RLock import logging logger = logging.getLogger('onvif') logging.basicConfig(level=logging.INFO) -logging.getLogger('suds.client').setLevel(logging.CRITICAL) +logging.getLogger('zeep.client').setLevel(logging.CRITICAL) -from suds.client import Client -from suds.wsse import Security, UsernameToken -from suds.cache import ObjectCache, NoCache -from suds_passworddigest.token import UsernameDigestToken -from suds.bindings import binding -binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope') +from zeep.client import Client +from zeep.wsse.username import UsernameToken +#from suds.wsse import Security#, UsernameToken +#from suds.cache import ObjectCache, NoCache +#from suds_passworddigest.token import UsernameDigestToken +#from suds.bindings import binding +#binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope') from onvif.exceptions import ONVIFError -from definition import SERVICES, NSMAP -from suds.sax.date import UTC +from definition import SERVICES +#from suds.sax.date import UTC import datetime as dt # Ensure methods to raise an ONVIFError Exception # when some thing was wrong @@ -29,28 +30,29 @@ def wrapped(*args, **kwargs): try: return func(*args, **kwargs) except Exception as err: + print('Ouuups: err = ', err) raise ONVIFError(err) return wrapped -class UsernameDigestTokenDtDiff(UsernameDigestToken): - ''' - UsernameDigestToken class, with a time offset parameter that can be adjusted; - This allows authentication on cameras without being time synchronized. - Please note that using NTP on both end is the recommended solution, - this should only be used in "safe" environements. - ''' - def __init__(self, user, passw, dt_diff=None) : -# Old Style class ... sigh ... - UsernameDigestToken.__init__(self, user, passw) - self.dt_diff = dt_diff - - def setcreated(self, *args, **kwargs): - dt_adjusted = None - if self.dt_diff : - dt_adjusted = (self.dt_diff + dt.datetime.utcnow()) - UsernameToken.setcreated(self, dt=dt_adjusted, *args, **kwargs) - self.created = str(UTC(self.created)) +#class UsernameDigestTokenDtDiff(UsernameDigestToken): +# ''' +# UsernameDigestToken class, with a time offset parameter that can be adjusted; +# This allows authentication on cameras without being time synchronized. +# Please note that using NTP on both end is the recommended solution, +# this should only be used in "safe" environements. +# ''' +# def __init__(self, user, passw, dt_diff=None) : +## Old Style class ... sigh ... +# UsernameDigestToken.__init__(self, user, passw) +# self.dt_diff = dt_diff + +# def setcreated(self, *args, **kwargs): +# dt_adjusted = None +# if self.dt_diff : +# dt_adjusted = (self.dt_diff + dt.datetime.utcnow()) +# UsernameToken.setcreated(self, dt=dt_adjusted, *args, **kwargs) +# self.created = str(UTC(self.created)) class ONVIFService(object): @@ -87,36 +89,41 @@ class ONVIFService(object): @safe_func def __init__(self, xaddr, user, passwd, url, cache_location='/tmp/suds', cache_duration=None, - encrypt=True, daemon=False, ws_client=None, no_cache=False, portType=None, dt_diff = None): + encrypt=True, daemon=False, ws_client=None, no_cache=False, + portType=None, dt_diff=None, binding_name=''): + print('ONVIFService() ENTER', locals()) + os.environ.pop('http_proxy', None) + os.environ.pop('https_proxy', None) if not os.path.isfile(url): raise ONVIFError('%s doesn`t exist!' % url) - if no_cache: - cache = NoCache() - else: - # Create cache object - # NOTE: if cache_location is specified, - # onvif must has the permission to access it. - cache = ObjectCache(location=cache_location) - # cache_duration: cache will expire in `cache_duration` days - if cache_duration is not None: - cache.setduration(days=cache_duration) +# if no_cache: +# cache = NoCache() +# else: +# # Create cache object +# # NOTE: if cache_location is specified, +# # onvif must has the permission to access it. +# cache = ObjectCache(location=cache_location) +# # cache_duration: cache will expire in `cache_duration` days +# if cache_duration is not None: +# cache.setduration(days=cache_duration) # Convert pathname to url - self.url = urlparse.urljoin('file:', urllib.pathname2url(url)) + self.url = url #urlparse.urljoin('file:', urllib.pathname2url(url)) self.xaddr = xaddr # Create soap client if not ws_client: - self.ws_client = Client(url=self.url, - location=self.xaddr, - cache=cache, - port=portType, - headers={'Content-Type': 'application/soap+xml'}) + print(self.url, self.xaddr) + self.zeep_client = Client(wsdl=url, wsse=UsernameToken(user, passwd, use_digest=True)) +# cache=cache, +# port=portType, +# headers={'Content-Type': 'application/soap+xml'}) + self.ws_client = self.zeep_client.create_service(binding_name, self.xaddr) + #print(self.ws_client.GetSystemDateAndTime()) else: - self.ws_client = ws_client - self.ws_client.set_options(location=self.xaddr) + self.ws_client = ws_client.create_service(binding_name, self.xaddr) # Set soap header for authentication self.user = user @@ -127,30 +134,35 @@ def __init__(self, xaddr, user, passwd, url, self.daemon = daemon self.dt_diff = dt_diff - self.set_wsse() +# self.set_wsse() # Method to create type instance of service method defined in WSDL - self.create_type = self.ws_client.factory.create - - @safe_func - def set_wsse(self, user=None, passwd=None): - ''' Basic ws-security auth ''' - if user: - self.user = user - if passwd: - self.passwd = passwd - - security = Security() - - if self.encrypt: - token = UsernameDigestTokenDtDiff(self.user, self.passwd, dt_diff=self.dt_diff) - else: - token = UsernameToken(self.user, self.passwd) - token.setnonce() - token.setcreated() - - security.tokens.append(token) - self.ws_client.set_options(wsse=security) +# print(">>>>>", self.zeep_client.__dict__) +# print(">>>>>", self.ws_client.__dict__) + #print(">>>>>", self.zeep_client.type_factory('ns1').__dict__) + #self.create_type = self.ws_client.factory.create + self.create_type = lambda x: self.zeep_client.get_element('ns0:' + x) + print('ONVIFService() EXIT') + +# @safe_func +# def set_wsse(self, user=None, passwd=None): +# ''' Basic ws-security auth ''' +# if user: +# self.user = user +# if passwd: +# self.passwd = passwd + +# security = Security() + +# if self.encrypt: +# token = UsernameDigestTokenDtDiff(self.user, self.passwd, dt_diff=self.dt_diff) +# else: +# token = UsernameToken(self.user, self.passwd) +# token.setnonce() +# token.setcreated() +# +# security.tokens.append(token) +# self.ws_client.set_options(wsse=security) @classmethod @safe_func @@ -206,17 +218,17 @@ def __getattr__(self, name): if builtin: return self.__dict__[name] else: - return self.service_wrapper(getattr(self.ws_client.service, name)) + return self.service_wrapper(getattr(self.ws_client, name)) class ONVIFCamera(object): ''' Python Implemention ONVIF compliant device This class integrates onvif services - + adjust_time parameter allows authentication on cameras without being time synchronized. - Please note that using NTP on both end is the recommended solution, + Please note that using NTP on both end is the recommended solution, this should only be used in "safe" environements. - Also, this cannot be used on AXIS camera, as every request is authenticated, contrary to ONVIF standard + Also, this cannot be used on AXIS camera, as every request is authenticated, contrary to ONVIF standard >>> from onvif import ONVIFCamera >>> mycam = ONVIFCamera('192.168.0.112', 80, 'admin', '12345') @@ -237,6 +249,7 @@ class ONVIFCamera(object): def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirname(os.path.dirname(__file__)), "wsdl"), cache_location=None, cache_duration=None, encrypt=True, daemon=False, no_cache=False, adjust_time=False): + print('ONVIFCamera() ENTER', locals()) self.host = host self.port = int(port) self.user = user @@ -257,6 +270,7 @@ def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirna self.update_xaddrs() self.to_dict = ONVIFService.to_dict + print('ONVIFCamera() EXIT') def update_xaddrs(self): # Establish devicemgmt service first @@ -267,11 +281,12 @@ def update_xaddrs(self): cam_date = dt.datetime(cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second) self.dt_diff = cam_date - dt.datetime.utcnow() self.devicemgmt.dt_diff = self.dt_diff - self.devicemgmt.set_wsse() + #self.devicemgmt.set_wsse() # Get XAddr of services on the device self.xaddrs = { } capabilities = self.devicemgmt.GetCapabilities({'Category': 'All'}) - for name, capability in capabilities: + for name in capabilities: + capability = capabilities[name] try: if name.lower() in SERVICES: ns = SERVICES[name.lower()]['ns'] @@ -284,7 +299,7 @@ def update_xaddrs(self): self.event = self.create_events_service() self.xaddrs['http://www.onvif.org/ver10/events/wsdl/PullPointSubscription'] = self.event.CreatePullPointSubscription().SubscriptionReference.Address except: - pass + pass def update_url(self, host=None, port=None): @@ -307,21 +322,21 @@ def update_url(self, host=None, port=None): xaddr = getattr(self.capabilities, sname.capitalize).XAddr self.services[sname].ws_client.set_options(location=xaddr) - def update_auth(self, user=None, passwd=None): - changed = False - if user and user != self.user: - changed = True - self.user = user - if passwd and passwd != self.passwd: - changed = True - self.passwd = passwd +# def update_auth(self, user=None, passwd=None): +# changed = False +# if user and user != self.user: +# changed = True +# self.user = user +# if passwd and passwd != self.passwd: +# changed = True +# self.passwd = passwd - if not changed: - return +# if not changed: +# return - with self.services_lock: - for service in self.services.keys(): - self.services[service].set_wsse(user, passwd) +# with self.services_lock: +# for service in self.services.keys(): +# self.services[service].set_wsse(user, passwd) def get_service(self, name, create=True): service = None @@ -337,6 +352,7 @@ def get_definition(self, name): raise ONVIFError('Unknown service %s' % name) wsdl_file = SERVICES[name]['wsdl'] ns = SERVICES[name]['ns'] + binding_name = '{%s}%s' % (ns, SERVICES[name]['binding']) wsdlpath = os.path.join(self.wsdl_dir, wsdl_file) if not os.path.isfile(wsdlpath): @@ -345,20 +361,20 @@ def get_definition(self, name): # XAddr for devicemgmt is fixed: if name == 'devicemgmt': xaddr = 'http://%s:%s/onvif/device_service' % (self.host, self.port) - return xaddr, wsdlpath + return xaddr, wsdlpath, binding_name # Get other XAddr xaddr = self.xaddrs.get(ns) if not xaddr: raise ONVIFError('Device doesn`t support service: %s' % name) - return xaddr, wsdlpath + return xaddr, wsdlpath, binding_name def create_onvif_service(self, name, from_template=True, portType=None): '''Create ONVIF service client''' name = name.lower() - xaddr, wsdl_file = self.get_definition(name) + xaddr, wsdl_file, binding_name = self.get_definition(name) with self.services_lock: svt = self.services_template.get(name) @@ -370,14 +386,20 @@ def create_onvif_service(self, name, from_template=True, portType=None): self.cache_duration, self.encrypt, self.daemon, - no_cache=self.no_cache, portType=portType, dt_diff=self.dt_diff) + no_cache=self.no_cache, + portType=portType, + dt_diff=self.dt_diff, + binding_name=binding_name) # No template, create new service from wsdl document. # A little time-comsuming else: service = ONVIFService(xaddr, self.user, self.passwd, wsdl_file, self.cache_location, self.cache_duration, self.encrypt, - self.daemon, no_cache=self.no_cache, portType=portType, dt_diff=self.dt_diff) + self.daemon, no_cache=self.no_cache, + portType=portType, + dt_diff=self.dt_diff, + binding_name=binding_name) self.services[name] = service diff --git a/onvif/definition.py b/onvif/definition.py index a7ae4d8..31ea6b9 100644 --- a/onvif/definition.py +++ b/onvif/definition.py @@ -1,20 +1,20 @@ SERVICES = { - # Name namespace wsdl file - 'devicemgmt': {'ns': 'http://www.onvif.org/ver10/device/wsdl', 'wsdl': 'devicemgmt.wsdl'}, - 'media' : {'ns': 'http://www.onvif.org/ver10/media/wsdl', 'wsdl': 'media.wsdl' }, - 'ptz' : {'ns': 'http://www.onvif.org/ver20/ptz/wsdl', 'wsdl': 'ptz.wsdl'}, - 'imaging' : {'ns': 'http://www.onvif.org/ver20/imaging/wsdl', 'wsdl': 'imaging.wsdl'}, - 'deviceio' : {'ns': 'http://www.onvif.org/ver10/deviceIO/wsdl', 'wsdl': 'deviceio.wsdl'}, - 'events' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl'}, - 'pullpoint' : {'ns': 'http://www.onvif.org/ver10/events/wsdl/PullPointSubscription', 'wsdl': 'events.wsdl'}, - 'analytics' : {'ns': 'http://www.onvif.org/ver20/analytics/wsdl', 'wsdl': 'analytics.wsdl'}, - 'recording' : {'ns': 'http://www.onvif.org/ver10/recording/wsdl', 'wsdl': 'recording.wsdl'}, - 'search' : {'ns': 'http://www.onvif.org/ver10/search/wsdl', 'wsdl': 'search.wsdl'}, - 'replay' : {'ns': 'http://www.onvif.org/ver10/replay/wsdl', 'wsdl': 'replay.wsdl'}, - 'receiver' : {'ns': 'http://www.onvif.org/ver10/receiver/wsdl', 'wsdl': 'receiver.wsdl'}, + # Name namespace wsdl file binding name + 'devicemgmt': {'ns': 'http://www.onvif.org/ver10/device/wsdl', 'wsdl': 'devicemgmt.wsdl', 'binding' : 'DeviceBinding'}, + 'media' : {'ns': 'http://www.onvif.org/ver10/media/wsdl', 'wsdl': 'media.wsdl', 'binding' : 'MediaBinding'}, + 'ptz' : {'ns': 'http://www.onvif.org/ver20/ptz/wsdl', 'wsdl': 'ptz.wsdl', 'binding' : 'PTZBinding'}, + 'imaging' : {'ns': 'http://www.onvif.org/ver20/imaging/wsdl', 'wsdl': 'imaging.wsdl', 'binding' : 'ImagingBinding'}, + 'deviceio' : {'ns': 'http://www.onvif.org/ver10/deviceIO/wsdl', 'wsdl': 'deviceio.wsdl', 'binding' : 'DeviceIOBinding'}, + 'events' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'EventBinding'}, + 'pullpoint' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'PullPointSubscriptionBinding'}, + 'analytics' : {'ns': 'http://www.onvif.org/ver20/analytics/wsdl', 'wsdl': 'analytics.wsdl', 'binding' : 'AnalyticsEngineBinding'}, + 'recording' : {'ns': 'http://www.onvif.org/ver10/recording/wsdl', 'wsdl': 'recording.wsdl', 'binding' : 'RecordingBinding'}, + 'search' : {'ns': 'http://www.onvif.org/ver10/search/wsdl', 'wsdl': 'search.wsdl', 'binding' : 'SearchBinding'}, + 'replay' : {'ns': 'http://www.onvif.org/ver10/replay/wsdl', 'wsdl': 'replay.wsdl', 'binding' : 'ReplayBinding'}, + 'receiver' : {'ns': 'http://www.onvif.org/ver10/receiver/wsdl', 'wsdl': 'receiver.wsdl', 'binding' : 'ReceiverBinding'}, } - -NSMAP = { } -for name, item in SERVICES.items(): - NSMAP[item['ns']] = name +# +#NSMAP = { } +#for name, item in SERVICES.items(): +# NSMAP[item['ns']] = name From 2fa9b6932df84579e4a35643e3ba870d86a3024f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Wed, 21 Jun 2017 19:59:48 +0200 Subject: [PATCH 02/39] First version compatible both with Python 2 and 3 --- onvif/__init__.py | 4 ++-- onvif/cli.py | 10 +++++----- onvif/client.py | 9 ++++++--- onvif/exceptions.py | 30 +++++++++++++++--------------- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/onvif/__init__.py b/onvif/__init__.py index aef09ea..de1775e 100644 --- a/onvif/__init__.py +++ b/onvif/__init__.py @@ -1,10 +1,10 @@ from onvif.client import ONVIFService, ONVIFCamera, SERVICES from onvif.exceptions import ONVIFError, ERR_ONVIF_UNKNOWN, \ ERR_ONVIF_PROTOCOL, ERR_ONVIF_WSDL, ERR_ONVIF_BUILD -from onvif import cli +#from onvif import cli __all__ = ( 'ONVIFService', 'ONVIFCamera', 'ONVIFError', 'ERR_ONVIF_UNKNOWN', 'ERR_ONVIF_PROTOCOL', 'ERR_ONVIF_WSDL', 'ERR_ONVIF_BUILD', - 'SERVICES', 'cli' + 'SERVICES'#, 'cli' ) diff --git a/onvif/cli.py b/onvif/cli.py index 549fdd1..0b5c265 100755 --- a/onvif/cli.py +++ b/onvif/cli.py @@ -1,6 +1,6 @@ #!/usr/bin/python '''ONVIF Client Command Line Interface''' - +from __future__ import print_function, division import re from cmd import Cmd from ast import literal_eval @@ -10,7 +10,7 @@ from suds import MethodNotFound from suds.sax.text import Text from onvif import ONVIFCamera, ONVIFService, ONVIFError -from definition import SERVICES +from .definition import SERVICES import os.path SUPPORTED_SERVICES = SERVICES.keys() @@ -21,10 +21,10 @@ def error(self, message): raise ValueError("%s\n%s" % (message, usage)) def success(message): - print 'True: ' + str(message) + print('True: ' + str(message)) def error(message): - print 'False: ' + str(message) + print('False: ' + str(message)) class ONVIFCLI(Cmd): prompt = 'ONVIF >>> ' @@ -151,7 +151,7 @@ def main(): try: args = parser.parse_args() except ValueError as err: - print str(err) + print(str(err)) return # Also need parse configuration file. diff --git a/onvif/client.py b/onvif/client.py index 4094fd8..24f1c82 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -1,7 +1,10 @@ from __future__ import print_function, division __version__ = '0.0.1' import os.path -from types import InstanceType +import sys +if sys.version_info.major <= 2: # InstanceType doesn't exist for Python3 + from types import InstanceType + #import urlparse #import urllib from threading import Thread, RLock @@ -20,7 +23,7 @@ #binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope') from onvif.exceptions import ONVIFError -from definition import SERVICES +from onvif.definition import SERVICES #from suds.sax.date import UTC import datetime as dt # Ensure methods to raise an ONVIFError Exception @@ -191,7 +194,7 @@ def call(params=None, callback=None): # No params if params is None: params = {} - elif isinstance(params, InstanceType): + elif sys.version_info.major <= 2 and isinstance(params, InstanceType): params = ONVIFService.to_dict(params) ret = func(**params) if callable(callback): diff --git a/onvif/exceptions.py b/onvif/exceptions.py index 977f692..e02b8da 100644 --- a/onvif/exceptions.py +++ b/onvif/exceptions.py @@ -1,8 +1,8 @@ ''' Core exceptions raised by the ONVIF Client ''' -from suds import WebFault, MethodNotFound, PortNotFound, \ - ServiceNotFound, TypeNotFound, BuildError, \ - SoapHeadersNotPermitted +#from suds import WebFault, MethodNotFound, PortNotFound, \ +# ServiceNotFound, TypeNotFound, BuildError, \ +# SoapHeadersNotPermitted # Error codes setting # Error unknown, e.g, HTTP errors @@ -18,18 +18,18 @@ class ONVIFError(Exception): def __init__(self, err): - if isinstance(err, (WebFault, SoapHeadersNotPermitted)): - self.reason = err.fault.Reason.Text - self.fault = err.fault - self.code = ERR_ONVIF_PROTOCOL - elif isinstance(err, (ServiceNotFound, PortNotFound, - MethodNotFound, TypeNotFound)): - self.reason = str(err) - self.code = ERR_ONVIF_PROTOCOL - elif isinstance(err, BuildError): - self.reason = str(err) - self.code = ERR_ONVIF_BUILD - else: +# if isinstance(err, (WebFault, SoapHeadersNotPermitted)): +# self.reason = err.fault.Reason.Text +# self.fault = err.fault +# self.code = ERR_ONVIF_PROTOCOL +# elif isinstance(err, (ServiceNotFound, PortNotFound, +# MethodNotFound, TypeNotFound)): +# self.reason = str(err) +# self.code = ERR_ONVIF_PROTOCOL +# elif isinstance(err, BuildError): +# self.reason = str(err) +# self.code = ERR_ONVIF_BUILD +# else: self.reason = 'Unknown error: ' + str(err) self.code = ERR_ONVIF_UNKNOWN From ac157d560f55eadcc9d151398e247bcb63e5b538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Thu, 22 Jun 2017 19:05:49 +0200 Subject: [PATCH 03/39] Update setup files --- README.rst | 4 +--- onvif/version.txt | 2 +- setup.py | 9 ++++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 2d057bf..19e418d 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,7 @@ ONVIF Client Implementation in Python Dependencies ------------ -`suds `_ >= 0.4 - -`suds-passworddigest `_ +`suds `_ >= 2.2.0 Install python-onvif -------------------- diff --git a/onvif/version.txt b/onvif/version.txt index b1e80bb..3b04cfb 100644 --- a/onvif/version.txt +++ b/onvif/version.txt @@ -1 +1 @@ -0.1.3 +0.2 diff --git a/setup.py b/setup.py index a2b1598..c8c7807 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ version_path = os.path.join(here, 'onvif/version.txt') version = open(version_path).read().strip() -requires = [ 'suds >= 0.4', 'suds-passworddigest' ] +requires = ['zeep >= 2.2.0'] CLASSIFIERS = [ 'Development Status :: 3 - Alpha', @@ -22,11 +22,12 @@ 'Topic :: Utilities', "Programming Language :: Python", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", ] -wsdl_files = [ 'wsdl/' + item for item in os.listdir('wsdl') ] +wsdl_files = ['wsdl/' + item for item in os.listdir('wsdl')] setup( name='onvif', @@ -49,5 +50,3 @@ 'console_scripts': ['onvif-cli = onvif.cli:main'] } ) - - From 611d1eb8fedb02b91e2ebd18650a91a2a90c66c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Thu, 22 Jun 2017 19:06:58 +0200 Subject: [PATCH 04/39] debugging --- onvif/client.py | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index 24f1c82..20dfa11 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -2,11 +2,9 @@ __version__ = '0.0.1' import os.path import sys -if sys.version_info.major <= 2: # InstanceType doesn't exist for Python3 +if sys.version_info.major <= 2: # InstanceType doesn't exist in Python3 from types import InstanceType -#import urlparse -#import urllib from threading import Thread, RLock import logging @@ -16,11 +14,7 @@ from zeep.client import Client from zeep.wsse.username import UsernameToken -#from suds.wsse import Security#, UsernameToken -#from suds.cache import ObjectCache, NoCache -#from suds_passworddigest.token import UsernameDigestToken -#from suds.bindings import binding -#binding.envns = ('SOAP-ENV', 'http://www.w3.org/2003/05/soap-envelope') +import zeep.helpers from onvif.exceptions import ONVIFError from onvif.definition import SERVICES @@ -33,7 +27,11 @@ def wrapped(*args, **kwargs): try: return func(*args, **kwargs) except Exception as err: - print('Ouuups: err = ', err) + print('Ouuups: err =', err, ', func =', func, ', args =', args, ', type(args) =', + type(args), ', kwargs =', kwargs, ', type(kwargs) =', type(kwargs)) + for a in args: + if isinstance(a, zeep.xsd.elements.element.Element): + print('<<<<<', type(a.resolve_type()), '>>>>>', type(zeep.helpers.serialize_object(a.resolve_type(), dict))) raise ONVIFError(err) return wrapped @@ -176,16 +174,9 @@ def clone(cls, service, *args, **kwargs): @staticmethod @safe_func - def to_dict(sudsobject): + def to_dict(zeepobject): # Convert a WSDL Type instance into a dictionary - if sudsobject is None: - return { } - elif isinstance(sudsobject, list): - ret = [ ] - for item in sudsobject: - ret.append(Client.dict(item)) - return ret - return Client.dict(sudsobject) + return {} if zeepobject is None else zeep.helpers.serialize_object(zeepobject) def service_wrapper(self, func): @safe_func @@ -194,7 +185,7 @@ def call(params=None, callback=None): # No params if params is None: params = {} - elif sys.version_info.major <= 2 and isinstance(params, InstanceType): + elif sys.version_info.major > 2 or isinstance(params, InstanceType): params = ONVIFService.to_dict(params) ret = func(**params) if callable(callback): From e3550b04e3b4fe25c420716d271ae3eaadcedfbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Fri, 23 Jun 2017 20:18:02 +0200 Subject: [PATCH 05/39] Add caching; debugging (first version working both for getting and setting camera attributes) --- README.rst | 2 +- onvif/client.py | 79 ++++++++++++++++++------------------------------- 2 files changed, 30 insertions(+), 51 deletions(-) diff --git a/README.rst b/README.rst index 19e418d..6fee151 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ ONVIF Client Implementation in Python Dependencies ------------ -`suds `_ >= 2.2.0 +`zeep `_ >= 2.2.0 Install python-onvif -------------------- diff --git a/onvif/client.py b/onvif/client.py index 20dfa11..3b6e1c1 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -1,9 +1,9 @@ from __future__ import print_function, division __version__ = '0.0.1' import os.path -import sys -if sys.version_info.major <= 2: # InstanceType doesn't exist in Python3 - from types import InstanceType +#import sys +#if sys.version_info.major <= 2: # InstanceType doesn't exist in Python3 +# from types import InstanceType from threading import Thread, RLock @@ -12,7 +12,7 @@ logging.basicConfig(level=logging.INFO) logging.getLogger('zeep.client').setLevel(logging.CRITICAL) -from zeep.client import Client +from zeep.client import Client, CachingClient from zeep.wsse.username import UsernameToken import zeep.helpers @@ -27,11 +27,8 @@ def wrapped(*args, **kwargs): try: return func(*args, **kwargs) except Exception as err: - print('Ouuups: err =', err, ', func =', func, ', args =', args, ', type(args) =', - type(args), ', kwargs =', kwargs, ', type(kwargs) =', type(kwargs)) - for a in args: - if isinstance(a, zeep.xsd.elements.element.Element): - print('<<<<<', type(a.resolve_type()), '>>>>>', type(zeep.helpers.serialize_object(a.resolve_type(), dict))) +# print('Ouuups: err =', err, ', func =', func, ', args =', args, ', type(args) =', +# type(args), ', kwargs =', kwargs, ', type(kwargs) =', type(kwargs)) raise ONVIFError(err) return wrapped @@ -89,42 +86,24 @@ class ONVIFService(object): @safe_func def __init__(self, xaddr, user, passwd, url, - cache_location='/tmp/suds', cache_duration=None, - encrypt=True, daemon=False, ws_client=None, no_cache=False, + encrypt=True, daemon=False, zeep_client=None, no_cache=False, portType=None, dt_diff=None, binding_name=''): - print('ONVIFService() ENTER', locals()) - os.environ.pop('http_proxy', None) - os.environ.pop('https_proxy', None) + #print('ONVIFService() ENTER', locals()) if not os.path.isfile(url): raise ONVIFError('%s doesn`t exist!' % url) -# if no_cache: -# cache = NoCache() -# else: -# # Create cache object -# # NOTE: if cache_location is specified, -# # onvif must has the permission to access it. -# cache = ObjectCache(location=cache_location) -# # cache_duration: cache will expire in `cache_duration` days -# if cache_duration is not None: -# cache.setduration(days=cache_duration) - - - # Convert pathname to url - self.url = url #urlparse.urljoin('file:', urllib.pathname2url(url)) + self.url = url self.xaddr = xaddr + wsse = UsernameToken(user, passwd, use_digest=encrypt) # Create soap client - if not ws_client: - print(self.url, self.xaddr) - self.zeep_client = Client(wsdl=url, wsse=UsernameToken(user, passwd, use_digest=True)) -# cache=cache, -# port=portType, -# headers={'Content-Type': 'application/soap+xml'}) - self.ws_client = self.zeep_client.create_service(binding_name, self.xaddr) - #print(self.ws_client.GetSystemDateAndTime()) + if not zeep_client: + #print(self.url, self.xaddr) + ClientType = Client if no_cache else CachingClient + self.zeep_client = ClientType(wsdl=url, wsse=wsse, strict=False, xml_huge_tree=True) else: - self.ws_client = ws_client.create_service(binding_name, self.xaddr) + self.zeep_client = zeep_client + self.ws_client = self.zeep_client.create_service(binding_name, self.xaddr) # Set soap header for authentication self.user = user @@ -142,8 +121,8 @@ def __init__(self, xaddr, user, passwd, url, # print(">>>>>", self.ws_client.__dict__) #print(">>>>>", self.zeep_client.type_factory('ns1').__dict__) #self.create_type = self.ws_client.factory.create - self.create_type = lambda x: self.zeep_client.get_element('ns0:' + x) - print('ONVIFService() EXIT') + self.create_type = lambda x: self.zeep_client.get_element('ns0:' + x)() + #print('ONVIFService() EXIT') # @safe_func # def set_wsse(self, user=None, passwd=None): @@ -185,9 +164,13 @@ def call(params=None, callback=None): # No params if params is None: params = {} - elif sys.version_info.major > 2 or isinstance(params, InstanceType): + else: # elif sys.version_info.major > 2 or isinstance(params, InstanceType): params = ONVIFService.to_dict(params) - ret = func(**params) + try: + ret = func(**params) + except TypeError: + #print('### func =', func, '### params =', params, '### type(params) =', type(params)) + ret = func(params) if callable(callback): callback(ret) return ret @@ -241,16 +224,15 @@ class ONVIFCamera(object): use_services_template = {'devicemgmt': True, 'ptz': True, 'media': True, 'imaging': True, 'events': True, 'analytics': True } def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirname(os.path.dirname(__file__)), "wsdl"), - cache_location=None, cache_duration=None, encrypt=True, daemon=False, no_cache=False, adjust_time=False): - print('ONVIFCamera() ENTER', locals()) + #print('ONVIFCamera() ENTER', locals()) + os.environ.pop('http_proxy', None) + os.environ.pop('https_proxy', None) self.host = host self.port = int(port) self.user = user self.passwd = passwd self.wsdl_dir = wsdl_dir - self.cache_location = cache_location - self.cache_duration = cache_duration self.encrypt = encrypt self.daemon = daemon self.no_cache = no_cache @@ -264,7 +246,7 @@ def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirna self.update_xaddrs() self.to_dict = ONVIFService.to_dict - print('ONVIFCamera() EXIT') + #print('ONVIFCamera() EXIT') def update_xaddrs(self): # Establish devicemgmt service first @@ -376,8 +358,6 @@ def create_onvif_service(self, name, from_template=True, portType=None): if svt and from_template and self.use_services_template.get(name): service = ONVIFService.clone(svt, xaddr, self.user, self.passwd, wsdl_file, - self.cache_location, - self.cache_duration, self.encrypt, self.daemon, no_cache=self.no_cache, @@ -388,8 +368,7 @@ def create_onvif_service(self, name, from_template=True, portType=None): # A little time-comsuming else: service = ONVIFService(xaddr, self.user, self.passwd, - wsdl_file, self.cache_location, - self.cache_duration, self.encrypt, + wsdl_file, self.encrypt, self.daemon, no_cache=self.no_cache, portType=portType, dt_diff=self.dt_diff, From fece8422afa1d9f8a5fa703c9cc1fb320f2be22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Mon, 26 Jun 2017 19:35:28 +0200 Subject: [PATCH 06/39] Port cli.py to zeep --- onvif/cli.py | 9 ++++----- onvif/client.py | 40 ++++++++++++++++++++-------------------- onvif/exceptions.py | 5 ++++- 3 files changed, 28 insertions(+), 26 deletions(-) mode change 100644 => 100755 onvif/client.py diff --git a/onvif/cli.py b/onvif/cli.py index 0b5c265..046aa56 100755 --- a/onvif/cli.py +++ b/onvif/cli.py @@ -4,13 +4,12 @@ import re from cmd import Cmd from ast import literal_eval -from json import dumps -from argparse import ArgumentParser, ArgumentError, REMAINDER +from argparse import ArgumentParser, REMAINDER -from suds import MethodNotFound -from suds.sax.text import Text +from zeep.exceptions import LookupError as MethodNotFound +from zeep.xsd import String as Text from onvif import ONVIFCamera, ONVIFService, ONVIFError -from .definition import SERVICES +from onvif.definition import SERVICES import os.path SUPPORTED_SERVICES = SERVICES.keys() diff --git a/onvif/client.py b/onvif/client.py old mode 100644 new mode 100755 index 3b6e1c1..3b45055 --- a/onvif/client.py +++ b/onvif/client.py @@ -353,26 +353,26 @@ def create_onvif_service(self, name, from_template=True, portType=None): xaddr, wsdl_file, binding_name = self.get_definition(name) with self.services_lock: - svt = self.services_template.get(name) - # Has a template, clone from it. Faster. - if svt and from_template and self.use_services_template.get(name): - service = ONVIFService.clone(svt, xaddr, self.user, - self.passwd, wsdl_file, - self.encrypt, - self.daemon, - no_cache=self.no_cache, - portType=portType, - dt_diff=self.dt_diff, - binding_name=binding_name) - # No template, create new service from wsdl document. - # A little time-comsuming - else: - service = ONVIFService(xaddr, self.user, self.passwd, - wsdl_file, self.encrypt, - self.daemon, no_cache=self.no_cache, - portType=portType, - dt_diff=self.dt_diff, - binding_name=binding_name) +# svt = self.services_template.get(name) +# # Has a template, clone from it. Faster. +# if svt and from_template and self.use_services_template.get(name): +# service = ONVIFService.clone(svt, xaddr, self.user, +# self.passwd, wsdl_file, +# self.encrypt, +# self.daemon, +# no_cache=self.no_cache, +# portType=portType, +# dt_diff=self.dt_diff, +# binding_name=binding_name) +# # No template, create new service from wsdl document. +# # A little time-comsuming +# else: + service = ONVIFService(xaddr, self.user, self.passwd, + wsdl_file, self.encrypt, + self.daemon, no_cache=self.no_cache, + portType=portType, + dt_diff=self.dt_diff, + binding_name=binding_name) self.services[name] = service diff --git a/onvif/exceptions.py b/onvif/exceptions.py index e02b8da..ce4a700 100644 --- a/onvif/exceptions.py +++ b/onvif/exceptions.py @@ -3,7 +3,10 @@ #from suds import WebFault, MethodNotFound, PortNotFound, \ # ServiceNotFound, TypeNotFound, BuildError, \ # SoapHeadersNotPermitted - +#TODO: Translate these errors into ONVIFError instances, mimicking the original 'suds' behaviour +#from zeep.exceptions import XMLSyntaxError, XMLParseError, UnexpectedElementError, \ +# WsdlSyntaxError, TransportError, LookupError, NamespaceError, Fault, ValidationError, \ +# SignatureVerificationFailed, IncompleteMessage, IncompleteOperation # Error codes setting # Error unknown, e.g, HTTP errors ERR_ONVIF_UNKNOWN = 1 From 4a298bd088a267d0ebc0e2259f94dc1f8acd7a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Tue, 27 Jun 2017 10:55:37 +0200 Subject: [PATCH 07/39] Update tests --- onvif/cli.py | 0 onvif/client.py | 0 onvif/version.txt | 3 ++- tests/test.py | 20 +++++++++----------- 4 files changed, 11 insertions(+), 12 deletions(-) mode change 100755 => 100644 onvif/cli.py mode change 100755 => 100644 onvif/client.py diff --git a/onvif/cli.py b/onvif/cli.py old mode 100755 new mode 100644 diff --git a/onvif/client.py b/onvif/client.py old mode 100755 new mode 100644 diff --git a/onvif/version.txt b/onvif/version.txt index 3b04cfb..5faa42c 100644 --- a/onvif/version.txt +++ b/onvif/version.txt @@ -1 +1,2 @@ -0.2 +0.2.0 + diff --git a/tests/test.py b/tests/test.py index 7d46f60..095320c 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,22 +1,20 @@ #!/usr/bin/python #-*-coding=utf-8 - +from __future__ import print_function, division import unittest -from suds import WebFault - from onvif import ONVIFCamera, ONVIFError -CAM_HOST = '192.168.0.112' +CAM_HOST = '172.20.9.84' CAM_PORT = 80 -CAM_USER = 'admin' -CAM_PASS = '12345' +CAM_USER = 'root' +CAM_PASS = 'password' DEBUG = False def log(ret): if DEBUG: - print ret + print(ret) class TestDevice(unittest.TestCase): @@ -41,7 +39,7 @@ def test_GetServices(self): def test_GetServiceCapabilities(self): '''Returns the capabilities of the devce service.''' ret = self.cam.devicemgmt.GetServiceCapabilities() - ret.Network._IPFilter + ret.Network.IPFilter def test_GetCapabilities(self): ''' @@ -73,7 +71,7 @@ def test_SetHostname(self): self.cam.devicemgmt.SetHostname({'Name':'testHostName'}) self.assertEqual(self.cam.devicemgmt.GetHostname().Name, 'testHostName') - self.cam.devicemgmt.SetHostname(pre_host_name) + self.cam.devicemgmt.SetHostname({'Name':pre_host_name.Name}) def test_SetHostnameFromDHCP(self): ''' Controls whether the hostname shall be retrieved from DHCP ''' @@ -84,7 +82,7 @@ def test_GetDNS(self): ''' Gets the DNS setting from a device ''' ret = self.cam.devicemgmt.GetDNS() self.assertTrue(hasattr(ret, 'FromDHCP')) - if ret.FromDHCP == False: + if not ret.FromDHCP and len(ret.DNSManual) > 0: log(ret.DNSManual[0].Type) log(ret.DNSManual[0].IPv4Address) @@ -111,7 +109,7 @@ def test_GetDynamicDNS(self): def test_SetDynamicDNS(self): ''' Set the dynamic DNS settings on a device ''' ret = self.cam.devicemgmt.GetDynamicDNS() - ret = self.cam.devicemgmt.SetDynamicDNS(dict(Type=ret.Type, Name="random")) + ret = self.cam.devicemgmt.SetDynamicDNS({'Type': 'NoUpdate', 'Name':None, 'TTL':None}) if __name__ == '__main__': unittest.main() From c97cbe7bd3b79fd678c340483ca312f579d777f3 Mon Sep 17 00:00:00 2001 From: Hovis Date: Sun, 6 Aug 2017 11:44:56 -0700 Subject: [PATCH 08/39] Update install instructions in README, fixes #2 --- README.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 602d519..281e6b9 100644 --- a/README.rst +++ b/README.rst @@ -13,13 +13,7 @@ Install python-onvif You should clone this repository and run setup.py:: - cd python-onvif && python setup.py install - -**From PyPI** - -:: - - pip install onvif + cd python-onvif-zeep && python setup.py install Getting Started --------------- From be51e2a85e2e164ab89355dd73825a6b4f8e9e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Wed, 16 Aug 2017 18:11:01 +0200 Subject: [PATCH 09/39] Add explanation about 'pip' command for installation --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index 281e6b9..85bb816 100644 --- a/README.rst +++ b/README.rst @@ -15,6 +15,12 @@ You should clone this repository and run setup.py:: cd python-onvif-zeep && python setup.py install +Alternatively, you can run:: + + pip install --upgrade https://github.com/FalkTannhaeuser/python-onvif-zeep/archive/zeep.zip + (If you are behind a proxy, add the appropriate '--proxy=' option to this *pip* command line.) + + Getting Started --------------- From e9ec97fa3cc05ba562115619b0854e174230323c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Wed, 16 Aug 2017 18:14:26 +0200 Subject: [PATCH 10/39] Fix formatting --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 85bb816..1bee294 100644 --- a/README.rst +++ b/README.rst @@ -18,7 +18,8 @@ You should clone this repository and run setup.py:: Alternatively, you can run:: pip install --upgrade https://github.com/FalkTannhaeuser/python-onvif-zeep/archive/zeep.zip - (If you are behind a proxy, add the appropriate '--proxy=' option to this *pip* command line.) + +(If you are behind a proxy, add the appropriate '--proxy=' option to this *pip* command line.) Getting Started From 4691372c094c159e38cb025d41de37cd44caa270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Fri, 22 Sep 2017 12:13:15 +0200 Subject: [PATCH 11/39] Fix wsdl target directory for Windows Anaconda and Cygwin Python; upload to https://pypi.python.org/pypi --- README.rst | 4 +--- onvif/version.txt | 3 +-- setup.py | 10 +++++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index aabb1cf..4d4f68c 100644 --- a/README.rst +++ b/README.rst @@ -17,9 +17,7 @@ You should clone this repository and run setup.py:: Alternatively, you can run:: - pip install --upgrade https://github.com/FalkTannhaeuser/python-onvif-zeep/archive/zeep.zip - -(If you are behind a proxy, add the appropriate '--proxy=' option to this *pip* command line.) + pip install --upgrade onvif_zeep Getting Started diff --git a/onvif/version.txt b/onvif/version.txt index cf74bd3..b003284 100644 --- a/onvif/version.txt +++ b/onvif/version.txt @@ -1,2 +1 @@ -0.2.1 - +0.2.7 diff --git a/setup.py b/setup.py index c8c7807..f256174 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ import os from setuptools import setup, find_packages +import sys here = os.path.abspath(os.path.dirname(__file__)) version_path = os.path.join(here, 'onvif/version.txt') @@ -27,10 +28,13 @@ "Programming Language :: Python :: 3.5", ] -wsdl_files = ['wsdl/' + item for item in os.listdir('wsdl')] +wsdl_files = [os.path.join('wsdl', item) for item in os.listdir('wsdl')] +wsdl_dst_dir = 'Lib/site-packages/wsdl' if sys.platform == 'win32' else \ + 'lib/python%d.%d/site-packages/wsdl' % (sys.version_info.major, + sys.version_info.minor) setup( - name='onvif', + name='onvif_zeep', version=version, description='Python Client for ONVIF Camera', long_description=open('README.rst', 'r').read(), @@ -45,7 +49,7 @@ packages=find_packages(exclude=['docs', 'examples', 'tests']), install_requires=requires, include_package_data=True, - data_files=[('wsdl', wsdl_files)], + data_files=[(wsdl_dst_dir, wsdl_files)], entry_points={ 'console_scripts': ['onvif-cli = onvif.cli:main'] } From 9d3c15d79fbb02f2f6d3498a99420709039db79e Mon Sep 17 00:00:00 2001 From: Lucas Zanella Date: Sun, 22 Oct 2017 19:29:06 -0200 Subject: [PATCH 12/39] added transport capabilities --- onvif/client.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index 8f17d5b..a12c283 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -83,7 +83,7 @@ class ONVIFService(object): @safe_func def __init__(self, xaddr, user, passwd, url, encrypt=True, daemon=False, zeep_client=None, no_cache=False, - portType=None, dt_diff=None, binding_name=''): + portType=None, dt_diff=None, binding_name='', transport=None): #print('ONVIFService() ENTER', locals()) if not os.path.isfile(url): @@ -96,7 +96,7 @@ def __init__(self, xaddr, user, passwd, url, if not zeep_client: #print(self.url, self.xaddr) ClientType = Client if no_cache else CachingClient - self.zeep_client = ClientType(wsdl=url, wsse=wsse, strict=False, xml_huge_tree=True) + self.zeep_client = ClientType(wsdl=url, wsse=wsse, strict=False, xml_huge_tree=True, transport=transport) else: self.zeep_client = zeep_client self.ws_client = self.zeep_client.create_service(binding_name, self.xaddr) @@ -221,7 +221,7 @@ class ONVIFCamera(object): use_services_template = {'devicemgmt': True, 'ptz': True, 'media': True, 'imaging': True, 'events': True, 'analytics': True } def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirname(os.path.dirname(__file__)), "wsdl"), - encrypt=True, daemon=False, no_cache=False, adjust_time=False): + encrypt=True, daemon=False, no_cache=False, adjust_time=False, transport=None): #print('ONVIFCamera() ENTER', locals()) os.environ.pop('http_proxy', None) os.environ.pop('https_proxy', None) @@ -234,6 +234,7 @@ def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirna self.daemon = daemon self.no_cache = no_cache self.adjust_time = adjust_time + self.transport = transport # Active service client container self.services = { } @@ -369,7 +370,8 @@ def create_onvif_service(self, name, from_template=True, portType=None): self.daemon, no_cache=self.no_cache, portType=portType, dt_diff=self.dt_diff, - binding_name=binding_name) + binding_name=binding_name, + transport=self.transport) self.services[name] = service From 5bf8551c50d826daec93a309d24a58476beeb47f Mon Sep 17 00:00:00 2001 From: Lucas Zanella Date: Sun, 22 Oct 2017 22:13:00 -0200 Subject: [PATCH 13/39] added transports support --- onvif/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index a12c283..2e5c490 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -12,8 +12,8 @@ from zeep.wsse.username import UsernameToken import zeep.helpers -from onvif.exceptions import ONVIFError -from onvif.definition import SERVICES +from exceptions import ONVIFError +from definition import SERVICES #from suds.sax.date import UTC import datetime as dt # Ensure methods to raise an ONVIFError Exception From 14d9b0aa41b27d3ec9d9e5fe8ebdfef26cf2ea81 Mon Sep 17 00:00:00 2001 From: Io Mihai Date: Tue, 23 Jan 2018 11:49:35 +0100 Subject: [PATCH 14/39] fix client.py imports for python3 --- onvif/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index 2e5c490..a12c283 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -12,8 +12,8 @@ from zeep.wsse.username import UsernameToken import zeep.helpers -from exceptions import ONVIFError -from definition import SERVICES +from onvif.exceptions import ONVIFError +from onvif.definition import SERVICES #from suds.sax.date import UTC import datetime as dt # Ensure methods to raise an ONVIFError Exception From a02544318e1fe527e25e58816dab2daafdde6395 Mon Sep 17 00:00:00 2001 From: Io Mihai Date: Tue, 23 Jan 2018 11:51:01 +0100 Subject: [PATCH 15/39] fix events pullpoint service xaddr --- onvif/client.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index a12c283..44b8bcc 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -271,7 +271,7 @@ def update_xaddrs(self): with self.services_lock: try: self.event = self.create_events_service() - self.xaddrs['http://www.onvif.org/ver10/events/wsdl/PullPointSubscription'] = self.event.CreatePullPointSubscription().SubscriptionReference.Address + self.xaddrs['http://www.onvif.org/ver10/events/wsdl/PullPointSubscription'] = self.event.CreatePullPointSubscription().SubscriptionReference.Address._value_1 except: pass @@ -319,13 +319,17 @@ def get_service(self, name, create=True): return getattr(self, 'create_%s_service' % name.lower())() return service - def get_definition(self, name): + def get_definition(self, name, portType=None): '''Returns xaddr and wsdl of specified service''' # Check if the service is supported if name not in SERVICES: raise ONVIFError('Unknown service %s' % name) wsdl_file = SERVICES[name]['wsdl'] ns = SERVICES[name]['ns'] + + if portType: + ns += '/' + portType + binding_name = '{%s}%s' % (ns, SERVICES[name]['binding']) wsdlpath = os.path.join(self.wsdl_dir, wsdl_file) @@ -348,7 +352,7 @@ def create_onvif_service(self, name, from_template=True, portType=None): '''Create ONVIF service client''' name = name.lower() - xaddr, wsdl_file, binding_name = self.get_definition(name) + xaddr, wsdl_file, binding_name = self.get_definition(name, portType) with self.services_lock: # svt = self.services_template.get(name) From ea56212bf7d5fdfd53890d7b37ab0146f1c11282 Mon Sep 17 00:00:00 2001 From: Io Mihai Date: Mon, 2 Apr 2018 11:03:13 +0200 Subject: [PATCH 16/39] Add HTTPS client support --- onvif/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/onvif/client.py b/onvif/client.py index 44b8bcc..dae35b7 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -338,7 +338,9 @@ def get_definition(self, name, portType=None): # XAddr for devicemgmt is fixed: if name == 'devicemgmt': - xaddr = 'http://%s:%s/onvif/device_service' % (self.host, self.port) + xaddr = '%s:%s/onvif/device_service' % \ + (self.host if (self.host.startswith('http://') or self.host.startswith('https://')) + else 'http://%s' % self.host, self.port) return xaddr, wsdlpath, binding_name # Get other XAddr From 728375f080fb8c51ab33aee96005a3b6abc46974 Mon Sep 17 00:00:00 2001 From: Io Mihai Date: Mon, 2 Apr 2018 11:03:44 +0200 Subject: [PATCH 17/39] gitignore Pipfile --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index db4561e..8eecbd4 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,8 @@ docs/_build/ # PyBuilder target/ + +.idea +.venv +Pipfile +Pipfile.lock From b4df7982d0549371599fd0b2d84485e082c0e03a Mon Sep 17 00:00:00 2001 From: Io Mihai Date: Tue, 3 Apr 2018 17:49:17 +0200 Subject: [PATCH 18/39] create_pullpoint_service() binding fix --- onvif/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index dae35b7..f0c1fcd 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -327,11 +327,11 @@ def get_definition(self, name, portType=None): wsdl_file = SERVICES[name]['wsdl'] ns = SERVICES[name]['ns'] + binding_name = '{%s}%s' % (ns, SERVICES[name]['binding']) + if portType: ns += '/' + portType - binding_name = '{%s}%s' % (ns, SERVICES[name]['binding']) - wsdlpath = os.path.join(self.wsdl_dir, wsdl_file) if not os.path.isfile(wsdlpath): raise ONVIFError('No such file: %s' % wsdlpath) From d9d81510fa039d02c046261664466e7dafa368c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Mon, 9 Apr 2018 20:03:44 +0200 Subject: [PATCH 19/39] Bump version after merging pull request --- onvif/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onvif/version.txt b/onvif/version.txt index b003284..a45be46 100644 --- a/onvif/version.txt +++ b/onvif/version.txt @@ -1 +1 @@ -0.2.7 +0.2.8 From 21ae5d3689a95d80abfe7ea182519e69f426ff4d Mon Sep 17 00:00:00 2001 From: Alexander Streb Date: Wed, 20 Jun 2018 10:58:50 +0200 Subject: [PATCH 20/39] Fixed client for zeep v3.0.0 --- onvif/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index f0c1fcd..e3fd4ab 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -8,7 +8,7 @@ logging.basicConfig(level=logging.INFO) logging.getLogger('zeep.client').setLevel(logging.CRITICAL) -from zeep.client import Client, CachingClient +from zeep.client import Client, CachingClient, Settings from zeep.wsse.username import UsernameToken import zeep.helpers @@ -96,7 +96,10 @@ def __init__(self, xaddr, user, passwd, url, if not zeep_client: #print(self.url, self.xaddr) ClientType = Client if no_cache else CachingClient - self.zeep_client = ClientType(wsdl=url, wsse=wsse, strict=False, xml_huge_tree=True, transport=transport) + settings = Settings() + settings.strict = False + settings.xml_huge_tree = True + self.zeep_client = ClientType(wsdl=url, wsse=wsse, transport=transport, settings=settings) else: self.zeep_client = zeep_client self.ws_client = self.zeep_client.create_service(binding_name, self.xaddr) From a49e461ff9067656b3925069e5d33c1f9ac8a649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Thu, 21 Jun 2018 10:57:47 +0200 Subject: [PATCH 21/39] Bumped version to 0.2.11 (now requires zeep >= 3.0.0) --- README.rst | 2 +- onvif/version.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 4d4f68c..5c6c673 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ ONVIF Client Implementation in Python Dependencies ------------ -`zeep `_ >= 2.2.0 +`zeep `_ >= 3.0.0 Install python-onvif-zeep ------------------------- diff --git a/onvif/version.txt b/onvif/version.txt index a45be46..d3b5ba4 100644 --- a/onvif/version.txt +++ b/onvif/version.txt @@ -1 +1 @@ -0.2.8 +0.2.11 diff --git a/setup.py b/setup.py index f256174..b468675 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ version_path = os.path.join(here, 'onvif/version.txt') version = open(version_path).read().strip() -requires = ['zeep >= 2.2.0'] +requires = ['zeep >= 3.0.0'] CLASSIFIERS = [ 'Development Status :: 3 - Alpha', From 0d7f847327e425c6994abfad139f109d5247ef05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Mon, 20 Aug 2018 19:29:37 +0200 Subject: [PATCH 22/39] Allow the use of a time delta parameter between local and camera time. This allow authentication on cameras without being time synchronized. Note: In commit 9b3beccf84806e03373091c905ea331a5e2c0814 this was already done previously for the "SUDS" SOAP client. Now done for Zeep. --- onvif/client.py | 52 ++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index e3fd4ab..7f3094c 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -23,30 +23,33 @@ def wrapped(*args, **kwargs): try: return func(*args, **kwargs) except Exception as err: -# print('Ouuups: err =', err, ', func =', func, ', args =', args, ', type(args) =', -# type(args), ', kwargs =', kwargs, ', type(kwargs) =', type(kwargs)) + #print('Ouuups: err =', err, ', func =', func, ', args =', args, ', kwargs =', kwargs) raise ONVIFError(err) return wrapped -#class UsernameDigestTokenDtDiff(UsernameDigestToken): -# ''' -# UsernameDigestToken class, with a time offset parameter that can be adjusted; -# This allows authentication on cameras without being time synchronized. -# Please note that using NTP on both end is the recommended solution, -# this should only be used in "safe" environements. -# ''' -# def __init__(self, user, passw, dt_diff=None) : -## Old Style class ... sigh ... -# UsernameDigestToken.__init__(self, user, passw) -# self.dt_diff = dt_diff - -# def setcreated(self, *args, **kwargs): -# dt_adjusted = None -# if self.dt_diff : -# dt_adjusted = (self.dt_diff + dt.datetime.utcnow()) -# UsernameToken.setcreated(self, dt=dt_adjusted, *args, **kwargs) -# self.created = str(UTC(self.created)) +class UsernameDigestTokenDtDiff(UsernameToken): + ''' + UsernameDigestToken class, with a time offset parameter that can be adjusted; + This allows authentication on cameras without being time synchronized. + Please note that using NTP on both end is the recommended solution, + this should only be used in "safe" environments. + ''' + def __init__(self, user, passw, dt_diff=None, **kwargs): + super().__init__(user, passw, **kwargs) + self.dt_diff = dt_diff # Date/time difference in datetime.timedelta + + def apply(self, envelope, headers): + old_created = self.created + if self.created is None: + self.created = dt.datetime.utcnow() + #print('UsernameDigestTokenDtDiff.created: old = %s (type = %s), dt_diff = %s (type = %s)' % (self.created, type(self.created), self.dt_diff, type(self.dt_diff)), end='') + if self.dt_diff is not None: + self.created += self.dt_diff + #print(' new = %s' % self.created) + result = super().apply(envelope, headers) + self.created = old_created + return result class ONVIFService(object): @@ -91,7 +94,8 @@ def __init__(self, xaddr, user, passwd, url, self.url = url self.xaddr = xaddr - wsse = UsernameToken(user, passwd, use_digest=encrypt) + wsse = UsernameDigestTokenDtDiff(user, passwd, dt_diff=dt_diff, use_digest=encrypt) +# wsse = UsernameToken(user, passwd, use_digest=encrypt) # Create soap client if not zeep_client: #print(self.url, self.xaddr) @@ -183,7 +187,6 @@ def call(params=None, callback=None): return call(params, callback) return wrapped - def __getattr__(self, name): ''' Call the real onvif Service operations, @@ -197,6 +200,7 @@ def __getattr__(self, name): else: return self.service_wrapper(getattr(self.ws_client, name)) + class ONVIFCamera(object): ''' Python Implemention ONVIF compliant device @@ -259,6 +263,7 @@ def update_xaddrs(self): self.dt_diff = cam_date - dt.datetime.utcnow() self.devicemgmt.dt_diff = self.dt_diff #self.devicemgmt.set_wsse() + self.devicemgmt = self.create_devicemgmt_service() # Get XAddr of services on the device self.xaddrs = { } capabilities = self.devicemgmt.GetCapabilities({'Category': 'All'}) @@ -278,7 +283,6 @@ def update_xaddrs(self): except: pass - def update_url(self, host=None, port=None): changed = False if host and self.host != host: @@ -334,7 +338,7 @@ def get_definition(self, name, portType=None): if portType: ns += '/' + portType - + wsdlpath = os.path.join(self.wsdl_dir, wsdl_file) if not os.path.isfile(wsdlpath): raise ONVIFError('No such file: %s' % wsdlpath) From 3273b2000e961e2f148282c4fd8c4523c8ae15b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Mon, 20 Aug 2018 19:41:27 +0200 Subject: [PATCH 23/39] Dead code removal --- onvif/client.py | 72 +++---------------------------------------------- 1 file changed, 3 insertions(+), 69 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index 7f3094c..6bc867f 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -14,7 +14,6 @@ from onvif.exceptions import ONVIFError from onvif.definition import SERVICES -#from suds.sax.date import UTC import datetime as dt # Ensure methods to raise an ONVIFError Exception # when some thing was wrong @@ -87,15 +86,12 @@ class ONVIFService(object): def __init__(self, xaddr, user, passwd, url, encrypt=True, daemon=False, zeep_client=None, no_cache=False, portType=None, dt_diff=None, binding_name='', transport=None): - #print('ONVIFService() ENTER', locals()) - if not os.path.isfile(url): raise ONVIFError('%s doesn`t exist!' % url) self.url = url self.xaddr = xaddr wsse = UsernameDigestTokenDtDiff(user, passwd, dt_diff=dt_diff, use_digest=encrypt) -# wsse = UsernameToken(user, passwd, use_digest=encrypt) # Create soap client if not zeep_client: #print(self.url, self.xaddr) @@ -113,39 +109,9 @@ def __init__(self, xaddr, user, passwd, url, self.passwd = passwd # Indicate wether password digest is needed self.encrypt = encrypt - self.daemon = daemon - self.dt_diff = dt_diff -# self.set_wsse() - - # Method to create type instance of service method defined in WSDL -# print(">>>>>", self.zeep_client.__dict__) -# print(">>>>>", self.ws_client.__dict__) - #print(">>>>>", self.zeep_client.type_factory('ns1').__dict__) - #self.create_type = self.ws_client.factory.create self.create_type = lambda x: self.zeep_client.get_element('ns0:' + x)() - #print('ONVIFService() EXIT') - -# @safe_func -# def set_wsse(self, user=None, passwd=None): -# ''' Basic ws-security auth ''' -# if user: -# self.user = user -# if passwd: -# self.passwd = passwd - -# security = Security() - -# if self.encrypt: -# token = UsernameDigestTokenDtDiff(self.user, self.passwd, dt_diff=self.dt_diff) -# else: -# token = UsernameToken(self.user, self.passwd) -# token.setnonce() -# token.setcreated() -# -# security.tokens.append(token) -# self.ws_client.set_options(wsse=security) @classmethod @safe_func @@ -190,7 +156,7 @@ def call(params=None, callback=None): def __getattr__(self, name): ''' Call the real onvif Service operations, - See the offical wsdl definition for the + See the official wsdl definition for the APIs detail(API name, request parameters, response parameters, parameter types, etc...) ''' @@ -208,7 +174,7 @@ class ONVIFCamera(object): adjust_time parameter allows authentication on cameras without being time synchronized. Please note that using NTP on both end is the recommended solution, - this should only be used in "safe" environements. + this should only be used in "safe" environments. Also, this cannot be used on AXIS camera, as every request is authenticated, contrary to ONVIF standard >>> from onvif import ONVIFCamera @@ -229,7 +195,6 @@ class ONVIFCamera(object): 'imaging': True, 'events': True, 'analytics': True } def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirname(os.path.dirname(__file__)), "wsdl"), encrypt=True, daemon=False, no_cache=False, adjust_time=False, transport=None): - #print('ONVIFCamera() ENTER', locals()) os.environ.pop('http_proxy', None) os.environ.pop('https_proxy', None) self.host = host @@ -251,7 +216,6 @@ def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirna self.update_xaddrs() self.to_dict = ONVIFService.to_dict - #print('ONVIFCamera() EXIT') def update_xaddrs(self): # Establish devicemgmt service first @@ -274,7 +238,7 @@ def update_xaddrs(self): ns = SERVICES[name.lower()]['ns'] self.xaddrs[ns] = capability['XAddr'] except Exception: - logger.exception('Unexcept service type') + logger.exception('Unexpected service type') with self.services_lock: try: @@ -303,22 +267,6 @@ def update_url(self, host=None, port=None): xaddr = getattr(self.capabilities, sname.capitalize).XAddr self.services[sname].ws_client.set_options(location=xaddr) -# def update_auth(self, user=None, passwd=None): -# changed = False -# if user and user != self.user: -# changed = True -# self.user = user -# if passwd and passwd != self.passwd: -# changed = True -# self.passwd = passwd - -# if not changed: -# return - -# with self.services_lock: -# for service in self.services.keys(): -# self.services[service].set_wsse(user, passwd) - def get_service(self, name, create=True): service = None service = getattr(self, name.lower(), None) @@ -364,20 +312,6 @@ def create_onvif_service(self, name, from_template=True, portType=None): xaddr, wsdl_file, binding_name = self.get_definition(name, portType) with self.services_lock: -# svt = self.services_template.get(name) -# # Has a template, clone from it. Faster. -# if svt and from_template and self.use_services_template.get(name): -# service = ONVIFService.clone(svt, xaddr, self.user, -# self.passwd, wsdl_file, -# self.encrypt, -# self.daemon, -# no_cache=self.no_cache, -# portType=portType, -# dt_diff=self.dt_diff, -# binding_name=binding_name) -# # No template, create new service from wsdl document. -# # A little time-comsuming -# else: service = ONVIFService(xaddr, self.user, self.passwd, wsdl_file, self.encrypt, self.daemon, no_cache=self.no_cache, From 1cf4ceedc5afeee970c1a87d0a7166a4532a3b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Falk=20Tannh=C3=A4user?= Date: Mon, 20 Aug 2018 19:41:59 +0200 Subject: [PATCH 24/39] Bump version number --- onvif/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onvif/version.txt b/onvif/version.txt index d3b5ba4..f2722b1 100644 --- a/onvif/version.txt +++ b/onvif/version.txt @@ -1 +1 @@ -0.2.11 +0.2.12 From a5f3f203ae3da32eb8afbfc0e88cdec1c22d153a Mon Sep 17 00:00:00 2001 From: Francois Wautier Date: Thu, 8 Nov 2018 14:56:42 +0700 Subject: [PATCH 25/39] This was not working. Still for Python 2. Made it into a simplistic interactive command. Tested with VStarcam --- examples/continuous_move.py | 175 +++++++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 51 deletions(-) diff --git a/examples/continuous_move.py b/examples/continuous_move.py index 058a6f9..1b9a003 100644 --- a/examples/continuous_move.py +++ b/examples/continuous_move.py @@ -1,63 +1,100 @@ -from time import sleep - +import asyncio, sys from onvif import ONVIFCamera +IP="192.168.0.100" # Camera IP address +PORT=10080 # Port +USER="admin" # Username +PASS="password" # Password + + XMAX = 1 XMIN = -1 YMAX = 1 YMIN = -1 +moverequest = None +ptz = None +active = False -def perform_move(ptz, request, timeout): +def do_move(ptz, request): # Start continuous move + global active + if active: + ptz.Stop({'ProfileToken': request.ProfileToken}) + active = True ptz.ContinuousMove(request) - # Wait a certain time - sleep(timeout) - # Stop continuous move - ptz.Stop({'ProfileToken': request.ProfileToken}) - -def move_up(ptz, request, timeout=1): - print 'move up...' - request.Velocity.PanTilt._x = 0 - request.Velocity.PanTilt._y = YMAX - perform_move(ptz, request, timeout) - -def move_down(ptz, request, timeout=1): - print 'move down...' - request.Velocity.PanTilt._x = 0 - request.Velocity.PanTilt._y = YMIN - perform_move(ptz, request, timeout) - -def move_right(ptz, request, timeout=1): - print 'move right...' - request.Velocity.PanTilt._x = XMAX - request.Velocity.PanTilt._y = 0 - perform_move(ptz, request, timeout) - -def move_left(ptz, request, timeout=1): - print 'move left...' - request.Velocity.PanTilt._x = XMIN - request.Velocity.PanTilt._y = 0 - perform_move(ptz, request, timeout) - -def continuous_move(): - mycam = ONVIFCamera('192.168.0.112', 80, 'admin', '12345') + +def move_up(ptz, request): + print ('move up...') + request.Velocity.PanTilt.x = 0 + request.Velocity.PanTilt.y = YMAX + do_move(ptz, request) + +def move_down(ptz, request): + print ('move down...') + request.Velocity.PanTilt.x = 0 + request.Velocity.PanTilt.y = YMIN + do_move(ptz, request) + +def move_right(ptz, request): + print ('move right...') + request.Velocity.PanTilt.x = XMAX + request.Velocity.PanTilt.y = 0 + do_move(ptz, request) + +def move_left(ptz, request): + print ('move left...') + request.Velocity.PanTilt.x = XMIN + request.Velocity.PanTilt.y = 0 + do_move(ptz, request) + + +def move_upleft(ptz, request): + print ('move up left...') + request.Velocity.PanTilt.x = XMIN + request.Velocity.PanTilt.y = YMAX + do_move(ptz, request) + +def move_upright(ptz, request): + print ('move up left...') + request.Velocity.PanTilt.x = XMAX + request.Velocity.PanTilt.y = YMAX + do_move(ptz, request) + +def move_downleft(ptz, request): + print ('move down left...') + request.Velocity.PanTilt.x = XMIN + request.Velocity.PanTilt.y = YMIN + do_move(ptz, request) + +def move_downright(ptz, request): + print ('move down left...') + request.Velocity.PanTilt.x = XMAX + request.Velocity.PanTilt.y = YMIN + do_move(ptz, request) + +def setup_move(): + mycam = ONVIFCamera(IP, PORT, USER, PASS) # Create media service object media = mycam.create_media_service() + # Create ptz service object + global ptz ptz = mycam.create_ptz_service() # Get target profile - media_profile = media.GetProfiles()[0]; + media_profile = media.GetProfiles()[0] # Get PTZ configuration options for getting continuous move range request = ptz.create_type('GetConfigurationOptions') - request.ConfigurationToken = media_profile.PTZConfiguration._token + request.ConfigurationToken = media_profile.PTZConfiguration.token ptz_configuration_options = ptz.GetConfigurationOptions(request) - request = ptz.create_type('ContinuousMove') - request.ProfileToken = media_profile._token + global moverequest + moverequest = ptz.create_type('ContinuousMove') + moverequest.ProfileToken = media_profile.token + if moverequest.Velocity is None: + moverequest.Velocity = ptz.GetStatus({'ProfileToken': media_profile.token}).Position - ptz.Stop({'ProfileToken': media_profile._token}) # Get range of pan and tilt # NOTE: X and Y are velocity vector @@ -67,17 +104,53 @@ def continuous_move(): YMAX = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max YMIN = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Min - # move right - move_right(ptz, request) - - # move left - move_left(ptz, request) - - # Move up - move_up(ptz, request) - - # move down - move_down(ptz, request) +def readin(): + """Reading from stdin and displaying menu""" + global moverequest, ptz + + selection = sys.stdin.readline().strip("\n") + lov=[ x for x in selection.split(" ") if x != ""] + if lov: + + if lov[0].lower() in ["u","up"]: + move_up(ptz,moverequest) + elif lov[0].lower() in ["d","do","dow","down"]: + move_down(ptz,moverequest) + elif lov[0].lower() in ["l","le","lef","left"]: + move_left(ptz,moverequest) + elif lov[0].lower() in ["l","le","lef","left"]: + move_left(ptz,moverequest) + elif lov[0].lower() in ["r","ri","rig","righ","right"]: + move_right(ptz,moverequest) + elif lov[0].lower() in ["ul"]: + move_upleft(ptz,moverequest) + elif lov[0].lower() in ["ur"]: + move_upright(ptz,moverequest) + elif lov[0].lower() in ["dl"]: + move_downleft(ptz,moverequest) + elif lov[0].lower() in ["dr"]: + move_downright(ptz,moverequest) + elif lov[0].lower() in ["s","st","sto","stop"]: + ptz.Stop({'ProfileToken': moverequest.ProfileToken}) + active = False + else: + 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'") + + print("") + print("Your command: ", end='',flush=True) + + if __name__ == '__main__': - continuous_move() + setup_move() + loop = asyncio.get_event_loop() + try: + loop.add_reader(sys.stdin,readin) + print("Use Ctrl-C to quit") + print("Your command: ", end='',flush=True) + loop.run_forever() + except: + pass + finally: + loop.remove_reader(sys.stdin) + loop.close() From 980b57931903d44d19a8c7ea70bb617937178d51 Mon Sep 17 00:00:00 2001 From: James Adams Date: Wed, 13 Feb 2019 15:40:11 -0500 Subject: [PATCH 26/39] added a notification service in order to facilitate requesting a notifications subscription #37 --- onvif/client.py | 3 +++ onvif/definition.py | 25 +++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index 6bc867f..d0bd89c 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -364,3 +364,6 @@ def create_pullpoint_service(self, from_template=True): def create_receiver_service(self, from_template=True): return self.create_onvif_service('receiver', from_template) + + def create_notification_service(self, from_template=True): + return self.create_onvif_service('notifications', from_template) diff --git a/onvif/definition.py b/onvif/definition.py index 31ea6b9..22450a6 100644 --- a/onvif/definition.py +++ b/onvif/definition.py @@ -1,17 +1,18 @@ SERVICES = { # Name namespace wsdl file binding name - 'devicemgmt': {'ns': 'http://www.onvif.org/ver10/device/wsdl', 'wsdl': 'devicemgmt.wsdl', 'binding' : 'DeviceBinding'}, - 'media' : {'ns': 'http://www.onvif.org/ver10/media/wsdl', 'wsdl': 'media.wsdl', 'binding' : 'MediaBinding'}, - 'ptz' : {'ns': 'http://www.onvif.org/ver20/ptz/wsdl', 'wsdl': 'ptz.wsdl', 'binding' : 'PTZBinding'}, - 'imaging' : {'ns': 'http://www.onvif.org/ver20/imaging/wsdl', 'wsdl': 'imaging.wsdl', 'binding' : 'ImagingBinding'}, - 'deviceio' : {'ns': 'http://www.onvif.org/ver10/deviceIO/wsdl', 'wsdl': 'deviceio.wsdl', 'binding' : 'DeviceIOBinding'}, - 'events' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'EventBinding'}, - 'pullpoint' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'PullPointSubscriptionBinding'}, - 'analytics' : {'ns': 'http://www.onvif.org/ver20/analytics/wsdl', 'wsdl': 'analytics.wsdl', 'binding' : 'AnalyticsEngineBinding'}, - 'recording' : {'ns': 'http://www.onvif.org/ver10/recording/wsdl', 'wsdl': 'recording.wsdl', 'binding' : 'RecordingBinding'}, - 'search' : {'ns': 'http://www.onvif.org/ver10/search/wsdl', 'wsdl': 'search.wsdl', 'binding' : 'SearchBinding'}, - 'replay' : {'ns': 'http://www.onvif.org/ver10/replay/wsdl', 'wsdl': 'replay.wsdl', 'binding' : 'ReplayBinding'}, - 'receiver' : {'ns': 'http://www.onvif.org/ver10/receiver/wsdl', 'wsdl': 'receiver.wsdl', 'binding' : 'ReceiverBinding'}, + 'devicemgmt' : {'ns': 'http://www.onvif.org/ver10/device/wsdl', 'wsdl': 'devicemgmt.wsdl', 'binding' : 'DeviceBinding'}, + 'media' : {'ns': 'http://www.onvif.org/ver10/media/wsdl', 'wsdl': 'media.wsdl', 'binding' : 'MediaBinding'}, + 'ptz' : {'ns': 'http://www.onvif.org/ver20/ptz/wsdl', 'wsdl': 'ptz.wsdl', 'binding' : 'PTZBinding'}, + 'imaging' : {'ns': 'http://www.onvif.org/ver20/imaging/wsdl', 'wsdl': 'imaging.wsdl', 'binding' : 'ImagingBinding'}, + 'deviceio' : {'ns': 'http://www.onvif.org/ver10/deviceIO/wsdl', 'wsdl': 'deviceio.wsdl', 'binding' : 'DeviceIOBinding'}, + 'events' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'EventBinding'}, + 'pullpoint' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'PullPointSubscriptionBinding'}, + 'notification' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'NotificationProducerBinding'}, + 'analytics' : {'ns': 'http://www.onvif.org/ver20/analytics/wsdl', 'wsdl': 'analytics.wsdl', 'binding' : 'AnalyticsEngineBinding'}, + 'recording' : {'ns': 'http://www.onvif.org/ver10/recording/wsdl', 'wsdl': 'recording.wsdl', 'binding' : 'RecordingBinding'}, + 'search' : {'ns': 'http://www.onvif.org/ver10/search/wsdl', 'wsdl': 'search.wsdl', 'binding' : 'SearchBinding'}, + 'replay' : {'ns': 'http://www.onvif.org/ver10/replay/wsdl', 'wsdl': 'replay.wsdl', 'binding' : 'ReplayBinding'}, + 'receiver' : {'ns': 'http://www.onvif.org/ver10/receiver/wsdl', 'wsdl': 'receiver.wsdl', 'binding' : 'ReceiverBinding'}, } # From fb5141ba161094309b5b42055c889e1d74800e3d Mon Sep 17 00:00:00 2001 From: James Adams Date: Wed, 13 Feb 2019 15:58:12 -0500 Subject: [PATCH 27/39] fixed bug where we had the service name as "notifications" (plural) instead of the correct "notification" (singular) #37 --- onvif/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onvif/client.py b/onvif/client.py index d0bd89c..eb00eb1 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -366,4 +366,4 @@ def create_receiver_service(self, from_template=True): return self.create_onvif_service('receiver', from_template) def create_notification_service(self, from_template=True): - return self.create_onvif_service('notifications', from_template) + return self.create_onvif_service('notification', from_template) From de055950129ac9b4e96ca6b7cdb9054187fb0daa Mon Sep 17 00:00:00 2001 From: James Adams Date: Mon, 18 Feb 2019 14:07:39 -0500 Subject: [PATCH 28/39] added an additional subscription service to facilitate subscription renewals #17 --- onvif/client.py | 13 ++++++++++++- onvif/definition.py | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/onvif/client.py b/onvif/client.py index eb00eb1..e509719 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -305,7 +305,15 @@ def get_definition(self, name, portType=None): return xaddr, wsdlpath, binding_name - def create_onvif_service(self, name, from_template=True, portType=None): + def create_onvif_service(self, name, portType=None): + """ + Create ONVIF service client. + + :param name: service name, should be present as a key within + the `SERVICES` dictionary declared in onvif.definition + :param portType: + :return: + """ '''Create ONVIF service client''' name = name.lower() @@ -367,3 +375,6 @@ def create_receiver_service(self, from_template=True): def create_notification_service(self, from_template=True): return self.create_onvif_service('notification', from_template) + + def create_subscription_service(self, from_template=True): + return self.create_onvif_service('subscription', from_template) diff --git a/onvif/definition.py b/onvif/definition.py index 22450a6..24acbd3 100644 --- a/onvif/definition.py +++ b/onvif/definition.py @@ -8,6 +8,7 @@ 'events' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'EventBinding'}, 'pullpoint' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'PullPointSubscriptionBinding'}, 'notification' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'NotificationProducerBinding'}, + 'subscription' : {'ns': 'http://www.onvif.org/ver10/events/wsdl', 'wsdl': 'events.wsdl', 'binding' : 'SubscriptionManagerBinding'}, 'analytics' : {'ns': 'http://www.onvif.org/ver20/analytics/wsdl', 'wsdl': 'analytics.wsdl', 'binding' : 'AnalyticsEngineBinding'}, 'recording' : {'ns': 'http://www.onvif.org/ver10/recording/wsdl', 'wsdl': 'recording.wsdl', 'binding' : 'RecordingBinding'}, 'search' : {'ns': 'http://www.onvif.org/ver10/search/wsdl', 'wsdl': 'search.wsdl', 'binding' : 'SearchBinding'}, From 8a18caac1eb5b16ff836799df638264cd863f530 Mon Sep 17 00:00:00 2001 From: James Adams Date: Mon, 18 Feb 2019 16:02:35 -0500 Subject: [PATCH 29/39] removed the unused `from_template` parameter from the ONVIFCamera.create_onvif_service function, various cleanups highlighted by PyCharm inspection for PEP8 compliance, spelling, unused variables, etc. #17 --- onvif/client.py | 40 +++++++++++----------- tests/test.py | 88 +++++++++++++++++++++++++------------------------ 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index e509719..409fdc7 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -338,43 +338,43 @@ def create_onvif_service(self, name, portType=None): def create_devicemgmt_service(self, from_template=True): # The entry point for devicemgmt service is fixed. - return self.create_onvif_service('devicemgmt', from_template) + return self.create_onvif_service('devicemgmt') - def create_media_service(self, from_template=True): - return self.create_onvif_service('media', from_template) + def create_media_service(self): + return self.create_onvif_service('media') - def create_ptz_service(self, from_template=True): - return self.create_onvif_service('ptz', from_template) + def create_ptz_service(self): + return self.create_onvif_service('ptz') - def create_imaging_service(self, from_template=True): - return self.create_onvif_service('imaging', from_template) + def create_imaging_service(self): + return self.create_onvif_service('imaging') - def create_deviceio_service(self, from_template=True): - return self.create_onvif_service('deviceio', from_template) + def create_deviceio_service(self): + return self.create_onvif_service('deviceio') - def create_events_service(self, from_template=True): - return self.create_onvif_service('events', from_template) + def create_events_service(self): + return self.create_onvif_service('events') - def create_analytics_service(self, from_template=True): - return self.create_onvif_service('analytics', from_template) + def create_analytics_service(self): + return self.create_onvif_service('analytics') def create_recording_service(self, from_template=True): - return self.create_onvif_service('recording', from_template) + return self.create_onvif_service('recording') def create_search_service(self, from_template=True): - return self.create_onvif_service('search', from_template) + return self.create_onvif_service('search') def create_replay_service(self, from_template=True): - return self.create_onvif_service('replay', from_template) + return self.create_onvif_service('replay') def create_pullpoint_service(self, from_template=True): - return self.create_onvif_service('pullpoint', from_template, portType='PullPointSubscription') + return self.create_onvif_service('pullpoint', portType='PullPointSubscription') def create_receiver_service(self, from_template=True): - return self.create_onvif_service('receiver', from_template) + return self.create_onvif_service('receiver') def create_notification_service(self, from_template=True): - return self.create_onvif_service('notification', from_template) + return self.create_onvif_service('notification') def create_subscription_service(self, from_template=True): - return self.create_onvif_service('subscription', from_template) + return self.create_onvif_service('subscription') diff --git a/tests/test.py b/tests/test.py index 095320c..bd63dc3 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,21 +1,23 @@ #!/usr/bin/python -#-*-coding=utf-8 +# -*-coding=utf-8 from __future__ import print_function, division import unittest from onvif import ONVIFCamera, ONVIFError -CAM_HOST = '172.20.9.84' -CAM_PORT = 80 -CAM_USER = 'root' -CAM_PASS = 'password' +CAM_HOST = '172.20.9.84' +CAM_PORT = 80 +CAM_USER = 'root' +CAM_PASS = 'password' DEBUG = False + def log(ret): if DEBUG: print(ret) + class TestDevice(unittest.TestCase): # Class level cam. Run this test more efficiently.. @@ -23,63 +25,61 @@ class TestDevice(unittest.TestCase): # ***************** Test Capabilities *************************** def test_GetWsdlUrl(self): - ret = self.cam.devicemgmt.GetWsdlUrl() + self.cam.devicemgmt.GetWsdlUrl() def test_GetServices(self): - ''' - Returns a cllection of the devices + """ + Returns a collection of the devices services and possibly their available capabilities - ''' - params = {'IncludeCapability': True } - ret = self.cam.devicemgmt.GetServices(params) + """ + params = {'IncludeCapability': True} + self.cam.devicemgmt.GetServices(params) params = self.cam.devicemgmt.create_type('GetServices') - params.IncludeCapability=False - ret = self.cam.devicemgmt.GetServices(params) + params.IncludeCapability = False + self.cam.devicemgmt.GetServices(params) def test_GetServiceCapabilities(self): - '''Returns the capabilities of the devce service.''' - ret = self.cam.devicemgmt.GetServiceCapabilities() - ret.Network.IPFilter + """Returns the capabilities of the device service.""" + self.cam.devicemgmt.GetServiceCapabilities() def test_GetCapabilities(self): - ''' - Probides a backward compatible interface for the base capabilities. - ''' - categorys = ['PTZ', 'Media', 'Imaging', - 'Device', 'Analytics', 'Events'] - ret = self.cam.devicemgmt.GetCapabilities() - for category in categorys: - ret = self.cam.devicemgmt.GetCapabilities({'Category': category}) + """ + Provides a backward compatible interface for the base capabilities. + """ + categories = ['PTZ', 'Media', 'Imaging', + 'Device', 'Analytics', 'Events'] + self.cam.devicemgmt.GetCapabilities() + for category in categories: + self.cam.devicemgmt.GetCapabilities({'Category': category}) with self.assertRaises(ONVIFError): self.cam.devicemgmt.GetCapabilities({'Category': 'unknown'}) # *************** Test Network ********************************* def test_GetHostname(self): - ''' Get the hostname from a device ''' + """ Get the hostname from a device """ self.cam.devicemgmt.GetHostname() def test_SetHostname(self): - ''' + """ Set the hostname on a device - A device shall accept strings formated according to + A device shall accept strings formatted according to RFC 1123 section 2.1 or alternatively to RFC 952, other string shall be considered as invalid strings - ''' + """ pre_host_name = self.cam.devicemgmt.GetHostname() - self.cam.devicemgmt.SetHostname({'Name':'testHostName'}) + self.cam.devicemgmt.SetHostname({'Name': 'testHostName'}) self.assertEqual(self.cam.devicemgmt.GetHostname().Name, 'testHostName') - - self.cam.devicemgmt.SetHostname({'Name':pre_host_name.Name}) + self.cam.devicemgmt.SetHostname({'Name': pre_host_name.Name}) def test_SetHostnameFromDHCP(self): - ''' Controls whether the hostname shall be retrieved from DHCP ''' + """ Controls whether the hostname shall be retrieved from DHCP """ ret = self.cam.devicemgmt.SetHostnameFromDHCP(dict(FromDHCP=False)) self.assertTrue(isinstance(ret, bool)) def test_GetDNS(self): - ''' Gets the DNS setting from a device ''' + """ Gets the DNS setting from a device """ ret = self.cam.devicemgmt.GetDNS() self.assertTrue(hasattr(ret, 'FromDHCP')) if not ret.FromDHCP and len(ret.DNSManual) > 0: @@ -87,29 +87,31 @@ def test_GetDNS(self): log(ret.DNSManual[0].IPv4Address) def test_SetDNS(self): - ''' Set the DNS settings on a device ''' - ret = self.cam.devicemgmt.SetDNS(dict(FromDHCP=False)) + """ Set the DNS settings on a device """ + self.cam.devicemgmt.SetDNS(dict(FromDHCP=False)) def test_GetNTP(self): - ''' Get the NTP settings from a device ''' + """ Get the NTP settings from a device """ ret = self.cam.devicemgmt.GetNTP() - if ret.FromDHCP == False: + if not ret.FromDHCP: self.assertTrue(hasattr(ret, 'NTPManual')) log(ret.NTPManual) def test_SetNTP(self): - '''Set the NTP setting''' - ret = self.cam.devicemgmt.SetNTP(dict(FromDHCP=False)) + """Set the NTP setting""" + self.cam.devicemgmt.SetNTP(dict(FromDHCP=False)) def test_GetDynamicDNS(self): - '''Get the dynamic DNS setting''' + """Get the dynamic DNS setting""" ret = self.cam.devicemgmt.GetDynamicDNS() log(ret) def test_SetDynamicDNS(self): - ''' Set the dynamic DNS settings on a device ''' - ret = self.cam.devicemgmt.GetDynamicDNS() - ret = self.cam.devicemgmt.SetDynamicDNS({'Type': 'NoUpdate', 'Name':None, 'TTL':None}) + """ Set the dynamic DNS settings on a device """ + self.cam.devicemgmt.GetDynamicDNS() + self.cam.devicemgmt.SetDynamicDNS({'Type': 'NoUpdate', 'Name': None, + 'TTL': None}) + if __name__ == '__main__': unittest.main() From 247c5bc23a53a055d57154f4bdc8b9c26dae70b0 Mon Sep 17 00:00:00 2001 From: James Adams Date: Tue, 19 Feb 2019 12:14:27 -0500 Subject: [PATCH 30/39] various minor cleanups suggested by PyCharm file inspection (PEP8 compliance, unused parameter variables, commented-out code, etc.) --- onvif/client.py | 101 ++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index 409fdc7..92afc8c 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -1,20 +1,22 @@ from __future__ import print_function, division __version__ = '0.0.1' +import datetime as dt +import logging import os.path from threading import Thread, RLock -import logging -logger = logging.getLogger('onvif') -logging.basicConfig(level=logging.INFO) -logging.getLogger('zeep.client').setLevel(logging.CRITICAL) - from zeep.client import Client, CachingClient, Settings from zeep.wsse.username import UsernameToken import zeep.helpers from onvif.exceptions import ONVIFError from onvif.definition import SERVICES -import datetime as dt + +logger = logging.getLogger('onvif') +logging.basicConfig(level=logging.INFO) +logging.getLogger('zeep.client').setLevel(logging.CRITICAL) + + # Ensure methods to raise an ONVIFError Exception # when some thing was wrong def safe_func(func): @@ -22,18 +24,17 @@ def wrapped(*args, **kwargs): try: return func(*args, **kwargs) except Exception as err: - #print('Ouuups: err =', err, ', func =', func, ', args =', args, ', kwargs =', kwargs) raise ONVIFError(err) return wrapped class UsernameDigestTokenDtDiff(UsernameToken): - ''' + """ UsernameDigestToken class, with a time offset parameter that can be adjusted; This allows authentication on cameras without being time synchronized. Please note that using NTP on both end is the recommended solution, this should only be used in "safe" environments. - ''' + """ def __init__(self, user, passw, dt_diff=None, **kwargs): super().__init__(user, passw, **kwargs) self.dt_diff = dt_diff # Date/time difference in datetime.timedelta @@ -42,17 +43,15 @@ def apply(self, envelope, headers): old_created = self.created if self.created is None: self.created = dt.datetime.utcnow() - #print('UsernameDigestTokenDtDiff.created: old = %s (type = %s), dt_diff = %s (type = %s)' % (self.created, type(self.created), self.dt_diff, type(self.dt_diff)), end='') if self.dt_diff is not None: self.created += self.dt_diff - #print(' new = %s' % self.created) result = super().apply(envelope, headers) self.created = old_created return result class ONVIFService(object): - ''' + """ Python Implemention for ONVIF Service. Services List: DeviceMgmt DeviceIO Event AnalyticsDevice Display Imaging Media @@ -80,12 +79,12 @@ class ONVIFService(object): params = device_service.create_type('SetHostname') params.Hostname = 'NewHostName' device_service.SetHostname(params) - ''' + """ @safe_func def __init__(self, xaddr, user, passwd, url, encrypt=True, daemon=False, zeep_client=None, no_cache=False, - portType=None, dt_diff=None, binding_name='', transport=None): + dt_diff=None, binding_name='', transport=None): if not os.path.isfile(url): raise ONVIFError('%s doesn`t exist!' % url) @@ -94,7 +93,6 @@ def __init__(self, xaddr, user, passwd, url, wsse = UsernameDigestTokenDtDiff(user, passwd, dt_diff=dt_diff, use_digest=encrypt) # Create soap client if not zeep_client: - #print(self.url, self.xaddr) ClientType = Client if no_cache else CachingClient settings = Settings() settings.strict = False @@ -139,7 +137,6 @@ def call(params=None, callback=None): try: ret = func(**params) except TypeError: - #print('### func =', func, '### params =', params, '### type(params) =', type(params)) ret = func(params) if callable(callback): callback(ret) @@ -154,13 +151,13 @@ def call(params=None, callback=None): return wrapped def __getattr__(self, name): - ''' + """ Call the real onvif Service operations, See the official wsdl definition for the APIs detail(API name, request parameters, response parameters, parameter types, etc...) - ''' - builtin = name.startswith('__') and name.endswith('__') + """ + builtin = name.startswith('__') and name.endswith('__') if builtin: return self.__dict__[name] else: @@ -168,9 +165,9 @@ def __getattr__(self, name): class ONVIFCamera(object): - ''' - Python Implemention ONVIF compliant device - This class integrates onvif services + """ + Python Implementation of an ONVIF compliant device. + This class integrates ONVIF services adjust_time parameter allows authentication on cameras without being time synchronized. Please note that using NTP on both end is the recommended solution, @@ -183,18 +180,20 @@ class ONVIFCamera(object): >>> media_service = mycam.create_media_service() >>> ptz_service = mycam.create_ptz_service() # Get PTZ Configuration: - >>> mycam.ptz.GetConfiguration() - # Another way: >>> ptz_service.GetConfiguration() - ''' + """ # Class-level variables services_template = {'devicemgmt': None, 'ptz': None, 'media': None, - 'imaging': None, 'events': None, 'analytics': None } + 'imaging': None, 'events': None, 'analytics': None} use_services_template = {'devicemgmt': True, 'ptz': True, 'media': True, - 'imaging': True, 'events': True, 'analytics': True } - def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirname(os.path.dirname(__file__)), "wsdl"), - encrypt=True, daemon=False, no_cache=False, adjust_time=False, transport=None): + 'imaging': True, 'events': True, 'analytics': True} + + def __init__(self, host, port, user, passwd, + wsdl_dir=os.path.join(os.path.dirname(os.path.dirname(__file__)), + "wsdl"), + encrypt=True, daemon=False, no_cache=False, adjust_time=False, + transport=None): os.environ.pop('http_proxy', None) os.environ.pop('https_proxy', None) self.host = host @@ -209,7 +208,7 @@ def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirna self.transport = transport # Active service client container - self.services = { } + self.services = {} self.services_lock = RLock() # Set xaddrs @@ -220,16 +219,16 @@ def __init__(self, host, port ,user, passwd, wsdl_dir=os.path.join(os.path.dirna def update_xaddrs(self): # Establish devicemgmt service first self.dt_diff = None - self.devicemgmt = self.create_devicemgmt_service() - if self.adjust_time : + self.devicemgmt = self.create_devicemgmt_service() + if self.adjust_time: cdate = self.devicemgmt.GetSystemDateAndTime().UTCDateTime - cam_date = dt.datetime(cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second) + cam_date = dt.datetime(cdate.Date.Year, cdate.Date.Month, cdate.Date.Day, + cdate.Time.Hour, cdate.Time.Minute, cdate.Time.Second) self.dt_diff = cam_date - dt.datetime.utcnow() self.devicemgmt.dt_diff = self.dt_diff - #self.devicemgmt.set_wsse() - self.devicemgmt = self.create_devicemgmt_service() + self.devicemgmt = self.create_devicemgmt_service() # Get XAddr of services on the device - self.xaddrs = { } + self.xaddrs = {} capabilities = self.devicemgmt.GetCapabilities({'Category': 'All'}) for name in capabilities: capability = capabilities[name] @@ -243,8 +242,9 @@ def update_xaddrs(self): with self.services_lock: try: self.event = self.create_events_service() - self.xaddrs['http://www.onvif.org/ver10/events/wsdl/PullPointSubscription'] = self.event.CreatePullPointSubscription().SubscriptionReference.Address._value_1 - except: + self.xaddrs['http://www.onvif.org/ver10/events/wsdl/PullPointSubscription'] = \ + self.event.CreatePullPointSubscription().SubscriptionReference.Address._value_1 + except Exception: pass def update_url(self, host=None, port=None): @@ -268,14 +268,13 @@ def update_url(self, host=None, port=None): self.services[sname].ws_client.set_options(location=xaddr) def get_service(self, name, create=True): - service = None service = getattr(self, name.lower(), None) if not service and create: return getattr(self, 'create_%s_service' % name.lower())() return service def get_definition(self, name, portType=None): - '''Returns xaddr and wsdl of specified service''' + """Returns xaddr and wsdl of specified service""" # Check if the service is supported if name not in SERVICES: raise ONVIFError('Unknown service %s' % name) @@ -301,7 +300,7 @@ def get_definition(self, name, portType=None): # Get other XAddr xaddr = self.xaddrs.get(ns) if not xaddr: - raise ONVIFError('Device doesn`t support service: %s' % name) + raise ONVIFError("Device doesn't support service: %s" % name) return xaddr, wsdlpath, binding_name @@ -310,11 +309,11 @@ def create_onvif_service(self, name, portType=None): Create ONVIF service client. :param name: service name, should be present as a key within - the `SERVICES` dictionary declared in onvif.definition + the `SERVICES` dictionary declared within the `onvif.definition` module :param portType: :return: """ - '''Create ONVIF service client''' + """Create ONVIF service client""" name = name.lower() xaddr, wsdl_file, binding_name = self.get_definition(name, portType) @@ -336,7 +335,7 @@ def create_onvif_service(self, name, portType=None): return service - def create_devicemgmt_service(self, from_template=True): + def create_devicemgmt_service(self): # The entry point for devicemgmt service is fixed. return self.create_onvif_service('devicemgmt') @@ -358,23 +357,23 @@ def create_events_service(self): def create_analytics_service(self): return self.create_onvif_service('analytics') - def create_recording_service(self, from_template=True): + def create_recording_service(self): return self.create_onvif_service('recording') - def create_search_service(self, from_template=True): + def create_search_service(self): return self.create_onvif_service('search') - def create_replay_service(self, from_template=True): + def create_replay_service(self): return self.create_onvif_service('replay') - def create_pullpoint_service(self, from_template=True): + def create_pullpoint_service(self): return self.create_onvif_service('pullpoint', portType='PullPointSubscription') - def create_receiver_service(self, from_template=True): + def create_receiver_service(self): return self.create_onvif_service('receiver') - def create_notification_service(self, from_template=True): + def create_notification_service(self): return self.create_onvif_service('notification') - def create_subscription_service(self, from_template=True): + def create_subscription_service(self): return self.create_onvif_service('subscription') From f737db95ed1a1e9636a21bb6153b7798ddb86b0b Mon Sep 17 00:00:00 2001 From: James Adams Date: Tue, 19 Feb 2019 14:07:34 -0500 Subject: [PATCH 31/39] removed unused parameter variable `portType` from the `ONVIFService` class constructor --- onvif/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/onvif/client.py b/onvif/client.py index 92afc8c..6404aeb 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -322,7 +322,6 @@ def create_onvif_service(self, name, portType=None): service = ONVIFService(xaddr, self.user, self.passwd, wsdl_file, self.encrypt, self.daemon, no_cache=self.no_cache, - portType=portType, dt_diff=self.dt_diff, binding_name=binding_name, transport=self.transport) From 8aba823343711e392c878a60a2cc8480df3a073a Mon Sep 17 00:00:00 2001 From: Geert van Horrik Date: Fri, 12 Apr 2019 22:33:57 +0200 Subject: [PATCH 32/39] Allow services to be created with custom transport --- onvif/client.py | 57 +++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index 6bc867f..4d117a4 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -305,20 +305,23 @@ def get_definition(self, name, portType=None): return xaddr, wsdlpath, binding_name - def create_onvif_service(self, name, from_template=True, portType=None): + def create_onvif_service(self, name, from_template=True, portType=None, transport=None): '''Create ONVIF service client''' name = name.lower() xaddr, wsdl_file, binding_name = self.get_definition(name, portType) with self.services_lock: + if not transport: + transport = self.transport + service = ONVIFService(xaddr, self.user, self.passwd, wsdl_file, self.encrypt, self.daemon, no_cache=self.no_cache, portType=portType, dt_diff=self.dt_diff, binding_name=binding_name, - transport=self.transport) + transport=transport) self.services[name] = service @@ -328,39 +331,41 @@ def create_onvif_service(self, name, from_template=True, portType=None): return service - def create_devicemgmt_service(self, from_template=True): + def create_devicemgmt_service(self, from_template=True, transport=None): # The entry point for devicemgmt service is fixed. - return self.create_onvif_service('devicemgmt', from_template) + return self.create_onvif_service('devicemgmt', from_template, transport=transport) - def create_media_service(self, from_template=True): - return self.create_onvif_service('media', from_template) + def create_media_service(self, from_template=True, transport=None): + return self.create_onvif_service('media', from_template, transport=transport) - def create_ptz_service(self, from_template=True): - return self.create_onvif_service('ptz', from_template) + def create_ptz_service(self, from_template=True, transport=None): + return self.create_onvif_service('ptz', from_template, transport=transport) - def create_imaging_service(self, from_template=True): - return self.create_onvif_service('imaging', from_template) + def create_imaging_service(self, from_template=True, transport=None): + return self.create_onvif_service('imaging', from_template, transport=transport) - def create_deviceio_service(self, from_template=True): - return self.create_onvif_service('deviceio', from_template) + def create_deviceio_service(self, from_template=True, transport=None): + return self.create_onvif_service('deviceio', from_template, transport=transport) - def create_events_service(self, from_template=True): - return self.create_onvif_service('events', from_template) + def create_events_service(self, from_template=True, transport=None): + return self.create_onvif_service('events', from_template, transport=transport) - def create_analytics_service(self, from_template=True): - return self.create_onvif_service('analytics', from_template) + def create_analytics_service(self, from_template=True, transport=None): + return self.create_onvif_service('analytics', from_template, transport=transport) - def create_recording_service(self, from_template=True): - return self.create_onvif_service('recording', from_template) + def create_recording_service(self, from_template=True, transport=None): + return self.create_onvif_service('recording', from_template, transport=transport) - def create_search_service(self, from_template=True): - return self.create_onvif_service('search', from_template) + def create_search_service(self, from_template=True, transport=None): + return self.create_onvif_service('search', from_template, transport=transport) - def create_replay_service(self, from_template=True): - return self.create_onvif_service('replay', from_template) + def create_replay_service(self, from_template=True, transport=None): + return self.create_onvif_service('replay', from_template, transport=transport) - def create_pullpoint_service(self, from_template=True): - return self.create_onvif_service('pullpoint', from_template, portType='PullPointSubscription') + def create_pullpoint_service(self, from_template=True, transport=None): + return self.create_onvif_service('pullpoint', from_template, + portType='PullPointSubscription', + transport=transport) - def create_receiver_service(self, from_template=True): - return self.create_onvif_service('receiver', from_template) + def create_receiver_service(self, from_template=True, transport=None): + return self.create_onvif_service('receiver', from_template, transport=transport) From 88b0e29db80e24ad715dfe964d4069041054d36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20H=E1=BB=93ng=20Qu=C3=A2n?= Date: Fri, 10 May 2019 15:59:59 +0700 Subject: [PATCH 33/39] Fix: Incorrect Onvif namespace prefix The bug is from Zeep: https://github.com/mvantellingen/python-zeep/issues/870 --- onvif/client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/onvif/client.py b/onvif/client.py index 5721a53..7f22601 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -98,6 +98,13 @@ def __init__(self, xaddr, user, passwd, url, settings.strict = False settings.xml_huge_tree = True self.zeep_client = ClientType(wsdl=url, wsse=wsse, transport=transport, settings=settings) + self.zeep_client.set_ns_prefix('tds', 'http://www.onvif.org/ver10/device/wsdl') + self.zeep_client.set_ns_prefix('tev', 'http://www.onvif.org/ver10/events/wsdl') + self.zeep_client.set_ns_prefix('timg', 'http://www.onvif.org/ver20/imaging/wsdl') + self.zeep_client.set_ns_prefix('tmd', 'http://www.onvif.org/ver10/deviceIO/wsdl') + self.zeep_client.set_ns_prefix('tptz', 'http://www.onvif.org/ver20/ptz/wsdl') + self.zeep_client.set_ns_prefix('ttr', 'http://www.onvif.org/ver10/media/wsdl') + self.zeep_client.set_ns_prefix('ter', 'http://www.onvif.org/ver10/error') else: self.zeep_client = zeep_client self.ws_client = self.zeep_client.create_service(binding_name, self.xaddr) From 5fae485147a2ef01cc177c3c9987560d7c661cc3 Mon Sep 17 00:00:00 2001 From: davidea Date: Tue, 11 Jun 2019 18:40:55 +0200 Subject: [PATCH 34/39] add the ability to rewrite the address and the port if the camera is in a router with nat --- onvif/client.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/onvif/client.py b/onvif/client.py index 5721a53..b6b7a68 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -231,6 +231,18 @@ def update_xaddrs(self): self.xaddrs = {} capabilities = self.devicemgmt.GetCapabilities({'Category': 'All'}) for name in capabilities: + try: + retrived_address=capabilities[name].XAddr + right=retrived_address.split("//")[1] + retrived_url=right.split("/")[0] + ip_address=retrived_url.split(":")[0] + port_address = retrived_url.split(":")[1] + if (self.host != ip_address or self.port != port_address): + remaining=right.split("/")[1] + new_address="http://"+self.host+":"+str(self.port)+"/"+right.split("/")[1]+"/"+right.split("/")[2] + capabilities[name].XAddr=new_address + except: + pass capability = capabilities[name] try: if name.lower() in SERVICES and capability is not None: From 1ab5e9dfade241502ffe559fc9b531334266f7d0 Mon Sep 17 00:00:00 2001 From: "Kwang Myung, Jeon" Date: Tue, 7 Apr 2020 19:52:52 +0900 Subject: [PATCH 35/39] revised by kmjeon --- examples/continuous_move.py | 38 ++++++++++++++++++++++--------------- examples/events.py | 2 +- examples/streaming.py | 2 +- tests/test.py | 6 +++--- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/examples/continuous_move.py b/examples/continuous_move.py index 1b9a003..2e0217b 100644 --- a/examples/continuous_move.py +++ b/examples/continuous_move.py @@ -1,10 +1,10 @@ -import asyncio, sys +import asyncio, sys, os from onvif import ONVIFCamera -IP="192.168.0.100" # Camera IP address -PORT=10080 # Port +IP="192.168.1.64" # Camera IP address +PORT=80 # Port USER="admin" # Username -PASS="password" # Password +PASS="intflow3121" # Password XMAX = 1 @@ -94,6 +94,8 @@ def setup_move(): moverequest.ProfileToken = media_profile.token if moverequest.Velocity is None: moverequest.Velocity = ptz.GetStatus({'ProfileToken': media_profile.token}).Position + moverequest.Velocity.PanTilt.space = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].URI + moverequest.Velocity.Zoom.space = ptz_configuration_options.Spaces.ContinuousZoomVelocitySpace[0].URI # Get range of pan and tilt @@ -143,14 +145,20 @@ def readin(): if __name__ == '__main__': setup_move() - loop = asyncio.get_event_loop() - try: - loop.add_reader(sys.stdin,readin) - print("Use Ctrl-C to quit") - print("Your command: ", end='',flush=True) - loop.run_forever() - except: - pass - finally: - loop.remove_reader(sys.stdin) - loop.close() + move_upleft(ptz,moverequest) + #if os.name == 'nt': + # loop = asyncio.ProactorEventLoop() # for subprocess' pipes on Windows + # asyncio.set_event_loop(loop) + #else: + # loop = asyncio.get_event_loop() + + #try: + # loop.add_reader(sys.stdin,readin) + # print("Use Ctrl-C to quit") + # print("Your command: ", end='',flush=True) + # loop.run_forever() + #except: + # pass + #finally: + # loop.remove_reader(sys.stdin) + # loop.close() diff --git a/examples/events.py b/examples/events.py index 4ad93a8..ac6bf52 100644 --- a/examples/events.py +++ b/examples/events.py @@ -4,7 +4,7 @@ if __name__ == '__main__': - mycam = ONVIFCamera('192.168.1.10', 8899, 'admin', 'admin') #, no_cache=True) + mycam = ONVIFCamera('192.168.1.64', 80, 'admin', 'intflow3121') #, no_cache=True) event_service = mycam.create_events_service() print(event_service.GetEventProperties()) diff --git a/examples/streaming.py b/examples/streaming.py index 1f7caa0..7ab402b 100644 --- a/examples/streaming.py +++ b/examples/streaming.py @@ -9,7 +9,7 @@ def media_profile_configuration(): ''' # Create the media service - mycam = ONVIFCamera('192.168.0.112', 80, 'admin', '12345') + mycam = ONVIFCamera('192.168.1.64', 80, 'admin', 'intflow3121') media_service = mycam.create_media_service() profiles = media_service.GetProfiles() diff --git a/tests/test.py b/tests/test.py index bd63dc3..38f2b5a 100644 --- a/tests/test.py +++ b/tests/test.py @@ -5,10 +5,10 @@ from onvif import ONVIFCamera, ONVIFError -CAM_HOST = '172.20.9.84' +CAM_HOST = '192.168.1.64' CAM_PORT = 80 -CAM_USER = 'root' -CAM_PASS = 'password' +CAM_USER = 'admin' +CAM_PASS = 'intflow3121' DEBUG = False From 7a26ac13797b6e2de6164c6f77e4213ca5279bd6 Mon Sep 17 00:00:00 2001 From: Ahn Date: Fri, 17 Apr 2020 18:19:50 +0900 Subject: [PATCH 36/39] AbsoluteMove --- examples/AbsoluteMove.py | 270 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 examples/AbsoluteMove.py diff --git a/examples/AbsoluteMove.py b/examples/AbsoluteMove.py new file mode 100644 index 0000000..791071f --- /dev/null +++ b/examples/AbsoluteMove.py @@ -0,0 +1,270 @@ +import asyncio, sys, os +from onvif import ONVIFCamera +import time + +IP="192.168.1.64" # Camera IP address +PORT=80 # Port +USER="admin" # Username +PASS="intflow3121" # Password + + +XMAX = 1 +XMIN = -1 +XNOW = 0.5 +YMAX = 1 +YMIN = -1 +YNOW = 0.5 +Move = 0.1 +Velocity = 1 +Zoom = 0 +positionrequest = None +ptz = None +active = False +ptz_configuration_options = None +media_profile = None + +def do_move(ptz, request): + + global active + if active: + ptz.Stop({'ProfileToken': request.ProfileToken}) + active = True + ptz.AbsoluteMove(request) + +def move_up(ptz, request): + + if YNOW - Move <= -1: + request.Position.PanTilt.y = YNOW + else: + request.Position.PanTilt.y = YNOW - Move + + + do_move(ptz, request) + +def move_down(ptz, request): + + if YNOW + Move >= 1: + request.Position.PanTilt.y = YNOW + else: + request.Position.PanTilt.y = YNOW + Move + + + do_move(ptz, request) + +def move_right(ptz, request): + + if XNOW - Move >= -0.99: + request.Position.PanTilt.x = XNOW - Move + elif abs(XNOW + Move) >= 0.0: + request.Position.PanTilt.x = abs(XNOW) - Move + elif abs(XNOW) <= 0.01: + request.Position.PanTilt.x = XNOW + + request.Position.PanTilt.y = YNOW + do_move(ptz, request) + +def move_left(ptz, request): + + if XNOW + Move <= 1.0: + request.Position.PanTilt.x = XNOW + Move + elif XNOW <= 1.0 and XNOW > 0.99: + request.Position.PanTilt.x = -XNOW + elif XNOW < 0: + request.Position.PanTilt.x = XNOW + Move + elif XNOW <= -0.105556 and XNOW > -0.11: + request.Position.PanTilt.x = XNOW + + request.Position.PanTilt.y = YNOW + do_move(ptz, request) + + +def move_upleft(ptz, request): + + if YNOW == -1: + request.Position.PanTilt.y = YNOW + else: + request.Position.PanTilt.y = YNOW - Move + if XNOW + Move <= 1.0: + request.Position.PanTilt.x = XNOW + Move + elif XNOW <= 1.0 and XNOW > 0.99: + request.Position.PanTilt.x = -XNOW + elif XNOW < 0: + request.Position.PanTilt.x = XNOW + Move + elif XNOW <= -0.105556 and XNOW > -0.11: + request.Position.PanTilt.x = XNOW + do_move(ptz, request) + +def move_upright(ptz, request): + + if YNOW == -1: + request.Position.PanTilt.y = YNOW + else: + request.Position.PanTilt.y = YNOW - Move + + if XNOW - Move >= -0.99: + request.Position.PanTilt.x = XNOW - Move + elif abs(XNOW + Move) >= 0.0: + request.Position.PanTilt.x = abs(XNOW) - Move + elif abs(XNOW) <= 0.01: + request.Position.PanTilt.x = XNOW + + do_move(ptz, request) + +def move_downleft(ptz, request): + + if YNOW - Move == 1: + request.Position.PanTilt.y = YNOW + else: + request.Position.PanTilt.y = YNOW - Move + + if XNOW + Move <= 1.0: + request.Position.PanTilt.x = XNOW + Move + elif XNOW <= 1.0 and XNOW > 0.99: + request.Position.PanTilt.x = -XNOW + elif XNOW < 0: + request.Position.PanTilt.x = XNOW + Move + elif XNOW <= -0.105556 and XNOW > -0.11: + request.Position.PanTilt.x = XNOW + + do_move(ptz, request) + +def move_downright(ptz, request): + + if YNOW == -1: + request.Position.PanTilt.y = YNOW + else: + request.Position.PanTilt.y = YNOW - Move + + if XNOW - Move >= -0.99: + request.Position.PanTilt.x = XNOW - Move + elif abs(XNOW + Move) >= 0.0: + request.Position.PanTilt.x = abs(XNOW) - Move + elif abs(XNOW) <= 0.01: + request.Position.PanTilt.x = XNOW + + do_move(ptz, request) + +def Zoom_in(ptz,request): + + if Zoom + Move >= 1.0: + request.Position.Zoom = 1.0 + else: + request.Position.Zoom = Zoom + Move + do_move(ptz, request) + +def Zoom_out(ptz,request): + + if Zoom - Move <= 0.0: + request.Position.Zoom = 0.0 + else: + request.Position.Zoom = Zoom - Move + do_move(ptz,request) + +def setup_move(): + mycam = ONVIFCamera(IP, PORT, USER, PASS) + # Create media service object + media = mycam.create_media_service() + + # Create ptz service object + global ptz , ptz_configuration_options, media_profile + ptz = mycam.create_ptz_service() + + # Get target profile + media_profile = media.GetProfiles()[0] + + + request = ptz.create_type('GetConfigurationOptions') + request.ConfigurationToken = media_profile.PTZConfiguration.token + ptz_configuration_options = ptz.GetConfigurationOptions(request) + + request_configuration = ptz.create_type('GetConfiguration') + request_configuration.PTZConfigurationToken = media_profile.PTZConfiguration.token + ptz_configuration = ptz.GetConfiguration(request_configuration) + + request_setconfiguration = ptz.create_type('SetConfiguration') + request_setconfiguration.PTZConfiguration = ptz_configuration + + global positionrequest + + positionrequest = ptz.create_type('AbsoluteMove') + positionrequest.ProfileToken = media_profile.token + + if positionrequest.Position is None : + positionrequest.Position = ptz.GetStatus({'ProfileToken': media_profile.token}).Position + positionrequest.Position.PanTilt.space = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].URI + positionrequest.Position.Zoom.space = ptz_configuration_options.Spaces.AbsoluteZoomPositionSpace[0].URI + if positionrequest.Speed is None : + positionrequest.Speed = ptz.GetStatus({'ProfileToken': media_profile.token}).Position + positionrequest.Speed.PanTilt.space = ptz_configuration_options.Spaces.PanTiltSpeedSpace[0].URI + +def Get_Status(): + # Get range of pan and tilt + global XMAX, XMIN, YMAX, YMIN, XNOW, YNOW, Velocity, Zoom + XMAX = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].XRange.Max + XMIN = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].XRange.Min + YMAX = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].YRange.Max + YMIN = ptz_configuration_options.Spaces.AbsolutePanTiltPositionSpace[0].YRange.Min + XNOW = ptz.GetStatus({'ProfileToken': media_profile.token}).Position.PanTilt.x + YNOW = ptz.GetStatus({'ProfileToken': media_profile.token}).Position.PanTilt.y + Velocity = ptz_configuration_options.Spaces.PanTiltSpeedSpace[0].XRange.Max + Zoom = ptz.GetStatus({'ProfileToken': media_profile.token}).Position.Zoom.x + + +def readin(): + """Reading from stdin and displaying menu""" + global positionrequest, ptz + + selection = sys.stdin.readline().strip("\n") + lov=[ x for x in selection.split(" ") if x != ""] + if lov: + + if lov[0].lower() in ["u","up"]: + move_up(ptz,positionrequest) + elif lov[0].lower() in ["d","do","dow","down"]: + move_down(ptz,positionrequest) + elif lov[0].lower() in ["l","le","lef","left"]: + move_left(ptz,positionrequest) + elif lov[0].lower() in ["l","le","lef","left"]: + move_left(ptz,positionrequest) + elif lov[0].lower() in ["r","ri","rig","righ","right"]: + move_right(ptz,positionrequest) + elif lov[0].lower() in ["ul"]: + move_upleft(ptz,positionrequest) + elif lov[0].lower() in ["ur"]: + move_upright(ptz,positionrequest) + elif lov[0].lower() in ["dl"]: + move_downleft(ptz,positionrequest) + elif lov[0].lower() in ["dr"]: + move_downright(ptz,positionrequest) + elif lov[0].lower() in ["s","st","sto","stop"]: + ptz.Stop({'ProfileToken': positionrequest.ProfileToken}) + active = False + else: + 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'") + + print("") + print("Your command: ", end='',flush=True) + +# Test Define +# def move(ptz, request): + +# request.Position.PanTilt.y = -1 +# request.Position.PanTilt.x = 0 + +# do_move(ptz,request) + +if __name__ == '__main__': + + setup_move() + # Get_Status() + # Zoom_out(ptz,positionrequest) + # Get_Status() + # move(ptz,positionrequest) + while True: + if active == True: + time.sleep(1) + active = False + else: + Get_Status() + + move_up(ptz, positionrequest) \ No newline at end of file From 7908158b2038d2d89103b1b557fa7d1540fbb6b4 Mon Sep 17 00:00:00 2001 From: Sergey Spivak Date: Fri, 10 Jun 2022 22:01:14 +0400 Subject: [PATCH 37/39] Prevent hangup of 50X10_32M cams on event pullpoint create --- onvif/client.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/onvif/client.py b/onvif/client.py index 5721a53..23191b5 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -192,7 +192,7 @@ class ONVIFCamera(object): def __init__(self, host, port, user, passwd, wsdl_dir=os.path.join(os.path.dirname(os.path.dirname(__file__)), "wsdl"), - encrypt=True, daemon=False, no_cache=False, adjust_time=False, + encrypt=True, daemon=False, no_cache=False, adjust_time=False, event_pullpoint=True, transport=None): os.environ.pop('http_proxy', None) os.environ.pop('https_proxy', None) @@ -205,6 +205,7 @@ def __init__(self, host, port, user, passwd, self.daemon = daemon self.no_cache = no_cache self.adjust_time = adjust_time + self.event_pullpoint = event_pullpoint self.transport = transport # Active service client container @@ -242,8 +243,9 @@ def update_xaddrs(self): with self.services_lock: try: self.event = self.create_events_service() - self.xaddrs['http://www.onvif.org/ver10/events/wsdl/PullPointSubscription'] = \ - self.event.CreatePullPointSubscription().SubscriptionReference.Address._value_1 + if self.event_pullpoint: + self.xaddrs['http://www.onvif.org/ver10/events/wsdl/PullPointSubscription'] = \ + self.event.CreatePullPointSubscription().SubscriptionReference.Address._value_1 except Exception: pass From 95e03d671d99019c98ac13edba57b2120120584f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B3=BD=E9=BE=99?= Date: Fri, 18 Aug 2023 09:36:17 +0800 Subject: [PATCH 38/39] [fix] Blank Topic title in PullMessages https://github.com/FalkTannhaeuser/python-onvif-zeep/issues/69 --- wsdl/b-2.xsd | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/wsdl/b-2.xsd b/wsdl/b-2.xsd index 1e0d47e..997bb8f 100644 --- a/wsdl/b-2.xsd +++ b/wsdl/b-2.xsd @@ -47,11 +47,12 @@ This document and the information contained herein is provided on an "AS IS" bas - - - - - + + + + + + From 83cf9ab52daaad8c901bfb7bb83677f18c0af8e3 Mon Sep 17 00:00:00 2001 From: Felipe Moreno Date: Fri, 25 Jul 2025 01:25:06 -0400 Subject: [PATCH 39/39] wsdl installation path compatible with virtual environments --- setup.py | 69 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/setup.py b/setup.py index b468675..6d69919 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,10 @@ +"""Setup script for the onvif_zeep package.""" import os +import sysconfig +import shutil from setuptools import setup, find_packages -import sys +from setuptools.command.install import install + here = os.path.abspath(os.path.dirname(__file__)) version_path = os.path.join(here, 'onvif/version.txt') @@ -28,29 +32,44 @@ "Programming Language :: Python :: 3.5", ] -wsdl_files = [os.path.join('wsdl', item) for item in os.listdir('wsdl')] -wsdl_dst_dir = 'Lib/site-packages/wsdl' if sys.platform == 'win32' else \ - 'lib/python%d.%d/site-packages/wsdl' % (sys.version_info.major, - sys.version_info.minor) + +class CustomInstallCommand(install): + """Custom install command to handle WSDL files.""" + def run(self): + # Run regular installation first + install.run(self) + + # Now manually copy the wsdl files to site-packages/wsdl + wsdl_src_dir = 'wsdl' + wsdl_dst_dir = os.path.join(sysconfig.get_paths()['purelib'], 'wsdl') + + os.makedirs(wsdl_dst_dir, exist_ok=True) + + for file in os.listdir(wsdl_src_dir): + src_path = os.path.join(wsdl_src_dir, file) + dst_path = os.path.join(wsdl_dst_dir, file) + shutil.copyfile(src_path, dst_path) + setup( - name='onvif_zeep', - version=version, - description='Python Client for ONVIF Camera', - long_description=open('README.rst', 'r').read(), - author='Cherish Chen', - author_email='sinchb128@gmail.com', - maintainer='sinchb', - maintainer_email='sinchb128@gmail.com', - license='MIT', - keywords=['ONVIF', 'Camera', 'IPC'], - url='http://github.com/quatanium/python-onvif', - zip_safe=False, - packages=find_packages(exclude=['docs', 'examples', 'tests']), - install_requires=requires, - include_package_data=True, - data_files=[(wsdl_dst_dir, wsdl_files)], - entry_points={ - 'console_scripts': ['onvif-cli = onvif.cli:main'] - } - ) + name='onvif_zeep', + version=version, + description='Python Client for ONVIF Camera', + long_description=open('README.rst', 'r').read(), + author='Cherish Chen', + author_email='sinchb128@gmail.com', + maintainer='sinchb', + maintainer_email='sinchb128@gmail.com', + license='MIT', + keywords=['ONVIF', 'Camera', 'IPC'], + url='http://github.com/quatanium/python-onvif', + zip_safe=False, + packages=find_packages(exclude=['docs', 'examples', 'tests']), + install_requires=requires, + entry_points={ + 'console_scripts': ['onvif-cli = onvif.cli:main'] + }, + cmdclass={ + 'install': CustomInstallCommand, + }, +)