1 from threading
import RLock
3 from datetime
import datetime
7 import cPickle
as pickle
23 return "AttributeValue({0})".format(self.
value)
43 self.
logger = logging.getLogger(__name__)
47 self.logger.debug(
"read %s", params)
49 for readvalue
in params.NodesToRead:
50 res.append(self._aspace.get_attribute_value(readvalue.NodeId, readvalue.AttributeId))
53 def write(self, params, user=User.Admin):
54 self.logger.debug(
"write %s as user %s", params, user)
56 for writevalue
in params.NodesToWrite:
57 if user != User.Admin:
58 if writevalue.AttributeId != ua.AttributeIds.Value:
61 al = self._aspace.get_attribute_value(writevalue.NodeId, ua.AttributeIds.AccessLevel)
62 ual = self._aspace.get_attribute_value(writevalue.NodeId, ua.AttributeIds.UserAccessLevel)
63 if not ua.ua_binary.test_bit(al.Value.Value, ua.AccessLevel.CurrentWrite)
or not ua.ua_binary.test_bit(ual.Value.Value, ua.AccessLevel.CurrentWrite):
66 res.append(self._aspace.set_attribute_value(writevalue.NodeId, writevalue.AttributeId, writevalue.Value))
73 self.
logger = logging.getLogger(__name__)
77 self.logger.debug(
"browse %s", params)
79 for desc
in params.NodesToBrowse:
85 if desc.NodeId
not in self.
_aspace:
86 res.StatusCode =
ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
88 node = self.
_aspace[desc.NodeId]
89 for ref
in node.references:
92 res.References.append(ref)
97 self.logger.debug(
"%s is not suitable due to direction", ref)
99 if not self.
_suitable_reftype(desc.ReferenceTypeId, ref.ReferenceTypeId, desc.IncludeSubtypes):
100 self.logger.debug(
"%s is not suitable due to type", ref)
102 if desc.NodeClassMask
and ((desc.NodeClassMask & ref.NodeClass) == 0):
103 self.logger.debug(
"%s is not suitable due to class", ref)
105 self.logger.debug(
"%s is a suitable ref for desc %s", ref, desc)
111 if not subtypes
and ref2.Identifier == ua.ObjectIds.HasSubtype:
113 if ref1.Identifier == ref2.Identifier:
116 if not subtypes
and ua.NodeId(ua.ObjectIds.HasSubtype)
in oktypes:
117 oktypes.remove(
ua.NodeId(ua.ObjectIds.HasSubtype))
118 return ref2
in oktypes
123 if nodedata
is not None:
124 for ref
in nodedata.references:
125 if ref.ReferenceTypeId.Identifier == ua.ObjectIds.HasSubtype
and ref.IsForward:
126 res.append(ref.NodeId)
131 if desc == ua.BrowseDirection.Both:
133 if desc == ua.BrowseDirection.Forward
and isforward:
135 if desc == ua.BrowseDirection.Inverse
and not isforward:
140 self.logger.debug(
"translate browsepath: %s", browsepaths)
142 for path
in browsepaths:
147 self.logger.debug(
"looking at path: %s", path)
149 if path.StartingNode
not in self.
_aspace:
150 res.StatusCode =
ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
152 current = path.StartingNode
153 for el
in path.RelativePath.Elements:
160 target.TargetId = current
161 target.RemainingPathIndex = 4294967295
162 res.Targets = [target]
166 nodedata = self.
_aspace[nodeid]
167 for ref
in nodedata.references:
169 if ref.BrowseName == el.TargetName:
171 self.logger.info(
"element %s was not found in node %s", el, nodeid)
178 self.
logger = logging.getLogger(__name__)
183 for item
in addnodeitems:
184 results.append(self.
_add_node(item, user))
193 if item.RequestedNewNodeId.has_null_identifier():
194 self.logger.debug(
"RequestedNewNodeId has null identifier, generating Identifier")
195 nodedata =
NodeData(self._aspace.generate_nodeid(item.RequestedNewNodeId.NamespaceIndex))
197 nodedata =
NodeData(item.RequestedNewNodeId)
199 if nodedata.nodeid
in self.
_aspace:
200 self.logger.warning(
"AddNodesItem: node already exists")
201 result.StatusCode =
ua.StatusCode(ua.StatusCodes.BadNodeIdExists)
204 if item.ParentNodeId.is_null():
208 elif item.ParentNodeId
not in self.
_aspace:
209 self.logger.warning(
"add_node: while adding node %s, requested parent node %s does not exists", nodedata.nodeid, item.ParentNodeId)
210 result.StatusCode =
ua.StatusCode(ua.StatusCodes.BadParentNodeIdInvalid)
213 if not user == User.Admin:
214 result.StatusCode =
ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
220 self.
_aspace[nodedata.nodeid] = nodedata
222 if not item.ParentNodeId.is_null():
231 result.AddedNodeId = nodedata.nodeid
251 desc.ReferenceTypeId = item.ReferenceTypeId
252 desc.NodeId = nodedata.nodeid
253 desc.NodeClass = item.NodeClass
254 desc.BrowseName = item.BrowseName
256 desc.TypeDefinition = item.TypeDefinition
257 desc.IsForward =
True 258 self.
_aspace[item.ParentNodeId].references.append(desc)
262 addref.ReferenceTypeId = item.ReferenceTypeId
263 addref.SourceNodeId = nodedata.nodeid
264 addref.TargetNodeId = item.ParentNodeId
265 addref.TargetNodeClass = self.
_aspace[item.ParentNodeId].attributes[ua.AttributeIds.NodeClass].value.Value.Value
266 addref.IsForward =
False 271 addref.SourceNodeId = nodedata.nodeid
272 addref.IsForward =
True 273 addref.ReferenceTypeId =
ua.NodeId(ua.ObjectIds.HasTypeDefinition)
274 addref.TargetNodeId = item.TypeDefinition
275 addref.TargetNodeClass = ua.NodeClass.DataType
280 for item
in deletenodeitems.NodesToDelete:
285 if user != User.Admin:
288 if item.NodeId
not in self.
_aspace:
289 self.logger.warning(
"DeleteNodesItem: node does not exists")
292 if item.DeleteTargetReferences:
293 for elem
in self._aspace.keys():
294 for rdesc
in self.
_aspace[elem].references:
295 if rdesc.NodeId == item.NodeId:
296 self.
_aspace[elem].references.remove(rdesc)
305 if ua.AttributeIds.Value
in nodedata.attributes:
306 for handle, callback
in nodedata.attributes[ua.AttributeIds.Value].datachange_callbacks.items():
308 callback(handle,
None,
ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown))
309 self._aspace.delete_datachange_callback(handle)
310 except Exception
as ex:
311 self.logger.exception(
"Error calling delete node callback callback %s, %s, %s", nodedata, ua.AttributeIds.Value, ex)
320 if addref.SourceNodeId
not in self.
_aspace:
322 if addref.TargetNodeId
not in self.
_aspace:
324 if user != User.Admin:
327 rdesc.ReferenceTypeId = addref.ReferenceTypeId
328 rdesc.IsForward = addref.IsForward
329 rdesc.NodeId = addref.TargetNodeId
330 rdesc.NodeClass = addref.TargetNodeClass
331 bname = self._aspace.get_attribute_value(addref.TargetNodeId, ua.AttributeIds.BrowseName).Value.Value
333 rdesc.BrowseName = bname
334 dname = self._aspace.get_attribute_value(addref.TargetNodeId, ua.AttributeIds.DisplayName).Value.Value
336 rdesc.DisplayName = dname
337 self.
_aspace[addref.SourceNodeId].references.append(rdesc)
347 if item.SourceNodeId
not in self.
_aspace:
349 if item.TargetNodeId
not in self.
_aspace:
351 if user != User.Admin:
354 for rdesc
in self.
_aspace[item.SourceNodeId].references:
355 if rdesc.NodeId
is item.TargetNodeId:
356 if rdesc.RefrenceTypeId != item.RefrenceTypeId:
357 return ua.StatusCode(ua.StatusCodes.BadReferenceTypeIdInvalid)
358 if rdesc.IsForward == item.IsForward
or item.DeleteBidirectional:
359 self.
_aspace[item.SourceNodeId].references.remove(rdesc)
361 for rdesc
in self.
_aspace[item.TargetNodeId].references:
362 if rdesc.NodeId
is item.SourceNodeId:
363 if rdesc.RefrenceTypeId != item.RefrenceTypeId:
364 return ua.StatusCode(ua.StatusCodes.BadReferenceTypeIdInvalid)
365 if rdesc.IsForward == item.IsForward
or item.DeleteBidirectional:
366 self.
_aspace[item.SourceNodeId].references.remove(rdesc)
373 dv.ServerTimestamp = datetime.utcnow()
374 dv.SourceTimestamp = datetime.utcnow()
378 self.
_add_node_attr(item, nodedata,
"AccessLevel", ua.VariantType.Byte)
379 self.
_add_node_attr(item, nodedata,
"ArrayDimensions", ua.VariantType.UInt32)
380 self.
_add_node_attr(item, nodedata,
"BrowseName", ua.VariantType.QualifiedName)
381 self.
_add_node_attr(item, nodedata,
"ContainsNoLoops", ua.VariantType.Boolean)
382 self.
_add_node_attr(item, nodedata,
"DataType", ua.VariantType.NodeId)
383 self.
_add_node_attr(item, nodedata,
"Description", ua.VariantType.LocalizedText)
384 self.
_add_node_attr(item, nodedata,
"DisplayName", ua.VariantType.LocalizedText)
385 self.
_add_node_attr(item, nodedata,
"EventNotifier", ua.VariantType.Byte)
386 self.
_add_node_attr(item, nodedata,
"Executable", ua.VariantType.Boolean)
387 self.
_add_node_attr(item, nodedata,
"Historizing", ua.VariantType.Boolean)
388 self.
_add_node_attr(item, nodedata,
"InverseName", ua.VariantType.LocalizedText)
389 self.
_add_node_attr(item, nodedata,
"IsAbstract", ua.VariantType.Boolean)
390 self.
_add_node_attr(item, nodedata,
"MinimumSamplingInterval", ua.VariantType.Double)
391 self.
_add_node_attr(item, nodedata,
"NodeClass", ua.VariantType.UInt32)
392 self.
_add_node_attr(item, nodedata,
"NodeId", ua.VariantType.NodeId)
393 self.
_add_node_attr(item, nodedata,
"Symmetric", ua.VariantType.Boolean)
394 self.
_add_node_attr(item, nodedata,
"UserAccessLevel", ua.VariantType.Byte)
395 self.
_add_node_attr(item, nodedata,
"UserExecutable", ua.VariantType.Boolean)
396 self.
_add_node_attr(item, nodedata,
"UserWriteMask", ua.VariantType.Byte)
397 self.
_add_node_attr(item, nodedata,
"ValueRank", ua.VariantType.Int32)
398 self.
_add_node_attr(item, nodedata,
"WriteMask", ua.VariantType.UInt32)
399 self.
_add_node_attr(item, nodedata,
"UserWriteMask", ua.VariantType.UInt32)
406 self.
logger = logging.getLogger(__name__)
411 for method
in methods:
412 results.append(self.
_call(method))
417 if method.ObjectId
not in self.
_aspace or method.MethodId
not in self.
_aspace:
418 res.StatusCode =
ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
420 node = self.
_aspace[method.MethodId]
421 if node.call
is None:
422 res.StatusCode =
ua.StatusCode(ua.StatusCodes.BadNothingToDo)
425 res.OutputArguments = node.call(method.ObjectId, *method.InputArguments)
426 for _
in method.InputArguments:
429 self.logger.exception(
"Error executing method call %s, an exception was raised: ", method)
430 res.StatusCode =
ua.StatusCode(ua.StatusCodes.BadUnexpectedError)
437 The address space object stores all the nodes of the OPC-UA server 439 The methods are thread safe 443 self.
logger = logging.getLogger(__name__)
454 return self._nodes.__getitem__(nodeid)
458 return self._nodes.__setitem__(nodeid, value)
462 return self._nodes.__contains__(nodeid)
466 self._nodes.__delitem__(nodeid)
485 return self._nodes.keys()
489 Delete all nodes in address space 496 Dump address space as binary to file; note that server must be stopped for this method to work 497 DO NOT DUMP AN ADDRESS SPACE WHICH IS USING A SHELF (load_aspace_shelf), ONLY CACHED NODES WILL GET DUMPED! 500 for nodeid, ndata
in self._nodes.items():
502 if ndata.call
is not None:
503 self.
_nodes[nodeid].call =
None 505 with open(path,
'wb')
as f:
506 pickle.dump(self.
_nodes, f, pickle.HIGHEST_PROTOCOL)
510 Load address space from a binary file, overwriting everything in the current address space 512 with open(path,
'rb')
as f:
513 self.
_nodes = pickle.load(f)
517 Make a shelf for containing the nodes from the standard address space; this is typically only done on first 518 start of the server. Subsequent server starts will load the shelf, nodes are then moved to a cache 519 by the LazyLoadingDict class when they are accessed. Saving data back to the shelf 520 is currently NOT supported, it is only used for the default OPC UA standard address space 522 Note: Intended for slow devices, such as Raspberry Pi, to greatly improve start up time 524 s = shelve.open(path,
"n", protocol=pickle.HIGHEST_PROTOCOL)
525 for nodeid, ndata
in self._nodes.keys():
526 s[nodeid.to_string()] = ndata
531 Load the standard address space nodes from a python shelve via LazyLoadingDict as needed. 532 The dump() method can no longer be used if the address space is being loaded from a shelf 534 Note: Intended for slow devices, such as Raspberry Pi, to greatly improve start up time 536 class LazyLoadingDict(collections.MutableMapping):
538 Special dict that only loads nodes as they are accessed. If a node is accessed it gets copied from the 539 shelve to the cache dict. All user nodes are saved in the cache ONLY. Saving data back to the shelf 540 is currently NOT supported 549 return self.
cache[key]
551 node = self.
cache[key] = self.
source[key.to_string()]
556 self.
cache[key] = value
559 return key
in self.
cache or key.to_string()
in self.
source 567 return iter(self.cache.keys())
571 return len(self.
cache)
573 self.
_nodes = LazyLoadingDict(shelve.open(path,
"r")) 577 self.logger.debug(
"get attr val: %s %s", nodeid, attr)
578 if nodeid
not in self.
_nodes:
580 dv.StatusCode =
ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown)
582 node = self.
_nodes[nodeid]
583 if attr
not in node.attributes:
585 dv.StatusCode =
ua.StatusCode(ua.StatusCodes.BadAttributeIdInvalid)
587 attval = node.attributes[attr]
588 if attval.value_callback:
589 return attval.value_callback()
594 self.logger.debug(
"set attr val: %s %s %s", nodeid, attr, value)
595 if nodeid
not in self.
_nodes:
597 node = self.
_nodes[nodeid]
598 if attr
not in node.attributes:
600 if not value.SourceTimestamp:
601 value.SourceTimestamp = datetime.utcnow()
602 if not value.ServerTimestamp:
603 value.ServerTimestamp = datetime.utcnow()
605 attval = node.attributes[attr]
609 if old.Value != value.Value:
610 cbs = list(attval.datachange_callbacks.items())
615 except Exception
as ex:
616 self.logger.exception(
"Error calling datachange callback %s, %s, %s", k, v, ex)
622 self.logger.debug(
"set attr callback: %s %s %s", nodeid, attr, callback)
623 if nodeid
not in self.
_nodes:
625 node = self.
_nodes[nodeid]
626 if attr
not in node.attributes:
627 return ua.StatusCode(ua.StatusCodes.BadAttributeIdInvalid), 0
628 attval = node.attributes[attr]
631 attval.datachange_callbacks[handle] = callback
637 nodeid, attr = self._handle_to_attribute_map.pop(handle)
638 self.
_nodes[nodeid].attributes[attr].datachange_callbacks.pop(handle)
642 node = self.
_nodes[methodid]
def delete_datachange_callback(self, handle)
def add_method_callback(self, methodid, callback)
def _add_node_attr(self, item, nodedata, name, vtype=None)
def generate_nodeid(self, idx=None)
def add_datachange_callback(self, nodeid, attr, callback)
def _delete_node_callbacks(self, nodedata)
def __init__(self, aspace)
def __setitem__(self, nodeid, value)
def __init__(self, aspace)
def __contains__(self, nodeid)
def _add_ref_to_parent(self, nodedata, item, user)
def _add_node(self, item, user)
def translate_browsepaths_to_nodeids(self, browsepaths)
def __init__(self, aspace)
def delete_references(self, refs, user=User.Admin)
def load_aspace_shelf(self, path)
def _add_nodeattributes(self, item, nodedata)
def get_attribute_value(self, nodeid, attr)
def _add_node_attributes(self, nodedata, item)
def _add_ref_from_parent(self, nodedata, item)
def __init__(self, nodeid)
def __init__(self, aspace)
def _suitable_direction(self, desc, isforward)
def delete_nodes(self, deletenodeitems, user=User.Admin)
_datachange_callback_counter
def write(self, params, user=User.Admin)
def set_attribute_value(self, nodeid, attr, value)
def add_references(self, refs, user=User.Admin)
def _delete_node(self, item, user)
def make_aspace_shelf(self, path)
def __init__(self, value)
def __delitem__(self, nodeid)
def _suitable_reftype(self, ref1, ref2, subtypes)
def _delete_reference(self, item, user)
def _add_reference(self, addref, user)
def _add_type_definition(self, nodedata, item, user)
def _find_element_in_node(self, el, nodeid)
def _get_sub_ref(self, ref)
def _is_suitable_ref(self, desc, ref)
def add_nodes(self, addnodeitems, user=User.Admin)
def _translate_browsepath_to_nodeid(self, path)
def __getitem__(self, nodeid)