Commit 5318bda8 authored by SCHUNK-Osswald's avatar SCHUNK-Osswald Committed by ORD

Added command line support for OPC-UA method calls according to #321 (#325)

* - added tools/uacall script

* As suggested by oroulet corrected misunderstandings in signature of
Node.call_method(). No parent needed, just use self.

* Old folks, knowing Python2.x only, keep forgetting that print should
have been a function in the first place...
parent 0141aca8
...@@ -18,3 +18,6 @@ dist ...@@ -18,3 +18,6 @@ dist
newdocs newdocs
examples/history.db examples/history.db
*.sql *.sql
/.project
/.pydevproject
/.settings/
...@@ -13,7 +13,7 @@ OPC UA binary protocol implementation is quasi complete and has been tested agai ...@@ -13,7 +13,7 @@ OPC UA binary protocol implementation is quasi complete and has been tested agai
Most low level code is autogenerated from xml specification, thus adding missing functionnality to client or server is often trivial. Most low level code is autogenerated from xml specification, thus adding missing functionnality to client or server is often trivial.
Using Python > 3.4 the dependencies are cryptography and dateutil. If using python 2.7 or pypy < 3 you also need to install enum34, trollius(asyncio), and futures(concurrent.futures), with pip for example. Using Python > 3.4 the dependencies are cryptography and dateutil. If using python 2.7 or pypy < 3 you also need to install enum34, trollius(asyncio), and futures(concurrent.futures), with pip for example.
coveryage.py reports a test coverage of over 90% of code, most of non-tested code is autogenerate code that is not used yet. coveryage.py reports a test coverage of over 90% of code, most of non-tested code is autogenerate code that is not used yet.
...@@ -32,6 +32,7 @@ A set of command line tools also available: https://github.com/FreeOpcUa/python- ...@@ -32,6 +32,7 @@ A set of command line tools also available: https://github.com/FreeOpcUa/python-
* uahistoryread * uahistoryread
* uaread (read attribute of a node) * uaread (read attribute of a node)
* uawrite (write attribute of a node) * uawrite (write attribute of a node)
* uacall (call method of a node)
* uasubscribe (subscribe to a node and print datachange events) * uasubscribe (subscribe to a node and print datachange events)
* uaclient (connect to server and start python shell) * uaclient (connect to server and start python shell)
* uaserver (starts a demo OPC UA server) * uaserver (starts a demo OPC UA server)
...@@ -52,11 +53,11 @@ Client: what works: ...@@ -52,11 +53,11 @@ Client: what works:
* history read * history read
* login with certificate * login with certificate
* communication encryption * communication encryption
* removing nodes * removing nodes
Tested servers: freeopcua C++, freeopcua Python, prosys, kepware, beckoff Tested servers: freeopcua C++, freeopcua Python, prosys, kepware, beckoff
Client: what is not implemented yet Client: what is not implemented yet
* localized text feature * localized text feature
* adding some missing modify methods * adding some missing modify methods
* XML protocol * XML protocol
...@@ -74,7 +75,7 @@ Server: what works: ...@@ -74,7 +75,7 @@ Server: what works:
* basic user implementation (one existing user called admin, which can be disabled, all others are read only) * basic user implementation (one existing user called admin, which can be disabled, all others are read only)
* encryption * encryption
* certificate handling * certificate handling
* removing nodes * removing nodes
* history support for data change and events * history support for data change and events
Tested clients: freeopcua C++, freeopcua Python, uaexpert, prosys, quickopc Tested clients: freeopcua C++, freeopcua Python, uaexpert, prosys, quickopc
...@@ -101,7 +102,7 @@ All protocol code is under opcua directory ...@@ -101,7 +102,7 @@ All protocol code is under opcua directory
- opcua/common contains high level objects and methods used both in server and client - opcua/common contains high level objects and methods used both in server and client
- opcua/client contains client specific code - opcua/client contains client specific code
- opcua/server contains server specific code - opcua/server contains server specific code
- opcua/utils contains some utilities function and classes - opcua/utils contains some utilities function and classes
- opcua/tools contains code for command lines tools - opcua/tools contains code for command lines tools
- schemas contains the XML and text files from specification and the python scripts used to autogenerate code - schemas contains the XML and text files from specification and the python scripts used to autogenerate code
- tests contains tests - tests contains tests
...@@ -115,7 +116,7 @@ python tests.py ...@@ -115,7 +116,7 @@ python tests.py
## Coverage ## Coverage
coverage run tests.py coverage run tests.py
coverage html coverage html
firefox htmlcov/index.html firefox htmlcov/index.html
...@@ -5,7 +5,7 @@ and browse address space ...@@ -5,7 +5,7 @@ and browse address space
from opcua import ua from opcua import ua
from opcua.common import events from opcua.common import events
import opcua.common import opcua.common
class Node(object): class Node(object):
...@@ -525,30 +525,30 @@ class Node(object): ...@@ -525,30 +525,30 @@ class Node(object):
def add_folder(self, nodeid, bname): def add_folder(self, nodeid, bname):
return opcua.common.manage_nodes.create_folder(self, nodeid, bname) return opcua.common.manage_nodes.create_folder(self, nodeid, bname)
def add_object(self, nodeid, bname, objecttype=None): def add_object(self, nodeid, bname, objecttype=None):
return opcua.common.manage_nodes.create_object(self, nodeid, bname, objecttype) return opcua.common.manage_nodes.create_object(self, nodeid, bname, objecttype)
def add_variable(self, nodeid, bname, val, varianttype=None, datatype=None): def add_variable(self, nodeid, bname, val, varianttype=None, datatype=None):
return opcua.common.manage_nodes.create_variable(self, nodeid, bname, val, varianttype, datatype) return opcua.common.manage_nodes.create_variable(self, nodeid, bname, val, varianttype, datatype)
def add_object_type(self, nodeid, bname): def add_object_type(self, nodeid, bname):
return opcua.common.manage_nodes.create_object_type(self, nodeid, bname) return opcua.common.manage_nodes.create_object_type(self, nodeid, bname)
def add_variable_type(self, nodeid, bname, datatype): def add_variable_type(self, nodeid, bname, datatype):
return opcua.common.manage_nodes.create_variable_type(self, nodeid, bname, datatype) return opcua.common.manage_nodes.create_variable_type(self, nodeid, bname, datatype)
def add_data_type(self, nodeid, bname, description=None): def add_data_type(self, nodeid, bname, description=None):
return opcua.common.manage_nodes.create_data_type(self, nodeid, bname, description=None) return opcua.common.manage_nodes.create_data_type(self, nodeid, bname, description=None)
def add_property(self, nodeid, bname, val, varianttype=None, datatype=None): def add_property(self, nodeid, bname, val, varianttype=None, datatype=None):
return opcua.common.manage_nodes.create_property(self, nodeid, bname, val, varianttype, datatype) return opcua.common.manage_nodes.create_property(self, nodeid, bname, val, varianttype, datatype)
def add_method(self, *args): def add_method(self, *args):
return opcua.common.manage_nodes.create_method(self, *args) return opcua.common.manage_nodes.create_method(self, *args)
def add_reference_type(self, parent, nodeid, bname): def add_reference_type(self, parent, nodeid, bname):
return opcua.common.manage_nodes.create_reference_type(parent, nodeid, bname) return opcua.common.manage_nodes.create_reference_type(parent, nodeid, bname)
def call_method(parent, methodid, *args): def call_method(self, methodid, *args):
return opcua.common.methods.call_method(parent, methodid, *args) return opcua.common.methods.call_method(self, methodid, *args)
...@@ -11,7 +11,7 @@ except ImportError: ...@@ -11,7 +11,7 @@ except ImportError:
import code import code
def embed(): def embed():
code.interact(local=dict(globals(), **locals())) code.interact(local=dict(globals(), **locals()))
from opcua import ua from opcua import ua
from opcua import Client from opcua import Client
...@@ -236,7 +236,7 @@ def uawrite(): ...@@ -236,7 +236,7 @@ def uawrite():
"--datatype", "--datatype",
dest="datatype", dest="datatype",
default="guess", default="guess",
choices=["guess", 'byte', 'sbyte', 'nodeid', 'expandednodeid', 'qualifiedname', 'browsename', 'string', 'float', 'double', 'int16', 'int32', "int64", 'uint16', 'uint32', 'uint64', "bool", "string", 'datetime', 'bytestring', 'xmlelement', 'statuscode', 'localizedtext'], choices=["guess", 'byte', 'sbyte', 'nodeid', 'expandednodeid', 'qualifiedname', 'browsename', 'string', 'float', 'double', 'int16', 'int32', "int64", 'uint16', 'uint32', 'uint64', "bool", "string", 'datetime', 'bytestring', 'xmlelement', 'statuscode', 'localizedtext'],
help="Data type to return") help="Data type to return")
parser.add_argument("value", parser.add_argument("value",
help="Value to be written", help="Value to be written",
...@@ -323,7 +323,7 @@ def _lsprint_long(pnode, depth, indent=""): ...@@ -323,7 +323,7 @@ def _lsprint_long(pnode, depth, indent=""):
print("{:30} {:25} {:25} {:10} {:30} {:25}".format("DisplayName", "NodeId", "BrowseName", "DataType", "Timestamp", "Value")) print("{:30} {:25} {:25} {:10} {:30} {:25}".format("DisplayName", "NodeId", "BrowseName", "DataType", "Timestamp", "Value"))
print("") print("")
for node in pnode.get_children(): for node in pnode.get_children():
attrs = node.get_attributes([ua.AttributeIds.DisplayName, attrs = node.get_attributes([ua.AttributeIds.DisplayName,
ua.AttributeIds.BrowseName, ua.AttributeIds.BrowseName,
ua.AttributeIds.NodeClass, ua.AttributeIds.NodeClass,
ua.AttributeIds.WriteMask, ua.AttributeIds.WriteMask,
...@@ -566,7 +566,7 @@ def uadiscover(): ...@@ -566,7 +566,7 @@ def uadiscover():
#action="store_false", #action="store_false",
#help="send a GetEndpoints request to server") #help="send a GetEndpoints request to server")
args = parse_args(parser) args = parse_args(parser)
client = Client(args.url, timeout=args.timeout) client = Client(args.url, timeout=args.timeout)
if args.network: if args.network:
...@@ -651,3 +651,75 @@ def uahistoryread(): ...@@ -651,3 +651,75 @@ def uahistoryread():
finally: finally:
client.disconnect() client.disconnect()
sys.exit(0) sys.exit(0)
def uacall():
parser = argparse.ArgumentParser(description="Call method of a node")
add_common_args(parser)
parser.add_argument("-m",
"--method",
dest="method",
type=int,
default=None,
help="Set method to call. If not given then (single) method of the selected node is used.")
parser.add_argument("-l",
"--list",
"--array",
dest="array",
default="guess",
choices=["guess", "true", "false"],
help="Value is an array")
parser.add_argument("-t",
"--datatype",
dest="datatype",
default="guess",
choices=["guess", 'byte', 'sbyte', 'nodeid', 'expandednodeid', 'qualifiedname', 'browsename', 'string', 'float', 'double', 'int16', 'int32', "int64", 'uint16', 'uint32', 'uint64', "bool", "string", 'datetime', 'bytestring', 'xmlelement', 'statuscode', 'localizedtext'],
help="Data type to return")
parser.add_argument("value",
help="Value to use for call to method, if any",
nargs="?",
metavar="VALUE")
args = parse_args(parser, requirenodeid=True)
client = Client(args.url, timeout=args.timeout)
client.set_security_string(args.security)
client.connect()
try:
node = get_node(client, args)
# val must be a tuple in order to enable method calls without arguments
if ( args.value is None ):
val = () #empty tuple
else:
val = (_val_to_variant(args.value, args),) # tuple with one element
# determine method to call: Either explicitly given or automatically select the method of the selected node.
methods = node.get_methods()
method_id = None
#print( "methods=%s" % (methods) )
if ( args.method is None ):
if ( len( methods ) == 0 ):
raise ValueError( "No methods in selected node and no method given" )
elif ( len( methods ) == 1 ):
method_id = methods[0]
else:
raise ValueError( "Selected node has %d methods but no method given. Provide one of %s" % (methods) )
else:
for m in methods:
if ( m.nodeid.Identifier == args.method ):
method_id = m.nodeid
break
if ( method_id is None):
# last resort:
method_id = ua.NodeId( identifier=args.method )#, namespaceidx=? )#, nodeidtype=?): )
#print( "method_id=%s\nval=%s" % (method_id,val) )
result_variants = node.call_method( method_id, *val )
print( "resulting result_variants=%s" % result_variants )
finally:
client.disconnect()
sys.exit(0)
print(args)
...@@ -7,7 +7,7 @@ if sys.version_info[0] < 3: ...@@ -7,7 +7,7 @@ if sys.version_info[0] < 3:
else: else:
install_requires = ["python-dateutil"] install_requires = ["python-dateutil"]
setup(name="freeopcua", setup(name="freeopcua",
version="0.10.17", version="0.10.17",
description="Pure Python OPC-UA client and server library", description="Pure Python OPC-UA client and server library",
author="Olivier Roulet-Dubonnet", author="Olivier Roulet-Dubonnet",
...@@ -29,7 +29,7 @@ setup(name="freeopcua", ...@@ -29,7 +29,7 @@ setup(name="freeopcua",
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
], ],
entry_points={'console_scripts': entry_points={'console_scripts':
[ [
'uaread = opcua.tools:uaread', 'uaread = opcua.tools:uaread',
'uals = opcua.tools:uals', 'uals = opcua.tools:uals',
...@@ -39,7 +39,8 @@ setup(name="freeopcua", ...@@ -39,7 +39,8 @@ setup(name="freeopcua",
'uahistoryread = opcua.tools:uahistoryread', 'uahistoryread = opcua.tools:uahistoryread',
'uaclient = opcua.tools:uaclient', 'uaclient = opcua.tools:uaclient',
'uaserver = opcua.tools:uaserver', 'uaserver = opcua.tools:uaserver',
'uadiscover = opcua.tools:uadiscover' 'uadiscover = opcua.tools:uadiscover',
'uacall = opcua.tools:uacall',
] ]
} }
) )
......
/tests_spaw.py
/export.xml
#!/usr/bin/env python
import sys
import os
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."))
from opcua.tools import uacall
if __name__ == "__main__":
uacall()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment