address_space.py
Go to the documentation of this file.
1 from threading import RLock
2 import logging
3 from datetime import datetime
4 import collections
5 import shelve
6 try:
7  import cPickle as pickle
8 except:
9  import pickle
10 
11 from opcua import ua
12 from opcua.server.users import User
13 
14 
15 class AttributeValue(object):
16 
17  def __init__(self, value):
18  self.value = value
19  self.value_callback = None
21 
22  def __str__(self):
23  return "AttributeValue({0})".format(self.value)
24  __repr__ = __str__
25 
26 
27 class NodeData(object):
28 
29  def __init__(self, nodeid):
30  self.nodeid = nodeid
31  self.attributes = {}
32  self.references = []
33  self.call = None
34 
35  def __str__(self):
36  return "NodeData(id:{0}, attrs:{1}, refs:{2})".format(self.nodeid, self.attributes, self.references)
37  __repr__ = __str__
38 
39 
40 class AttributeService(object):
41 
42  def __init__(self, aspace):
43  self.logger = logging.getLogger(__name__)
44  self._aspace = aspace
45 
46  def read(self, params):
47  self.logger.debug("read %s", params)
48  res = []
49  for readvalue in params.NodesToRead:
50  res.append(self._aspace.get_attribute_value(readvalue.NodeId, readvalue.AttributeId))
51  return res
52 
53  def write(self, params, user=User.Admin):
54  self.logger.debug("write %s as user %s", params, user)
55  res = []
56  for writevalue in params.NodesToWrite:
57  if user != User.Admin:
58  if writevalue.AttributeId != ua.AttributeIds.Value:
59  res.append(ua.StatusCode(ua.StatusCodes.BadUserAccessDenied))
60  continue
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):
64  res.append(ua.StatusCode(ua.StatusCodes.BadUserAccessDenied))
65  continue
66  res.append(self._aspace.set_attribute_value(writevalue.NodeId, writevalue.AttributeId, writevalue.Value))
67  return res
68 
69 
70 class ViewService(object):
71 
72  def __init__(self, aspace):
73  self.logger = logging.getLogger(__name__)
74  self._aspace = aspace
75 
76  def browse(self, params):
77  self.logger.debug("browse %s", params)
78  res = []
79  for desc in params.NodesToBrowse:
80  res.append(self._browse(desc))
81  return res
82 
83  def _browse(self, desc):
84  res = ua.BrowseResult()
85  if desc.NodeId not in self._aspace:
86  res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
87  return res
88  node = self._aspace[desc.NodeId]
89  for ref in node.references:
90  if not self._is_suitable_ref(desc, ref):
91  continue
92  res.References.append(ref)
93  return res
94 
95  def _is_suitable_ref(self, desc, ref):
96  if not self._suitable_direction(desc.BrowseDirection, ref.IsForward):
97  self.logger.debug("%s is not suitable due to direction", ref)
98  return False
99  if not self._suitable_reftype(desc.ReferenceTypeId, ref.ReferenceTypeId, desc.IncludeSubtypes):
100  self.logger.debug("%s is not suitable due to type", ref)
101  return False
102  if desc.NodeClassMask and ((desc.NodeClassMask & ref.NodeClass) == 0):
103  self.logger.debug("%s is not suitable due to class", ref)
104  return False
105  self.logger.debug("%s is a suitable ref for desc %s", ref, desc)
106  return True
107 
108  def _suitable_reftype(self, ref1, ref2, subtypes):
109  """
110  """
111  if not subtypes and ref2.Identifier == ua.ObjectIds.HasSubtype:
112  return False
113  if ref1.Identifier == ref2.Identifier:
114  return True
115  oktypes = self._get_sub_ref(ref1)
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
119 
120  def _get_sub_ref(self, ref):
121  res = []
122  nodedata = self._aspace[ref]
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)
127  res += self._get_sub_ref(ref.NodeId)
128  return res
129 
130  def _suitable_direction(self, desc, isforward):
131  if desc == ua.BrowseDirection.Both:
132  return True
133  if desc == ua.BrowseDirection.Forward and isforward:
134  return True
135  if desc == ua.BrowseDirection.Inverse and not isforward:
136  return True
137  return False
138 
139  def translate_browsepaths_to_nodeids(self, browsepaths):
140  self.logger.debug("translate browsepath: %s", browsepaths)
141  results = []
142  for path in browsepaths:
143  results.append(self._translate_browsepath_to_nodeid(path))
144  return results
145 
147  self.logger.debug("looking at path: %s", path)
148  res = ua.BrowsePathResult()
149  if path.StartingNode not in self._aspace:
150  res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
151  return res
152  current = path.StartingNode
153  for el in path.RelativePath.Elements:
154  nodeid = self._find_element_in_node(el, current)
155  if not nodeid:
156  res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNoMatch)
157  return res
158  current = nodeid
159  target = ua.BrowsePathTarget()
160  target.TargetId = current
161  target.RemainingPathIndex = 4294967295
162  res.Targets = [target]
163  return res
164 
165  def _find_element_in_node(self, el, nodeid):
166  nodedata = self._aspace[nodeid]
167  for ref in nodedata.references:
168  # FIXME: here we should check other arguments!!
169  if ref.BrowseName == el.TargetName:
170  return ref.NodeId
171  self.logger.info("element %s was not found in node %s", el, nodeid)
172  return None
173 
174 
175 class NodeManagementService(object):
176 
177  def __init__(self, aspace):
178  self.logger = logging.getLogger(__name__)
179  self._aspace = aspace
180 
181  def add_nodes(self, addnodeitems, user=User.Admin):
182  results = []
183  for item in addnodeitems:
184  results.append(self._add_node(item, user))
185  return results
186 
187  def _add_node(self, item, user):
188  result = ua.AddNodesResult()
189 
190  # If Identifier of requested NodeId is null we generate a new NodeId using
191  # the namespace of the nodeid, this is an extention of the spec to allow
192  # to requests the server to generate a new nodeid in a specified namespace
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))
196  else:
197  nodedata = NodeData(item.RequestedNewNodeId)
198 
199  if nodedata.nodeid in self._aspace:
200  self.logger.warning("AddNodesItem: node already exists")
201  result.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdExists)
202  return result
203 
204  if item.ParentNodeId.is_null():
205  # self.logger.warning("add_node: creating node %s without parent", nodedata.nodeid)
206  # should return Error here, but the standard namespace define many nodes without parents...
207  pass
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)
211  return result
212 
213  if not user == User.Admin:
214  result.StatusCode = ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
215  return result
216 
217  self._add_node_attributes(nodedata, item)
218 
219  # now add our node to db
220  self._aspace[nodedata.nodeid] = nodedata
221 
222  if not item.ParentNodeId.is_null():
223  self._add_ref_from_parent(nodedata, item)
224  self._add_ref_to_parent(nodedata, item, user)
225 
226  # add type definition
227  if item.TypeDefinition != ua.NodeId():
228  self._add_type_definition(nodedata, item, user)
229 
230  result.StatusCode = ua.StatusCode()
231  result.AddedNodeId = nodedata.nodeid
232 
233  return result
234 
235  def _add_node_attributes(self, nodedata, item):
236  # add common attrs
237  nodedata.attributes[ua.AttributeIds.NodeId] = AttributeValue(
238  ua.DataValue(ua.Variant(nodedata.nodeid, ua.VariantType.NodeId))
239  )
240  nodedata.attributes[ua.AttributeIds.BrowseName] = AttributeValue(
241  ua.DataValue(ua.Variant(item.BrowseName, ua.VariantType.QualifiedName))
242  )
243  nodedata.attributes[ua.AttributeIds.NodeClass] = AttributeValue(
244  ua.DataValue(ua.Variant(item.NodeClass, ua.VariantType.Int32))
245  )
246  # add requested attrs
247  self._add_nodeattributes(item.NodeAttributes, nodedata)
248 
249  def _add_ref_from_parent(self, nodedata, item):
250  desc = ua.ReferenceDescription()
251  desc.ReferenceTypeId = item.ReferenceTypeId
252  desc.NodeId = nodedata.nodeid
253  desc.NodeClass = item.NodeClass
254  desc.BrowseName = item.BrowseName
255  desc.DisplayName = ua.LocalizedText(item.BrowseName.Name)
256  desc.TypeDefinition = item.TypeDefinition
257  desc.IsForward = True
258  self._aspace[item.ParentNodeId].references.append(desc)
259 
260  def _add_ref_to_parent(self, nodedata, item, user):
261  addref = ua.AddReferencesItem()
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
267  self._add_reference(addref, user)
268 
269  def _add_type_definition(self, nodedata, item, user):
270  addref = ua.AddReferencesItem()
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
276  self._add_reference(addref, user)
277 
278  def delete_nodes(self, deletenodeitems, user=User.Admin):
279  results = []
280  for item in deletenodeitems.NodesToDelete:
281  results.append(self._delete_node(item, user))
282  return results
283 
284  def _delete_node(self, item, user):
285  if user != User.Admin:
286  return ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
287 
288  if item.NodeId not in self._aspace:
289  self.logger.warning("DeleteNodesItem: node does not exists")
290  return ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown)
291 
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)
297 
298  self._delete_node_callbacks(self._aspace[item.NodeId])
299 
300  del(self._aspace[item.NodeId])
301 
302  return ua.StatusCode()
303 
304  def _delete_node_callbacks(self, nodedata):
305  if ua.AttributeIds.Value in nodedata.attributes:
306  for handle, callback in nodedata.attributes[ua.AttributeIds.Value].datachange_callbacks.items():
307  try:
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)
312 
313  def add_references(self, refs, user=User.Admin):
314  result = []
315  for ref in refs:
316  result.append(self._add_reference(ref, user))
317  return result
318 
319  def _add_reference(self, addref, user):
320  if addref.SourceNodeId not in self._aspace:
321  return ua.StatusCode(ua.StatusCodes.BadSourceNodeIdInvalid)
322  if addref.TargetNodeId not in self._aspace:
323  return ua.StatusCode(ua.StatusCodes.BadTargetNodeIdInvalid)
324  if user != User.Admin:
325  return ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
326  rdesc = ua.ReferenceDescription()
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
332  if bname:
333  rdesc.BrowseName = bname
334  dname = self._aspace.get_attribute_value(addref.TargetNodeId, ua.AttributeIds.DisplayName).Value.Value
335  if dname:
336  rdesc.DisplayName = dname
337  self._aspace[addref.SourceNodeId].references.append(rdesc)
338  return ua.StatusCode()
339 
340  def delete_references(self, refs, user=User.Admin):
341  result = []
342  for ref in refs:
343  result.append(self._delete_reference(ref, user))
344  return result
345 
346  def _delete_reference(self, item, user):
347  if item.SourceNodeId not in self._aspace:
348  return ua.StatusCode(ua.StatusCodes.BadSourceNodeIdInvalid)
349  if item.TargetNodeId not in self._aspace:
350  return ua.StatusCode(ua.StatusCodes.BadTargetNodeIdInvalid)
351  if user != User.Admin:
352  return ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
353 
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)
360 
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)
367 
368  return ua.StatusCode()
369 
370  def _add_node_attr(self, item, nodedata, name, vtype=None):
371  if item.SpecifiedAttributes & getattr(ua.NodeAttributesMask, name):
372  dv = ua.DataValue(ua.Variant(getattr(item, name), vtype))
373  dv.ServerTimestamp = datetime.utcnow()
374  dv.SourceTimestamp = datetime.utcnow()
375  nodedata.attributes[getattr(ua.AttributeIds, name)] = AttributeValue(dv)
376 
377  def _add_nodeattributes(self, item, nodedata):
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)
400  self._add_node_attr(item, nodedata, "Value")
401 
402 
403 class MethodService(object):
404 
405  def __init__(self, aspace):
406  self.logger = logging.getLogger(__name__)
407  self._aspace = aspace
408 
409  def call(self, methods):
410  results = []
411  for method in methods:
412  results.append(self._call(method))
413  return results
414 
415  def _call(self, method):
416  res = ua.CallMethodResult()
417  if method.ObjectId not in self._aspace or method.MethodId not in self._aspace:
418  res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
419  else:
420  node = self._aspace[method.MethodId]
421  if node.call is None:
422  res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNothingToDo)
423  else:
424  try:
425  res.OutputArguments = node.call(method.ObjectId, *method.InputArguments)
426  for _ in method.InputArguments:
427  res.InputArgumentResults.append(ua.StatusCode())
428  except Exception:
429  self.logger.exception("Error executing method call %s, an exception was raised: ", method)
430  res.StatusCode = ua.StatusCode(ua.StatusCodes.BadUnexpectedError)
431  return res
432 
433 
434 class AddressSpace(object):
435 
436  """
437  The address space object stores all the nodes of the OPC-UA server
438  and helper methods.
439  The methods are thread safe
440  """
441 
442  def __init__(self):
443  self.logger = logging.getLogger(__name__)
444  self._nodes = {}
445  self._lock = RLock() # FIXME: should use multiple reader, one writter pattern
448  self._default_idx = 2
449  self._nodeid_counter = {0: 20000, 1: 2000}
450 
451  def __getitem__(self, nodeid):
452  with self._lock:
453  if nodeid in self._nodes:
454  return self._nodes.__getitem__(nodeid)
455 
456  def __setitem__(self, nodeid, value):
457  with self._lock:
458  return self._nodes.__setitem__(nodeid, value)
459 
460  def __contains__(self, nodeid):
461  with self._lock:
462  return self._nodes.__contains__(nodeid)
463 
464  def __delitem__(self, nodeid):
465  with self._lock:
466  self._nodes.__delitem__(nodeid)
467 
468  def generate_nodeid(self, idx=None):
469  if idx is None:
470  idx = self._default_idx
471  if idx in self._nodeid_counter:
472  self._nodeid_counter[idx] += 1
473  else:
474  self._nodeid_counter[idx] = 1
475  nodeid = ua.NodeId(self._nodeid_counter[idx], idx)
476  with self._lock: # OK since reentrant lock
477  while True:
478  if nodeid in self._nodes:
479  nodeid = self.generate_nodeid(idx)
480  else:
481  return nodeid
482 
483  def keys(self):
484  with self._lock:
485  return self._nodes.keys()
486 
487  def empty(self):
488  """
489  Delete all nodes in address space
490  """
491  with self._lock:
492  self._nodes = {}
493 
494  def dump(self, path):
495  """
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!
498  """
499  # prepare nodes in address space for being serialized
500  for nodeid, ndata in self._nodes.items():
501  # if the node has a reference to a method call, remove it so the object can be serialized
502  if ndata.call is not None:
503  self._nodes[nodeid].call = None
504 
505  with open(path, 'wb') as f:
506  pickle.dump(self._nodes, f, pickle.HIGHEST_PROTOCOL)
507 
508  def load(self, path):
509  """
510  Load address space from a binary file, overwriting everything in the current address space
511  """
512  with open(path, 'rb') as f:
513  self._nodes = pickle.load(f)
514 
515  def make_aspace_shelf(self, path):
516  """
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
521 
522  Note: Intended for slow devices, such as Raspberry Pi, to greatly improve start up time
523  """
524  s = shelve.open(path, "n", protocol=pickle.HIGHEST_PROTOCOL)
525  for nodeid, ndata in self._nodes.keys():
526  s[nodeid.to_string()] = ndata
527  s.close()
528 
529  def load_aspace_shelf(self, path):
530  """
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
533 
534  Note: Intended for slow devices, such as Raspberry Pi, to greatly improve start up time
535  """
536  class LazyLoadingDict(collections.MutableMapping):
537  """
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
541  """
542  def __init__(self, source):
543  self.source = source # python shelf
544  self.cache = {} # internal dict
545 
546  def __getitem__(self, key):
547  # try to get the item (node) from the cache, if it isn't there get it from the shelf
548  try:
549  return self.cache[key]
550  except KeyError:
551  node = self.cache[key] = self.source[key.to_string()]
552  return node
553 
554  def __setitem__(self, key, value):
555  # add a new item to the cache; if this item is in the shelf it is not updated
556  self.cache[key] = value
557 
558  def __contains__(self, key):
559  return key in self.cache or key.to_string() in self.source
560 
561  def __delitem__(self, key):
562  # only deleting items from the cache is allowed
563  del self.cache[key]
564 
565  def __iter__(self):
566  # only the cache can be iterated over
567  return iter(self.cache.keys())
568 
569  def __len__(self):
570  # only returns the length of items in the cache, not unaccessed items in the shelf
571  return len(self.cache)
572 
573  self._nodes = LazyLoadingDict(shelve.open(path, "r"))
574 
575  def get_attribute_value(self, nodeid, attr):
576  with self._lock:
577  self.logger.debug("get attr val: %s %s", nodeid, attr)
578  if nodeid not in self._nodes:
579  dv = ua.DataValue()
580  dv.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown)
581  return dv
582  node = self._nodes[nodeid]
583  if attr not in node.attributes:
584  dv = ua.DataValue()
585  dv.StatusCode = ua.StatusCode(ua.StatusCodes.BadAttributeIdInvalid)
586  return dv
587  attval = node.attributes[attr]
588  if attval.value_callback:
589  return attval.value_callback()
590  return attval.value
591 
592  def set_attribute_value(self, nodeid, attr, value):
593  with self._lock:
594  self.logger.debug("set attr val: %s %s %s", nodeid, attr, value)
595  if nodeid not in self._nodes:
596  return ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown)
597  node = self._nodes[nodeid]
598  if attr not in node.attributes:
599  return ua.StatusCode(ua.StatusCodes.BadAttributeIdInvalid)
600  if not value.SourceTimestamp:
601  value.SourceTimestamp = datetime.utcnow()
602  if not value.ServerTimestamp:
603  value.ServerTimestamp = datetime.utcnow()
604 
605  attval = node.attributes[attr]
606  old = attval.value
607  attval.value = value
608  cbs = []
609  if old.Value != value.Value: # only send call callback when a value change has happend
610  cbs = list(attval.datachange_callbacks.items())
611 
612  for k, v in cbs:
613  try:
614  v(k, value)
615  except Exception as ex:
616  self.logger.exception("Error calling datachange callback %s, %s, %s", k, v, ex)
617 
618  return ua.StatusCode()
619 
620  def add_datachange_callback(self, nodeid, attr, callback):
621  with self._lock:
622  self.logger.debug("set attr callback: %s %s %s", nodeid, attr, callback)
623  if nodeid not in self._nodes:
624  return ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown), 0
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]
630  handle = self._datachange_callback_counter
631  attval.datachange_callbacks[handle] = callback
632  self._handle_to_attribute_map[handle] = (nodeid, attr)
633  return ua.StatusCode(), handle
634 
635  def delete_datachange_callback(self, handle):
636  with self._lock:
637  nodeid, attr = self._handle_to_attribute_map.pop(handle)
638  self._nodes[nodeid].attributes[attr].datachange_callbacks.pop(handle)
639 
640  def add_method_callback(self, methodid, callback):
641  with self._lock:
642  node = self._nodes[methodid]
643  node.call = callback
def add_method_callback(self, methodid, callback)
def _add_node_attr(self, item, nodedata, name, vtype=None)
def add_datachange_callback(self, nodeid, attr, callback)
def __setitem__(self, nodeid, value)
def _add_ref_to_parent(self, nodedata, item, user)
def translate_browsepaths_to_nodeids(self, browsepaths)
def delete_references(self, refs, user=User.Admin)
def get_attribute_value(self, nodeid, attr)
def _suitable_direction(self, desc, isforward)
def delete_nodes(self, deletenodeitems, user=User.Admin)
def write(self, params, user=User.Admin)
def set_attribute_value(self, nodeid, attr, value)
def add_references(self, refs, user=User.Admin)
def _suitable_reftype(self, ref1, ref2, subtypes)
def _add_type_definition(self, nodedata, item, user)
def _find_element_in_node(self, el, nodeid)
def add_nodes(self, addnodeitems, user=User.Admin)


ros_opcua_impl_python_opcua
Author(s): Denis Štogl , Daniel Draper
autogenerated on Tue Jan 19 2021 03:12:43