Commit 5efd5083 authored by ORD's avatar ORD

Merge pull request #165 from iirob/inverse_references

Added support for Inverse references
parents 60866b82 dd4d25d0
......@@ -37,7 +37,7 @@ def create_folder(parent, *args):
or namespace index, name
"""
nodeid, qname = _parse_add_args(*args)
return node.Node(parent.server, _create_folder(parent.server, parent.nodeid, nodeid, qname))
return node.Node(parent.server, _create_object(parent.server, parent.nodeid, nodeid, qname, ua.ObjectIds.FolderType))
def create_object(parent, *args):
......@@ -47,7 +47,7 @@ def create_object(parent, *args):
or namespace index, name
"""
nodeid, qname = _parse_add_args(*args)
return node.Node(parent.server, _create_object(parent.server, parent.nodeid, nodeid, qname))
return node.Node(parent.server, _create_object(parent.server, parent.nodeid, nodeid, qname, ua.ObjectIds.BaseObjectType))
def create_property(parent, *args):
......@@ -91,37 +91,21 @@ def create_method(parent, *args):
outputs = args[4]
else:
outputs = []
return _create_method(parent, nodeid, qname, callback, inputs, outputs)
return node.Node(parent.server, _create_method(parent, nodeid, qname, callback, inputs, outputs))
def _create_folder(server, parentnodeid, nodeid, qname):
def _create_object(server, parentnodeid, nodeid, qname, objecttype):
addnode = ua.AddNodesItem()
addnode.RequestedNewNodeId = nodeid
addnode.BrowseName = qname
addnode.NodeClass = ua.NodeClass.Object
addnode.ParentNodeId = parentnodeid
addnode.ReferenceTypeId = ua.NodeId.from_string("i=35")
addnode.TypeDefinition = ua.NodeId.from_string("i=61")
attrs = ua.ObjectAttributes()
attrs.Description = ua.LocalizedText(qname.Name)
attrs.DisplayName = ua.LocalizedText(qname.Name)
attrs.WriteMask = 0
attrs.UserWriteMask = 0
attrs.EventNotifier = 0
addnode.NodeAttributes = attrs
results = server.add_nodes([addnode])
results[0].StatusCode.check()
return results[0].AddedNodeId
def _create_object(server, parentnodeid, nodeid, qname):
addnode = ua.AddNodesItem()
addnode.RequestedNewNodeId = nodeid
addnode.BrowseName = qname
addnode.NodeClass = ua.NodeClass.Object
addnode.ParentNodeId = parentnodeid
addnode.ReferenceTypeId = ua.NodeId.from_string("i=35")
addnode.TypeDefinition = ua.NodeId(ua.ObjectIds.BaseObjectType)
#TODO: maybe move to address_space.py and implement for all node types?
if node.Node(server, parentnodeid).get_type_definition() == ua.ObjectIds.FolderType:
addnode.ReferenceTypeId = ua.NodeId(ua.ObjectIds.Organizes)
else:
addnode.ReferenceTypeId = ua.NodeId(ua.ObjectIds.HasComponent)
addnode.TypeDefinition = ua.NodeId(objecttype)
attrs = ua.ObjectAttributes()
attrs.Description = ua.LocalizedText(qname.Name)
attrs.DisplayName = ua.LocalizedText(qname.Name)
......@@ -180,7 +164,7 @@ def _create_method(parent, nodeid, qname, callback, inputs, outputs):
addnode.BrowseName = qname
addnode.NodeClass = ua.NodeClass.Method
addnode.ParentNodeId = parent.nodeid
addnode.ReferenceTypeId = ua.NodeId.from_string("i=47")
addnode.ReferenceTypeId = ua.NodeId(ua.ObjectIds.HasComponent)
#node.TypeDefinition = ua.NodeId(ua.ObjectIds.BaseObjectType)
attrs = ua.MethodAttributes()
attrs.Description = ua.LocalizedText(qname.Name)
......
......@@ -242,12 +242,7 @@ class Node(object):
HasNotifier = 48
HasOrderedComponent = 49
"""
references = self.get_children_descriptions(refs, nodeclassmask)
nodes = []
for desc in references:
node = Node(self.server, desc.NodeId)
nodes.append(node)
return nodes
return self.get_referenced_nodes(refs, ua.BrowseDirection.Forward, nodeclassmask)
def get_properties(self):
"""
......@@ -257,11 +252,19 @@ class Node(object):
return self.get_children(refs=ua.ObjectIds.HasProperty, nodeclassmask=ua.NodeClass.Variable)
def get_children_descriptions(self, refs=ua.ObjectIds.HierarchicalReferences, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
return self.get_references(refs, ua.BrowseDirection.Forward, nodeclassmask, includesubtypes)
def get_references(self, refs=ua.ObjectIds.References, direction=ua.BrowseDirection.Both, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
"""
return all attributes of child nodes as UA BrowseResult structs
returns references of the node based on specific filter defined with:
refs = ObjectId of the Reference
direction = Browse direction for references
nodeclassmask = filter nodes based on specific class
includesubtypes = If true subtypes of the reference (ref) are also included
"""
desc = ua.BrowseDescription()
desc.BrowseDirection = ua.BrowseDirection.Forward
desc.BrowseDirection = direction
desc.ReferenceTypeId = ua.TwoByteNodeId(refs)
desc.IncludeSubtypes = includesubtypes
desc.NodeClassMask = nodeclassmask
......@@ -274,6 +277,36 @@ class Node(object):
results = self.server.browse(params)
return results[0].References
def get_referenced_nodes(self, refs=ua.ObjectIds.References, direction=ua.BrowseDirection.Both, nodeclassmask=ua.NodeClass.Unspecified, includesubtypes=True):
"""
returns referenced nodes based on specific filter
Paramters are the same as for get_references
"""
references = self.get_references(refs, direction, nodeclassmask, includesubtypes)
nodes = []
for desc in references:
node = Node(self.server, desc.NodeId)
nodes.append(node)
return nodes
def get_type_definition(self):
"""
returns type definition of the node.
"""
references = self.get_references(refs=ua.ObjectIds.HasTypeDefinition, direction=ua.BrowseDirection.Forward)
if len(references) == 0:
return ua.ObjectIds.BaseObjectType
return references[0].NodeId.Identifier
def get_parent(self):
"""
returns parent of the node.
"""
refs = self.get_references(refs=ua.ObjectIds.HierarchicalReferences, direction=ua.BrowseDirection.Inverse)
return Node(self.server, refs[0].NodeId)
def get_child(self, path):
"""
get a child specified by its path from this node.
......
......@@ -108,7 +108,8 @@ class ViewService(object):
"""
if ref1.Identifier == ref2.Identifier:
return True
if not subtypes and ref2.Identifier == ua.ObjectIds.HasSubtype:
#TODO: Please check the changes here
elif not subtypes:
return False
oktypes = self._get_sub_ref(ref1)
return ref2 in oktypes
......@@ -117,7 +118,7 @@ class ViewService(object):
res = []
nodedata = self._aspace[ref]
for ref in nodedata.references:
if ref.ReferenceTypeId.Identifier == ua.ObjectIds.HasSubtype:
if ref.ReferenceTypeId.Identifier == ua.ObjectIds.HasSubtype and ref.IsForward:
res.append(ref.NodeId)
res += self._get_sub_ref(ref.NodeId)
return res
......@@ -127,6 +128,8 @@ class ViewService(object):
return True
if desc == ua.BrowseDirection.Forward and isforward:
return True
if desc == ua.BrowseDirection.Inverse and not isforward:
return True
return False
def translate_browsepaths_to_nodeids(self, browsepaths):
......@@ -215,6 +218,9 @@ class NodeManagementService(object):
# add requested attrs
self._add_nodeattributes(item.NodeAttributes, nodedata)
# now add our node to db
self._aspace[nodedata.nodeid] = nodedata
if not item.ParentNodeId.is_null():
desc = ua.ReferenceDescription()
desc.ReferenceTypeId = item.ReferenceTypeId
......@@ -226,8 +232,13 @@ class NodeManagementService(object):
desc.IsForward = True
self._aspace[item.ParentNodeId].references.append(desc)
# now add our node to db
self._aspace[nodedata.nodeid] = nodedata
addref = ua.AddReferencesItem()
addref.ReferenceTypeId = item.ReferenceTypeId
addref.SourceNodeId = nodedata.nodeid
addref.TargetNodeId = item.ParentNodeId
addref.TargetNodeClass = self._aspace[item.ParentNodeId].attributes[ua.AttributeIds.NodeClass].value.Value.Value
addref.IsForward = False
self._add_reference(addref, user)
# add type definition
if item.TypeDefinition != ua.NodeId():
......
......@@ -86,7 +86,7 @@ class MySubHandler():
class MySubHandler2():
def __init__(self):
self.results = []
self.results = []
def datachange_notification(self, node, val, data):
self.results.append((node, val))
......@@ -97,8 +97,8 @@ class MySubHandler2():
class MySubHandlerCounter():
def __init__(self):
self.datachange_count = 0
self.event_count = 0
self.datachange_count = 0
self.event_count = 0
def datachange_notification(self, node, val, data):
self.datachange_count += 1
......@@ -107,8 +107,6 @@ class MySubHandlerCounter():
self.event_count += 1
class CommonTests(object):
'''
......@@ -223,14 +221,42 @@ class CommonTests(object):
self.assertTrue(prop2 in props)
self.assertFalse(var in props)
self.assertFalse(folder in props)
self.assertFalse(obj2 in props)
all_vars = obj.get_children(nodeclassmask=ua.NodeClass.Variable)
self.assertTrue(prop in all_vars)
self.assertTrue(var in all_vars)
self.assertFalse(folder in props)
self.assertFalse(obj2 in props)
all_objs = obj.get_children(nodeclassmask=ua.NodeClass.Object)
self.assertTrue(folder in all_objs)
self.assertTrue(obj2 in all_objs)
self.assertFalse(var in all_objs)
def test_browse_references(self):
objects = self.opc.get_objects_node()
folder = objects.add_folder(4, "folder")
childs = objects.get_referenced_nodes(refs=ua.ObjectIds.Organizes, direction=ua.BrowseDirection.Forward, includesubtypes=False)
self.assertTrue(folder in childs)
childs = objects.get_referenced_nodes(refs=ua.ObjectIds.Organizes, direction=ua.BrowseDirection.Both, includesubtypes=False)
self.assertTrue(folder in childs)
childs = objects.get_referenced_nodes(refs=ua.ObjectIds.Organizes, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertFalse(folder in childs)
parents = folder.get_referenced_nodes(refs=ua.ObjectIds.Organizes, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertTrue(objects in parents)
parents = folder.get_referenced_nodes(refs=ua.ObjectIds.HierarchicalReferences, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertFalse(objects in parents)
parents = folder.get_referenced_nodes(refs=ua.ObjectIds.HierarchicalReferences, direction=ua.BrowseDirection.Inverse, includesubtypes=True)
self.assertTrue(objects in parents)
parent = folder.get_parent()
self.assertEqual(parent, objects)
def test_browsename_with_spaces(self):
o = self.opc.get_objects_node()
v = o.add_variable(3, 'BNVariable with spaces and %&+?/', 1.3)
......@@ -387,7 +413,7 @@ class CommonTests(object):
def test_utf8(self):
objects = self.opc.get_objects_node()
utf_string = "æøå@%&"
bn = ua.QualifiedName(utf_string, 3)
bn = ua.QualifiedName(utf_string, 3)
nid = ua.NodeId("æølå", 3)
val = "æøå"
v = objects.add_variable(nid, bn, val)
......@@ -747,7 +773,7 @@ class CommonTests(object):
o = self.opc.get_objects_node()
# subscribe to a variable
startv1 = True
startv1 = True
v1 = o.add_variable(3, 'SubscriptionVariableBool', startv1)
sub = self.opc.create_subscription(100, msclt)
handle1 = sub.subscribe_data_change(v1)
......@@ -777,9 +803,9 @@ class CommonTests(object):
msclt = MySubHandler2()
o = self.opc.get_objects_node()
startv1 = True
startv1 = True
v1 = o.add_variable(3, 'SubscriptionVariableMany1', startv1)
startv2 = [1.22, 1.65]
startv2 = [1.22, 1.65]
v2 = o.add_variable(3, 'SubscriptionVariableMany2', startv2)
sub = self.opc.create_subscription(100, msclt)
......@@ -787,7 +813,7 @@ class CommonTests(object):
# Now check we get the start values
nodes = [v1, v2]
count = 0
while not len(msclt.results) > 1:
count += 1
......@@ -804,7 +830,7 @@ class CommonTests(object):
else:
self.fail("Error node {} is neither {} nor {}".format(node, v1, v2))
sub.delete()
sub.delete()
def test_subscribe_server_time(self):
msclt = MySubHandler()
......@@ -856,14 +882,57 @@ class CommonTests(object):
def test_add_nodes(self):
objects = self.opc.get_objects_node()
f = objects.add_folder(3, 'MyFolder')
child = objects.get_child("3:MyFolder")
self.assertEqual(child, f)
o = f.add_object(3, 'MyObject')
child = f.get_child("3:MyObject")
self.assertEqual(child, o)
v = f.add_variable(3, 'MyVariable', 6)
child = f.get_child("3:MyVariable")
self.assertEqual(child, v)
p = f.add_property(3, 'MyProperty', 10)
child = f.get_child("3:MyProperty")
self.assertEqual(child, p)
childs = f.get_children()
self.assertTrue(o in childs)
self.assertTrue(v in childs)
self.assertTrue(p in childs)
def test_references_for_added_nodes(self):
objects = self.opc.get_objects_node()
o = objects.add_object(3, 'MyObject')
nodes = objects.get_referenced_nodes(refs=ua.ObjectIds.Organizes, direction=ua.BrowseDirection.Forward, includesubtypes=False)
self.assertTrue(o in nodes)
nodes = o.get_referenced_nodes(refs=ua.ObjectIds.Organizes, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertTrue(objects in nodes)
self.assertEqual(o.get_parent(), objects)
self.assertEqual(o.get_type_definition(), ua.ObjectIds.BaseObjectType)
o2 = o.add_object(3, 'MySecondObject')
nodes = o.get_referenced_nodes(refs=ua.ObjectIds.HasComponent, direction=ua.BrowseDirection.Forward, includesubtypes=False)
self.assertTrue(o2 in nodes)
nodes = o2.get_referenced_nodes(refs=ua.ObjectIds.HasComponent, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertTrue(o in nodes)
self.assertEqual(o2.get_parent(), o)
self.assertEqual(o2.get_type_definition(), ua.ObjectIds.BaseObjectType)
v = o.add_variable(3, 'MyVariable', 6)
nodes = o.get_referenced_nodes(refs=ua.ObjectIds.HasComponent, direction=ua.BrowseDirection.Forward, includesubtypes=False)
self.assertTrue(v in nodes)
nodes = v.get_referenced_nodes(refs=ua.ObjectIds.HasComponent, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertTrue(o in nodes)
self.assertEqual(v.get_parent(), o)
self.assertEqual(v.get_type_definition(), ua.ObjectIds.BaseDataVariableType)
p = o.add_property(3, 'MyProperty', 2)
nodes = o.get_referenced_nodes(refs=ua.ObjectIds.HasProperty, direction=ua.BrowseDirection.Forward, includesubtypes=False)
self.assertTrue(p in nodes)
nodes = p.get_referenced_nodes(refs=ua.ObjectIds.HasProperty, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertTrue(o in nodes)
self.assertEqual(p.get_parent(), o)
self.assertEqual(p.get_type_definition(), ua.ObjectIds.PropertyType)
def test_get_endpoints(self):
endpoints = self.opc.get_endpoints()
self.assertTrue(len(endpoints) > 0)
self.assertTrue(endpoints[0].EndpointUrl.startswith("opc.tcp://"))
......@@ -6,6 +6,7 @@ from datetime import timedelta
from opcua import Server
from opcua import Client
from opcua import ua
from opcua import uamethod
port_num = 485140
......@@ -50,7 +51,7 @@ class TestServer(unittest.TestCase, CommonTests):
self.assertTrue(new_app_uri in [s.ApplicationUri for s in new_servers])
finally:
client.disconnect()
def test_find_servers2(self):
client = Client(self.discovery.endpoint.geturl())
client.connect()
......@@ -136,5 +137,23 @@ class TestServer(unittest.TestCase, CommonTests):
var.set_value(3.0)
self.srv.iserver.disable_history(var)
def test_references_for_added_nodes_method(self):
objects = self.opc.get_objects_node()
o = objects.add_object(3, 'MyObject')
nodes = objects.get_referenced_nodes(refs=ua.ObjectIds.Organizes, direction=ua.BrowseDirection.Forward, includesubtypes=False)
self.assertTrue(o in nodes)
nodes = o.get_referenced_nodes(refs=ua.ObjectIds.Organizes, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertTrue(objects in nodes)
self.assertEqual(o.get_parent(), objects)
self.assertEqual(o.get_type_definition(), ua.ObjectIds.BaseObjectType)
@uamethod
def callback(parent):
return
m = o.add_method(3, 'MyMethod', callback)
nodes = o.get_referenced_nodes(refs=ua.ObjectIds.HasComponent, direction=ua.BrowseDirection.Forward, includesubtypes=False)
self.assertTrue(m in nodes)
nodes = m.get_referenced_nodes(refs=ua.ObjectIds.HasComponent, direction=ua.BrowseDirection.Inverse, includesubtypes=False)
self.assertTrue(o in nodes)
self.assertEqual(m.get_parent(), o)
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