address_space.py
Go to the documentation of this file.
00001 from threading import RLock
00002 import logging
00003 from datetime import datetime
00004 import collections
00005 import shelve
00006 try:
00007     import cPickle as pickle
00008 except:
00009     import pickle
00010 
00011 from opcua import ua
00012 from opcua.server.users import User
00013 
00014 
00015 class AttributeValue(object):
00016 
00017     def __init__(self, value):
00018         self.value = value
00019         self.value_callback = None
00020         self.datachange_callbacks = {}
00021 
00022     def __str__(self):
00023         return "AttributeValue({0})".format(self.value)
00024     __repr__ = __str__
00025 
00026 
00027 class NodeData(object):
00028 
00029     def __init__(self, nodeid):
00030         self.nodeid = nodeid
00031         self.attributes = {}
00032         self.references = []
00033         self.call = None
00034 
00035     def __str__(self):
00036         return "NodeData(id:{0}, attrs:{1}, refs:{2})".format(self.nodeid, self.attributes, self.references)
00037     __repr__ = __str__
00038 
00039 
00040 class AttributeService(object):
00041 
00042     def __init__(self, aspace):
00043         self.logger = logging.getLogger(__name__)
00044         self._aspace = aspace
00045 
00046     def read(self, params):
00047         self.logger.debug("read %s", params)
00048         res = []
00049         for readvalue in params.NodesToRead:
00050             res.append(self._aspace.get_attribute_value(readvalue.NodeId, readvalue.AttributeId))
00051         return res
00052 
00053     def write(self, params, user=User.Admin):
00054         self.logger.debug("write %s as user %s", params, user)
00055         res = []
00056         for writevalue in params.NodesToWrite:
00057             if user != User.Admin:
00058                 if writevalue.AttributeId != ua.AttributeIds.Value:
00059                     res.append(ua.StatusCode(ua.StatusCodes.BadUserAccessDenied))
00060                     continue
00061                 al = self._aspace.get_attribute_value(writevalue.NodeId, ua.AttributeIds.AccessLevel)
00062                 ual = self._aspace.get_attribute_value(writevalue.NodeId, ua.AttributeIds.UserAccessLevel)
00063                 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):
00064                     res.append(ua.StatusCode(ua.StatusCodes.BadUserAccessDenied))
00065                     continue
00066             res.append(self._aspace.set_attribute_value(writevalue.NodeId, writevalue.AttributeId, writevalue.Value))
00067         return res
00068 
00069 
00070 class ViewService(object):
00071 
00072     def __init__(self, aspace):
00073         self.logger = logging.getLogger(__name__)
00074         self._aspace = aspace
00075 
00076     def browse(self, params):
00077         self.logger.debug("browse %s", params)
00078         res = []
00079         for desc in params.NodesToBrowse:
00080             res.append(self._browse(desc))
00081         return res
00082 
00083     def _browse(self, desc):
00084         res = ua.BrowseResult()
00085         if desc.NodeId not in self._aspace:
00086             res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
00087             return res
00088         node = self._aspace[desc.NodeId]
00089         for ref in node.references:
00090             if not self._is_suitable_ref(desc, ref):
00091                 continue
00092             res.References.append(ref)
00093         return res
00094 
00095     def _is_suitable_ref(self, desc, ref):
00096         if not self._suitable_direction(desc.BrowseDirection, ref.IsForward):
00097             self.logger.debug("%s is not suitable due to direction", ref)
00098             return False
00099         if not self._suitable_reftype(desc.ReferenceTypeId, ref.ReferenceTypeId, desc.IncludeSubtypes):
00100             self.logger.debug("%s is not suitable due to type", ref)
00101             return False
00102         if desc.NodeClassMask and ((desc.NodeClassMask & ref.NodeClass) == 0):
00103             self.logger.debug("%s is not suitable due to class", ref)
00104             return False
00105         self.logger.debug("%s is a suitable ref for desc %s", ref, desc)
00106         return True
00107 
00108     def _suitable_reftype(self, ref1, ref2, subtypes):
00109         """
00110         """
00111         if not subtypes and ref2.Identifier == ua.ObjectIds.HasSubtype:
00112             return False
00113         if ref1.Identifier == ref2.Identifier:
00114             return True
00115         oktypes = self._get_sub_ref(ref1)
00116         if not subtypes and ua.NodeId(ua.ObjectIds.HasSubtype) in oktypes:
00117             oktypes.remove(ua.NodeId(ua.ObjectIds.HasSubtype))
00118         return ref2 in oktypes
00119 
00120     def _get_sub_ref(self, ref):
00121         res = []
00122         nodedata = self._aspace[ref]
00123         if nodedata is not None:
00124             for ref in nodedata.references:
00125                 if ref.ReferenceTypeId.Identifier == ua.ObjectIds.HasSubtype and ref.IsForward:
00126                     res.append(ref.NodeId)
00127                     res += self._get_sub_ref(ref.NodeId)
00128         return res
00129 
00130     def _suitable_direction(self, desc, isforward):
00131         if desc == ua.BrowseDirection.Both:
00132             return True
00133         if desc == ua.BrowseDirection.Forward and isforward:
00134             return True
00135         if desc == ua.BrowseDirection.Inverse and not isforward:
00136             return True
00137         return False
00138 
00139     def translate_browsepaths_to_nodeids(self, browsepaths):
00140         self.logger.debug("translate browsepath: %s", browsepaths)
00141         results = []
00142         for path in browsepaths:
00143             results.append(self._translate_browsepath_to_nodeid(path))
00144         return results
00145 
00146     def _translate_browsepath_to_nodeid(self, path):
00147         self.logger.debug("looking at path: %s", path)
00148         res = ua.BrowsePathResult()
00149         if path.StartingNode not in self._aspace:
00150             res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
00151             return res
00152         current = path.StartingNode
00153         for el in path.RelativePath.Elements:
00154             nodeid = self._find_element_in_node(el, current)
00155             if not nodeid:
00156                 res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNoMatch)
00157                 return res
00158             current = nodeid
00159         target = ua.BrowsePathTarget()
00160         target.TargetId = current
00161         target.RemainingPathIndex = 4294967295
00162         res.Targets = [target]
00163         return res
00164 
00165     def _find_element_in_node(self, el, nodeid):
00166         nodedata = self._aspace[nodeid]
00167         for ref in nodedata.references:
00168             # FIXME: here we should check other arguments!!
00169             if ref.BrowseName == el.TargetName:
00170                 return ref.NodeId
00171         self.logger.info("element %s was not found in node %s", el, nodeid)
00172         return None
00173 
00174 
00175 class NodeManagementService(object):
00176 
00177     def __init__(self, aspace):
00178         self.logger = logging.getLogger(__name__)
00179         self._aspace = aspace
00180 
00181     def add_nodes(self, addnodeitems, user=User.Admin):
00182         results = []
00183         for item in addnodeitems:
00184             results.append(self._add_node(item, user))
00185         return results
00186 
00187     def _add_node(self, item, user):
00188         result = ua.AddNodesResult()
00189 
00190         # If Identifier of requested NodeId is null we generate a new NodeId using
00191         # the namespace of the nodeid, this is an extention of the spec to allow
00192         # to requests the server to generate a new nodeid in a specified namespace
00193         if item.RequestedNewNodeId.has_null_identifier():
00194             self.logger.debug("RequestedNewNodeId has null identifier, generating Identifier")
00195             nodedata = NodeData(self._aspace.generate_nodeid(item.RequestedNewNodeId.NamespaceIndex))
00196         else:
00197             nodedata = NodeData(item.RequestedNewNodeId)
00198 
00199         if nodedata.nodeid in self._aspace:
00200             self.logger.warning("AddNodesItem: node already exists")
00201             result.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdExists)
00202             return result
00203 
00204         if item.ParentNodeId.is_null():
00205             # self.logger.warning("add_node: creating node %s without parent", nodedata.nodeid)
00206             # should return Error here, but the standard namespace define many nodes without parents...
00207             pass
00208         elif item.ParentNodeId not in self._aspace:
00209             self.logger.warning("add_node: while adding node %s, requested parent node %s does not exists", nodedata.nodeid, item.ParentNodeId)
00210             result.StatusCode = ua.StatusCode(ua.StatusCodes.BadParentNodeIdInvalid)
00211             return result
00212 
00213         if not user == User.Admin:
00214             result.StatusCode = ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
00215             return result
00216 
00217         self._add_node_attributes(nodedata, item)
00218 
00219         # now add our node to db
00220         self._aspace[nodedata.nodeid] = nodedata
00221 
00222         if not item.ParentNodeId.is_null():
00223             self._add_ref_from_parent(nodedata, item)
00224             self._add_ref_to_parent(nodedata, item, user)
00225 
00226         # add type definition
00227         if item.TypeDefinition != ua.NodeId():
00228             self._add_type_definition(nodedata, item, user)
00229 
00230         result.StatusCode = ua.StatusCode()
00231         result.AddedNodeId = nodedata.nodeid
00232 
00233         return result
00234 
00235     def _add_node_attributes(self, nodedata, item):
00236         # add common attrs
00237         nodedata.attributes[ua.AttributeIds.NodeId] = AttributeValue(
00238             ua.DataValue(ua.Variant(nodedata.nodeid, ua.VariantType.NodeId))
00239         )
00240         nodedata.attributes[ua.AttributeIds.BrowseName] = AttributeValue(
00241             ua.DataValue(ua.Variant(item.BrowseName, ua.VariantType.QualifiedName))
00242         )
00243         nodedata.attributes[ua.AttributeIds.NodeClass] = AttributeValue(
00244             ua.DataValue(ua.Variant(item.NodeClass, ua.VariantType.Int32))
00245         )
00246         # add requested attrs
00247         self._add_nodeattributes(item.NodeAttributes, nodedata)
00248 
00249     def _add_ref_from_parent(self, nodedata, item):
00250         desc = ua.ReferenceDescription()
00251         desc.ReferenceTypeId = item.ReferenceTypeId
00252         desc.NodeId = nodedata.nodeid
00253         desc.NodeClass = item.NodeClass
00254         desc.BrowseName = item.BrowseName
00255         desc.DisplayName = ua.LocalizedText(item.BrowseName.Name)
00256         desc.TypeDefinition = item.TypeDefinition
00257         desc.IsForward = True
00258         self._aspace[item.ParentNodeId].references.append(desc)
00259 
00260     def _add_ref_to_parent(self, nodedata, item, user):
00261         addref = ua.AddReferencesItem()
00262         addref.ReferenceTypeId = item.ReferenceTypeId
00263         addref.SourceNodeId = nodedata.nodeid
00264         addref.TargetNodeId = item.ParentNodeId
00265         addref.TargetNodeClass = self._aspace[item.ParentNodeId].attributes[ua.AttributeIds.NodeClass].value.Value.Value
00266         addref.IsForward = False
00267         self._add_reference(addref, user)
00268 
00269     def _add_type_definition(self, nodedata, item, user):
00270         addref = ua.AddReferencesItem()
00271         addref.SourceNodeId = nodedata.nodeid
00272         addref.IsForward = True
00273         addref.ReferenceTypeId = ua.NodeId(ua.ObjectIds.HasTypeDefinition)
00274         addref.TargetNodeId = item.TypeDefinition
00275         addref.TargetNodeClass = ua.NodeClass.DataType
00276         self._add_reference(addref, user)
00277 
00278     def delete_nodes(self, deletenodeitems, user=User.Admin):
00279         results = []
00280         for item in deletenodeitems.NodesToDelete:
00281             results.append(self._delete_node(item, user))
00282         return results
00283 
00284     def _delete_node(self, item, user):
00285         if user != User.Admin:
00286             return ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
00287 
00288         if item.NodeId not in self._aspace:
00289             self.logger.warning("DeleteNodesItem: node does not exists")
00290             return ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown)
00291 
00292         if item.DeleteTargetReferences:
00293             for elem in self._aspace.keys():
00294                 for rdesc in self._aspace[elem].references:
00295                     if rdesc.NodeId == item.NodeId:
00296                         self._aspace[elem].references.remove(rdesc)
00297 
00298         self._delete_node_callbacks(self._aspace[item.NodeId])
00299 
00300         del(self._aspace[item.NodeId])
00301 
00302         return ua.StatusCode()
00303 
00304     def _delete_node_callbacks(self, nodedata):
00305         if ua.AttributeIds.Value in nodedata.attributes:
00306             for handle, callback in nodedata.attributes[ua.AttributeIds.Value].datachange_callbacks.items():
00307                 try:
00308                     callback(handle, None, ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown))
00309                     self._aspace.delete_datachange_callback(handle)
00310                 except Exception as ex:
00311                     self.logger.exception("Error calling delete node callback callback %s, %s, %s", nodedata, ua.AttributeIds.Value, ex)
00312 
00313     def add_references(self, refs, user=User.Admin):
00314         result = []
00315         for ref in refs:
00316             result.append(self._add_reference(ref, user))
00317         return result
00318 
00319     def _add_reference(self, addref, user):
00320         if addref.SourceNodeId not in self._aspace:
00321             return ua.StatusCode(ua.StatusCodes.BadSourceNodeIdInvalid)
00322         if addref.TargetNodeId not in self._aspace:
00323             return ua.StatusCode(ua.StatusCodes.BadTargetNodeIdInvalid)
00324         if user != User.Admin:
00325             return ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
00326         rdesc = ua.ReferenceDescription()
00327         rdesc.ReferenceTypeId = addref.ReferenceTypeId
00328         rdesc.IsForward = addref.IsForward
00329         rdesc.NodeId = addref.TargetNodeId
00330         rdesc.NodeClass = addref.TargetNodeClass
00331         bname = self._aspace.get_attribute_value(addref.TargetNodeId, ua.AttributeIds.BrowseName).Value.Value
00332         if bname:
00333             rdesc.BrowseName = bname
00334         dname = self._aspace.get_attribute_value(addref.TargetNodeId, ua.AttributeIds.DisplayName).Value.Value
00335         if dname:
00336             rdesc.DisplayName = dname
00337         self._aspace[addref.SourceNodeId].references.append(rdesc)
00338         return ua.StatusCode()
00339 
00340     def delete_references(self, refs, user=User.Admin):
00341         result = []
00342         for ref in refs:
00343             result.append(self._delete_reference(ref, user))
00344         return result
00345 
00346     def _delete_reference(self, item, user):
00347         if item.SourceNodeId not in self._aspace:
00348             return ua.StatusCode(ua.StatusCodes.BadSourceNodeIdInvalid)
00349         if item.TargetNodeId not in self._aspace:
00350             return ua.StatusCode(ua.StatusCodes.BadTargetNodeIdInvalid)
00351         if user != User.Admin:
00352             return ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
00353 
00354         for rdesc in self._aspace[item.SourceNodeId].references:
00355             if rdesc.NodeId is item.TargetNodeId:
00356                 if rdesc.RefrenceTypeId != item.RefrenceTypeId:
00357                     return ua.StatusCode(ua.StatusCodes.BadReferenceTypeIdInvalid)
00358                 if rdesc.IsForward == item.IsForward or item.DeleteBidirectional:
00359                     self._aspace[item.SourceNodeId].references.remove(rdesc)
00360 
00361         for rdesc in self._aspace[item.TargetNodeId].references:
00362             if rdesc.NodeId is item.SourceNodeId:
00363                 if rdesc.RefrenceTypeId != item.RefrenceTypeId:
00364                     return ua.StatusCode(ua.StatusCodes.BadReferenceTypeIdInvalid)
00365                 if rdesc.IsForward == item.IsForward or item.DeleteBidirectional:
00366                     self._aspace[item.SourceNodeId].references.remove(rdesc)
00367 
00368         return ua.StatusCode()
00369 
00370     def _add_node_attr(self, item, nodedata, name, vtype=None):
00371         if item.SpecifiedAttributes & getattr(ua.NodeAttributesMask, name):
00372             dv = ua.DataValue(ua.Variant(getattr(item, name), vtype))
00373             dv.ServerTimestamp = datetime.utcnow()
00374             dv.SourceTimestamp = datetime.utcnow()
00375             nodedata.attributes[getattr(ua.AttributeIds, name)] = AttributeValue(dv)
00376 
00377     def _add_nodeattributes(self, item, nodedata):
00378         self._add_node_attr(item, nodedata, "AccessLevel", ua.VariantType.Byte)
00379         self._add_node_attr(item, nodedata, "ArrayDimensions", ua.VariantType.UInt32)
00380         self._add_node_attr(item, nodedata, "BrowseName", ua.VariantType.QualifiedName)
00381         self._add_node_attr(item, nodedata, "ContainsNoLoops", ua.VariantType.Boolean)
00382         self._add_node_attr(item, nodedata, "DataType", ua.VariantType.NodeId)
00383         self._add_node_attr(item, nodedata, "Description", ua.VariantType.LocalizedText)
00384         self._add_node_attr(item, nodedata, "DisplayName", ua.VariantType.LocalizedText)
00385         self._add_node_attr(item, nodedata, "EventNotifier", ua.VariantType.Byte)
00386         self._add_node_attr(item, nodedata, "Executable", ua.VariantType.Boolean)
00387         self._add_node_attr(item, nodedata, "Historizing", ua.VariantType.Boolean)
00388         self._add_node_attr(item, nodedata, "InverseName", ua.VariantType.LocalizedText)
00389         self._add_node_attr(item, nodedata, "IsAbstract", ua.VariantType.Boolean)
00390         self._add_node_attr(item, nodedata, "MinimumSamplingInterval", ua.VariantType.Double)
00391         self._add_node_attr(item, nodedata, "NodeClass", ua.VariantType.UInt32)
00392         self._add_node_attr(item, nodedata, "NodeId", ua.VariantType.NodeId)
00393         self._add_node_attr(item, nodedata, "Symmetric", ua.VariantType.Boolean)
00394         self._add_node_attr(item, nodedata, "UserAccessLevel", ua.VariantType.Byte)
00395         self._add_node_attr(item, nodedata, "UserExecutable", ua.VariantType.Boolean)
00396         self._add_node_attr(item, nodedata, "UserWriteMask", ua.VariantType.Byte)
00397         self._add_node_attr(item, nodedata, "ValueRank", ua.VariantType.Int32)
00398         self._add_node_attr(item, nodedata, "WriteMask", ua.VariantType.UInt32)
00399         self._add_node_attr(item, nodedata, "UserWriteMask", ua.VariantType.UInt32)
00400         self._add_node_attr(item, nodedata, "Value")
00401 
00402 
00403 class MethodService(object):
00404 
00405     def __init__(self, aspace):
00406         self.logger = logging.getLogger(__name__)
00407         self._aspace = aspace
00408 
00409     def call(self, methods):
00410         results = []
00411         for method in methods:
00412             results.append(self._call(method))
00413         return results
00414 
00415     def _call(self, method):
00416         res = ua.CallMethodResult()
00417         if method.ObjectId not in self._aspace or method.MethodId not in self._aspace:
00418             res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdInvalid)
00419         else:
00420             node = self._aspace[method.MethodId]
00421             if node.call is None:
00422                 res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNothingToDo)
00423             else:
00424                 try:
00425                     res.OutputArguments = node.call(method.ObjectId, *method.InputArguments)
00426                     for _ in method.InputArguments:
00427                         res.InputArgumentResults.append(ua.StatusCode())
00428                 except Exception:
00429                     self.logger.exception("Error executing method call %s, an exception was raised: ", method)
00430                     res.StatusCode = ua.StatusCode(ua.StatusCodes.BadUnexpectedError)
00431         return res
00432 
00433 
00434 class AddressSpace(object):
00435 
00436     """
00437     The address space object stores all the nodes of the OPC-UA server
00438     and helper methods.
00439     The methods are thread safe
00440     """
00441 
00442     def __init__(self):
00443         self.logger = logging.getLogger(__name__)
00444         self._nodes = {}
00445         self._lock = RLock()  # FIXME: should use multiple reader, one writter pattern
00446         self._datachange_callback_counter = 200
00447         self._handle_to_attribute_map = {}
00448         self._default_idx = 2
00449         self._nodeid_counter = {0: 20000, 1: 2000}
00450 
00451     def __getitem__(self, nodeid):
00452         with self._lock:
00453             if nodeid in self._nodes:
00454                 return self._nodes.__getitem__(nodeid)
00455 
00456     def __setitem__(self, nodeid, value):
00457         with self._lock:
00458             return self._nodes.__setitem__(nodeid, value)
00459 
00460     def __contains__(self, nodeid):
00461         with self._lock:
00462             return self._nodes.__contains__(nodeid)
00463 
00464     def __delitem__(self, nodeid):
00465         with self._lock:
00466             self._nodes.__delitem__(nodeid)
00467 
00468     def generate_nodeid(self, idx=None):
00469         if idx is None:
00470             idx = self._default_idx
00471         if idx in self._nodeid_counter:
00472             self._nodeid_counter[idx] += 1
00473         else:
00474             self._nodeid_counter[idx] = 1
00475         nodeid = ua.NodeId(self._nodeid_counter[idx], idx)
00476         with self._lock:  # OK since reentrant lock
00477             while True:
00478                 if nodeid in self._nodes:
00479                     nodeid = self.generate_nodeid(idx)
00480                 else:
00481                     return nodeid
00482 
00483     def keys(self):
00484         with self._lock:
00485             return self._nodes.keys()
00486 
00487     def empty(self):
00488         """
00489         Delete all nodes in address space
00490         """
00491         with self._lock:
00492             self._nodes = {}
00493 
00494     def dump(self, path):
00495         """
00496         Dump address space as binary to file; note that server must be stopped for this method to work
00497         DO NOT DUMP AN ADDRESS SPACE WHICH IS USING A SHELF (load_aspace_shelf), ONLY CACHED NODES WILL GET DUMPED!
00498         """
00499         # prepare nodes in address space for being serialized
00500         for nodeid, ndata in self._nodes.items():
00501             # if the node has a reference to a method call, remove it so the object can be serialized
00502             if ndata.call is not None:
00503                 self._nodes[nodeid].call = None
00504 
00505         with open(path, 'wb') as f:
00506             pickle.dump(self._nodes, f, pickle.HIGHEST_PROTOCOL)
00507 
00508     def load(self, path):
00509         """
00510         Load address space from a binary file, overwriting everything in the current address space
00511         """
00512         with open(path, 'rb') as f:
00513             self._nodes = pickle.load(f)
00514 
00515     def make_aspace_shelf(self, path):
00516         """
00517         Make a shelf for containing the nodes from the standard address space; this is typically only done on first
00518         start of the server. Subsequent server starts will load the shelf, nodes are then moved to a cache
00519         by the LazyLoadingDict class when they are accessed. Saving data back to the shelf
00520         is currently NOT supported, it is only used for the default OPC UA standard address space
00521 
00522         Note: Intended for slow devices, such as Raspberry Pi, to greatly improve start up time
00523         """
00524         s = shelve.open(path, "n", protocol=pickle.HIGHEST_PROTOCOL)
00525         for nodeid, ndata in self._nodes.keys():
00526             s[nodeid.to_string()] = ndata
00527         s.close()
00528 
00529     def load_aspace_shelf(self, path):
00530         """
00531         Load the standard address space nodes from a python shelve via LazyLoadingDict as needed.
00532         The dump() method can no longer be used if the address space is being loaded from a shelf
00533 
00534         Note: Intended for slow devices, such as Raspberry Pi, to greatly improve start up time
00535         """
00536         class LazyLoadingDict(collections.MutableMapping):
00537             """
00538             Special dict that only loads nodes as they are accessed. If a node is accessed it gets copied from the
00539             shelve to the cache dict. All user nodes are saved in the cache ONLY. Saving data back to the shelf
00540             is currently NOT supported
00541             """
00542             def __init__(self, source):
00543                 self.source = source  # python shelf
00544                 self.cache = {}  # internal dict
00545 
00546             def __getitem__(self, key):
00547                 # try to get the item (node) from the cache, if it isn't there get it from the shelf
00548                 try:
00549                     return self.cache[key]
00550                 except KeyError:
00551                     node = self.cache[key] = self.source[key.to_string()]
00552                     return node
00553 
00554             def __setitem__(self, key, value):
00555                 # add a new item to the cache; if this item is in the shelf it is not updated
00556                 self.cache[key] = value
00557 
00558             def __contains__(self, key):
00559                 return key in self.cache or key.to_string() in self.source
00560 
00561             def __delitem__(self, key):
00562                 # only deleting items from the cache is allowed
00563                 del self.cache[key]
00564 
00565             def __iter__(self):
00566                 # only the cache can be iterated over
00567                 return iter(self.cache.keys())
00568 
00569             def __len__(self):
00570                 # only returns the length of items in the cache, not unaccessed items in the shelf
00571                 return len(self.cache)
00572 
00573         self._nodes = LazyLoadingDict(shelve.open(path, "r"))
00574 
00575     def get_attribute_value(self, nodeid, attr):
00576         with self._lock:
00577             self.logger.debug("get attr val: %s %s", nodeid, attr)
00578             if nodeid not in self._nodes:
00579                 dv = ua.DataValue()
00580                 dv.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown)
00581                 return dv
00582             node = self._nodes[nodeid]
00583             if attr not in node.attributes:
00584                 dv = ua.DataValue()
00585                 dv.StatusCode = ua.StatusCode(ua.StatusCodes.BadAttributeIdInvalid)
00586                 return dv
00587             attval = node.attributes[attr]
00588             if attval.value_callback:
00589                 return attval.value_callback()
00590             return attval.value
00591 
00592     def set_attribute_value(self, nodeid, attr, value):
00593         with self._lock:
00594             self.logger.debug("set attr val: %s %s %s", nodeid, attr, value)
00595             if nodeid not in self._nodes:
00596                 return ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown)
00597             node = self._nodes[nodeid]
00598             if attr not in node.attributes:
00599                 return ua.StatusCode(ua.StatusCodes.BadAttributeIdInvalid)
00600             if not value.SourceTimestamp:
00601                 value.SourceTimestamp = datetime.utcnow()
00602             if not value.ServerTimestamp:
00603                 value.ServerTimestamp = datetime.utcnow()
00604 
00605             attval = node.attributes[attr]
00606             old = attval.value
00607             attval.value = value
00608             cbs = []
00609             if old.Value != value.Value:  # only send call callback when a value change has happend
00610                 cbs = list(attval.datachange_callbacks.items())
00611 
00612         for k, v in cbs:
00613             try:
00614                 v(k, value)
00615             except Exception as ex:
00616                 self.logger.exception("Error calling datachange callback %s, %s, %s", k, v, ex)
00617 
00618         return ua.StatusCode()
00619 
00620     def add_datachange_callback(self, nodeid, attr, callback):
00621         with self._lock:
00622             self.logger.debug("set attr callback: %s %s %s", nodeid, attr, callback)
00623             if nodeid not in self._nodes:
00624                 return ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown), 0
00625             node = self._nodes[nodeid]
00626             if attr not in node.attributes:
00627                 return ua.StatusCode(ua.StatusCodes.BadAttributeIdInvalid), 0
00628             attval = node.attributes[attr]
00629             self._datachange_callback_counter += 1
00630             handle = self._datachange_callback_counter
00631             attval.datachange_callbacks[handle] = callback
00632             self._handle_to_attribute_map[handle] = (nodeid, attr)
00633             return ua.StatusCode(), handle
00634 
00635     def delete_datachange_callback(self, handle):
00636         with self._lock:
00637             nodeid, attr = self._handle_to_attribute_map.pop(handle)
00638             self._nodes[nodeid].attributes[attr].datachange_callbacks.pop(handle)
00639 
00640     def add_method_callback(self, methodid, callback):
00641         with self._lock:
00642             node = self._nodes[methodid]
00643             node.call = callback


ros_opcua_impl_python_opcua
Author(s): Denis Štogl , Daniel Draper
autogenerated on Sat Jun 8 2019 18:26:23