def setNodeDatatypeRecursive(node, nodeset): if not isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode): raise RuntimeError("Node {}: DataType can only be set for VariableNode and VariableTypeNode".format(str(node.id))) if node.dataType is not None: return # If BaseVariableType if node.id == NodeId("ns=0;i=62"): if node.dataType is None: # Set to default BaseDataType node.dataType = NodeId("ns=0;i=24") return if isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode): typeDefNode = nodeset.getNodeTypeDefinition(node) if typeDefNode is None: # Use the parent type. raise RuntimeError("Cannot get node for HasTypeDefinition of VariableNode " + node.browseName.name + " " + str(node.id)) setNodeDatatypeRecursive(typeDefNode, nodeset) node.dataType = typeDefNode.dataType else: # Use the parent type. if node.parent is None: raise RuntimeError("Parent node not defined for " + node.browseName.name + " " + str(node.id)) setNodeDatatypeRecursive(node.parent, nodeset) node.dataType = node.parent.dataType
def generateNodeCode_begin(node, nodeset, code_global): code = [] codeCleanup = [] code.append("UA_StatusCode retVal = UA_STATUSCODE_GOOD;") # Attributes if isinstance(node, ReferenceTypeNode): code.extend(generateReferenceTypeNodeCode(node)) elif isinstance(node, ObjectNode): code.extend(generateObjectNodeCode(node)) elif isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode): [code1, codeCleanup1, codeGlobal1] = generateVariableNodeCode(node, nodeset) code.extend(code1) codeCleanup.extend(codeCleanup1) code_global.extend(codeGlobal1) elif isinstance(node, VariableTypeNode): [code1, codeCleanup1, codeGlobal1] = generateVariableTypeNodeCode(node, nodeset) code.extend(code1) codeCleanup.extend(codeCleanup1) code_global.extend(codeGlobal1) elif isinstance(node, MethodNode): code.extend(generateMethodNodeCode(node)) elif isinstance(node, ObjectTypeNode): code.extend(generateObjectTypeNodeCode(node)) elif isinstance(node, DataTypeNode): code.extend(generateDataTypeNodeCode(node)) elif isinstance(node, ViewNode): code.extend(generateViewNodeCode(node)) if node.displayName is not None: code.append("attr.displayName = " + generateLocalizedTextCode(node.displayName, alloc=False) + ";") if node.description is not None: code.append("#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS") code.append("attr.description = " + generateLocalizedTextCode(node.description, alloc=False) + ";") code.append("#endif") if node.writeMask is not None: code.append("attr.writeMask = %d;" % node.writeMask) if node.userWriteMask is not None: code.append("attr.userWriteMask = %d;" % node.userWriteMask) # AddNodes call code.append("retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_{},". format(makeCIdentifier(node.__class__.__name__.upper().replace("NODE" ,"")))) code.append(generateNodeIdCode(node.id) + ",") code.append(generateNodeIdCode(node.parent.id if node.parent else NodeId()) + ",") code.append(generateNodeIdCode(node.parentReference.id if node.parent else NodeId()) + ",") code.append(generateQualifiedNameCode(node.browseName) + ",") if isinstance(node, VariableNode) or isinstance(node, ObjectNode): typeDefRef = node.popTypeDef() code.append(generateNodeIdCode(typeDefRef.target) + ",") else: code.append(" UA_NODEID_NULL,") code.append("(const UA_NodeAttributes*)&attr, &UA_TYPES[UA_TYPES_{}ATTRIBUTES],NULL, NULL);". format(makeCIdentifier(node.__class__.__name__.upper().replace("NODE" ,"")))) code.extend(codeCleanup) return "\n".join(code)
def replaceAliases(self, aliases): if str(self.id) in aliases: self.id = NodeId(aliases[self.id]) if isinstance(self, VariableNode) or isinstance( self, VariableTypeNode): if str(self.dataType) in aliases: self.dataType = NodeId(aliases[self.dataType]) new_refs = dict() for ref in self.references: if str(ref.source) in aliases: ref.source = NodeId(aliases[ref.source]) if str(ref.target) in aliases: ref.target = NodeId(aliases[ref.target]) if str(ref.referenceType) in aliases: ref.referenceType = NodeId(aliases[ref.referenceType]) new_refs[ref] = None self.references = new_refs
def getParentReference(self, parentreftypes): # HasSubtype has precedence for ref in self.references: if ref.referenceType == NodeId("ns=0;i=45") and not ref.isForward: return ref for ref in self.references: if ref.referenceType in parentreftypes and not ref.isForward: return ref return None
def setNodeValueRankRecursive(node, nodeset): if not isinstance(node, VariableNode) and not isinstance( node, VariableTypeNode): raise RuntimeError( "Node {}: ValueRank can only be set for VariableNode and VariableTypeNode" .format(str(node.id))) if node.valueRank is not None: return # If BaseVariableType if node.id == NodeId("ns=0;i=62"): if node.valueRank is None: # BaseVariableType always has -2 node.valueRank = -2 return if isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode): typeDefNode = nodeset.getNodeTypeDefinition(node) if typeDefNode is None: # Use the parent type. raise RuntimeError( "Cannot get node for HasTypeDefinition of VariableNode " + node.browseName.name + " " + str(node.id)) if not isinstance(typeDefNode, VariableTypeNode): raise RuntimeError( "Node {} ({}) has an invalid type definition. {} is not a VariableType node." .format(str(node.id), node.browseName.name, str(typeDefNode.id))) setNodeValueRankRecursive(typeDefNode, nodeset) if typeDefNode.valueRank is not None: node.valueRank = typeDefNode.valueRank else: raise RuntimeError( "Node {}: the ValueRank of the parent node is None.".format( str(node.id))) else: if node.parent is None: raise RuntimeError( "Node {}: does not have a parent. Probably the parent node was blacklisted?" .format(str(node.id))) # Check if parent node limits the value rank setNodeValueRankRecursive(node.parent, nodeset) if node.parent.valueRank is not None: node.valueRank = node.parent.valueRank else: raise RuntimeError( "Node {}: the ValueRank of the parent node is None.".format( str(node.id)))
def getDataTypeNode(self, dataType): if isinstance(dataType, string_types): if not valueIsInternalType(dataType): logger.error("Not a valid dataType string: " + dataType) return None return self.nodes[NodeId(self.aliases[dataType])] if isinstance(dataType, NodeId): if dataType.i == 0: return None dataTypeNode = self.nodes[dataType] if not isinstance(dataTypeNode, DataTypeNode): logger.error("Node id " + str(dataType) + " is not reference a valid dataType.") return None return dataTypeNode return None
def getNodeByIDString(self, idStr): # Split id to namespace part and id part m = re.match("ns=([^;]+);(.*)", idStr) if m: ns = m.group(1) # Convert namespace uri to index if not ns.isdigit(): if ns not in self.namespaces: return None ns = self.namespaces.index(ns) idStr = "ns={};{}".format(ns, m.group(2)) nodeId = NodeId(idStr) if not nodeId in self.nodes: return None return self.nodes[nodeId]
def sortNodes(nodeset): # reverse hastypedefinition references to treat only forward references hasTypeDef = NodeId("ns=0;i=40") for u in nodeset.nodes.values(): for ref in u.references: if ref.referenceType == hasTypeDef: ref.isForward = not ref.isForward # Only hierarchical types... relevant_refs = nodeset.getRelevantOrderingReferences() # determine in-degree of unfulfilled references L = [node for node in nodeset.nodes.values() if node.hidden] # ordered list of nodes R = {node.id: node for node in nodeset.nodes.values() if not node.hidden} # remaining nodes in_degree = {id: 0 for id in R.keys()} for u in R.values(): # for each node for ref in u.references: if not ref.referenceType in relevant_refs: continue if nodeset.nodes[ref.target].hidden: continue if ref.isForward: continue in_degree[u.id] += 1 # Print ReferenceType and DataType nodes first. They may be required even # though there is no reference to them. For example if the referencetype is # used in a reference, it must exist. A Variable node may point to a # DataTypeNode in the datatype attribute and not via an explicit reference. Q = { node for node in R.values() if in_degree[node.id] == 0 and (isinstance(node, ReferenceTypeNode) or isinstance(node, DataTypeNode)) } while Q: u = Q.pop() # choose node of zero in-degree and 'remove' it from graph L.append(u) del R[u.id] for ref in u.references: if not ref.referenceType in relevant_refs: continue if nodeset.nodes[ref.target].hidden: continue if not ref.isForward: continue in_degree[ref.target] -= 1 if in_degree[ref.target] == 0: Q.add(R[ref.target]) # Order the remaining nodes Q = {node for node in R.values() if in_degree[node.id] == 0} while Q: u = Q.pop() # choose node of zero in-degree and 'remove' it from graph L.append(u) del R[u.id] for ref in u.references: if not ref.referenceType in relevant_refs: continue if nodeset.nodes[ref.target].hidden: continue if not ref.isForward: continue in_degree[ref.target] -= 1 if in_degree[ref.target] == 0: Q.add(R[ref.target]) # reverse hastype references for u in nodeset.nodes.values(): for ref in u.references: if ref.referenceType == hasTypeDef: ref.isForward = not ref.isForward if len(L) != len(nodeset.nodes.values()): print(len(L)) stillOpen = "" for id in in_degree: if in_degree[id] == 0: continue node = nodeset.nodes[id] stillOpen += node.browseName.name + "/" + str(node.id) + " = " + str(in_degree[id]) + \ " " + str(node.references) + "\r\n" raise Exception( "Node graph is circular on the specified references. Still open nodes:\r\n" + stillOpen) return L
nsCount += 1 # # We need to notify the open62541 server of the namespaces used to be able to use i.e. ns=3 # namespaceArrayNames = preProc.getUsedNamespaceArrayNames() # for key in namespaceArrayNames: # ns.addNamespace(key, namespaceArrayNames[key]) # Set the nodes from the ignore list to hidden. This removes them from # dependency calculation and from printing their generated code. These nodes # should be already pre-created on the server to avoid any errors during # creation. for ignoreFile in args.ignoreFiles: for line in ignoreFile.readlines(): line = line.replace(" ", "") id = line.replace("\n", "") ns.hide_node(NodeId(id)) #if not ns.hide_node(NodeId(id)): # logger.info("Cannot ignore node, namespace does currently not contain a node with id " + str(id)) ignoreFile.close() # Remove nodes that are not printable or contain parsing errors, such as # unresolvable or no references or invalid NodeIDs ns.sanitize() # Generate the BSD file from the XML. ns.generateParser(args.existing, args.infiles, args.bsdFile) # Allocate/Parse the data values. In order to do this, we must have run # buidEncodingRules. ns.allocateVariables()
logger = logging.getLogger(__name__) if sys.version_info[0] >= 3: # strings are already parsed to unicode def unicode(s): return s string_types = str else: string_types = basestring #################### # Helper Functions # #################### hassubtype = NodeId("ns=0;i=45") def getSubTypesOf(nodeset, node, skipNodes=[]): if node in skipNodes: return [] re = set() re.add(node) for ref in node.references: if (ref.referenceType == hassubtype): skipAll = set() skipAll.update(skipNodes) skipAll.update(re) if (ref.source == node.id and ref.isForward): re.update(getSubTypesOf(nodeset, nodeset.nodes[ref.target], skipNodes=skipAll)) elif (ref.target == node.id and not ref.isForward): re.update(getSubTypesOf(nodeset, nodeset.nodes[ref.source], skipNodes=skipAll))
def RefOrAlias(s): try: return NodeId(s) except Exception: return s
def buildEncoding(self, nodeset, indent=0, force=False, namespaceMapping=None): """ buildEncoding() determines the structure and aliases used for variables of this DataType. The function will parse the XML <Definition> of the dataType and extract "Name"-"Type" tuples. If successful, buildEncoding will return a nested list of the following format: [['Alias1', ['Alias2', ['BuiltinType']]], [Alias2, ['BuiltinType']], ...] Aliases are fieldnames defined by this DataType or DataTypes referenced. A list such as ['DataPoint', ['Int32']] indicates that a value will encode an Int32 with the alias 'DataPoint' such as <DataPoint>12827</DataPoint>. Only the first Alias of a nested list is considered valid for the BuiltinType. Single-Elemented lists are always BuiltinTypes. Every nested list must converge in a builtin type to be encodable. buildEncoding will follow the first type inheritance reference (hasSupertype) of the dataType if necessary; If instead to "DataType" a numeric "Value" attribute is encountered, the DataType will be considered an enumeration and all Variables using it will be encoded as Int32. DataTypes can be either structures or enumeration - mixed definitions will be unencodable. Calls to getEncoding() will be iterative. buildEncoding() can be called only once per dataType, with all following calls returning the predetermined value. Use of the 'force=True' parameter will force the Definition to be reparsed. After parsing, __definition__ holds the field definition as a list. Note that this might deviate from the encoding, especially if inheritance was used. """ prefix = " " + "|" * indent + "+" if force == True: self.__encodable__ = None if self.__encodable__ is not None and self.__encodable__: if self.isEncodable(): logger.debug(prefix + str(self.__baseTypeEncoding__) + " (already analyzed)") else: logger.debug(prefix + str(self.__baseTypeEncoding__) + "(already analyzed, not encodable!)") return self.__baseTypeEncoding__ self.__encodable__ = True if indent == 0: logger.debug("Parsing DataType " + str(self.browseName) + " (" + str(self.id) + ")") if valueIsInternalType(self.browseName.name): self.__baseTypeEncoding__ = [self.browseName.name] self.__encodable__ = True logger.debug(prefix + str(self.browseName) + "*") logger.debug("Encodable as: " + str(self.__baseTypeEncoding__)) logger.debug("") return self.__baseTypeEncoding__ # Check if there is a supertype available parentType = None for ref in self.references: if ref.isForward: continue # hasSubtype if ref.referenceType.i == 45: targetNode = nodeset.nodes[ref.target] if targetNode is not None and isinstance( targetNode, DataTypeNode): parentType = targetNode break if self.__xmlDefinition__ is None: if parentType is not None: logger.debug(prefix + "Attempting definition using supertype " + str(targetNode.browseName) + " for DataType " + " " + str(self.browseName)) subenc = targetNode.buildEncoding( nodeset=nodeset, indent=indent + 1, namespaceMapping=namespaceMapping) if not targetNode.isEncodable(): self.__encodable__ = True else: self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [ self.browseName.name, subenc, None, 'false' ] if len(self.__baseTypeEncoding__) == 0: logger.debug(prefix + "No viable definition for " + str(self.browseName) + " " + str(self.id) + " found.") self.__encodable__ = True if indent == 0: if not self.__encodable__: logger.debug("Not encodable (partial): " + str(self.__baseTypeEncoding__)) else: logger.debug("Encodable as: " + str(self.__baseTypeEncoding__)) logger.debug("") return self.__baseTypeEncoding__ isEnum = False # An option set is at the same time also an enum, at least for the encoding below isOptionSet = parentType is not None and parentType.id.ns == 0 and parentType.id.i == 12755 # We need to store the definition as ordered data, but cannot use orderedDict # for backward compatibility with Python 2.6 and 3.4 enumDict = [] typeDict = [] # An XML Definition is provided and will be parsed... now for x in self.__xmlDefinition__.childNodes: if x.nodeType == x.ELEMENT_NODE: fname = "" fdtype = "" enumVal = "" valueRank = None #symbolicName = None arrayDimensions = None isOptional = "" for at, av in x.attributes.items(): if at == "DataType": fdtype = str(av) if fdtype in nodeset.aliases: fdtype = nodeset.aliases[fdtype] elif at == "Name": fname = str(av) elif at == "SymbolicName": # ignore continue # symbolicName = str(av) elif at == "Value": enumVal = int(av) isEnum = True elif at == "ValueRank": valueRank = int(av) elif at == "IsOptional": isOptional = str(av) elif at == "ArrayDimensions": arrayDimensions = int(av) elif at == "AllowSubTypes": # ignore continue else: logger.warn("Unknown Field Attribute " + str(at)) # This can either be an enumeration OR a structure, not both. # Figure out which of the dictionaries gets the newly read value pair if isEnum: # This is an enumeration enumDict.append((fname, enumVal)) continue else: if fdtype == "": # If no datatype given use base datatype fdtype = "i=24" # This might be a subtype... follow the node defined as datatype to find out # what encoding to use fdTypeNodeId = NodeId(fdtype) if namespaceMapping != None: fdTypeNodeId.ns = namespaceMapping[fdTypeNodeId.ns] if not fdTypeNodeId in nodeset.nodes: raise Exception("Node {} not found in nodeset".format( fdTypeNodeId)) dtnode = nodeset.nodes[fdTypeNodeId] # The node in the datatype element was found. we inherit its encoding, # but must still ensure that the dtnode is itself validly encodable typeDict.append([fname, dtnode]) fdtype = str(dtnode.browseName.name) logger.debug(prefix + fname + " : " + fdtype + " -> " + str(dtnode.id)) subenc = dtnode.buildEncoding( nodeset=nodeset, indent=indent + 1, namespaceMapping=namespaceMapping) if isOptional: self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [ [fname, subenc, valueRank, 'true'] ] else: self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [ [fname, subenc, valueRank, 'false'] ] if not dtnode.isEncodable(): # If we inherit an encoding from an unencodable node, this node is # also not encodable self.__encodable__ = True break # If we used inheritance to determine an encoding without alias, there is a # the possibility that lists got double-nested despite of only one element # being encoded, such as [['Int32']] or [['alias',['int32']]]. Remove that # enclosing list. while len(self.__baseTypeEncoding__) == 1 and isinstance( self.__baseTypeEncoding__[0], list): self.__baseTypeEncoding__ = self.__baseTypeEncoding__[0] if isOptionSet == True: self.__isOptionSet__ = True subenc = parentType.buildEncoding( nodeset=nodeset, namespaceMapping=namespaceMapping) if not parentType.isEncodable(): self.__encodable__ = True else: self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [ self.browseName.name, subenc, None ] self.__definition__ = enumDict return self.__baseTypeEncoding__ if isEnum == True: self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + ['Int32'] self.__definition__ = enumDict self.__isEnum__ = True logger.debug(prefix + "Int32* -> enumeration with dictionary " + str(enumDict) + " encodable " + str(self.__encodable__)) return self.__baseTypeEncoding__ if indent == 0: if not self.__encodable__: logger.debug("Not encodable (partial): " + str(self.__baseTypeEncoding__)) else: logger.debug("Encodable as: " + str(self.__baseTypeEncoding__)) self.__isEnum__ = False logger.debug("") self.__definition__ = typeDict return self.__baseTypeEncoding__
def popTypeDef(self): for ref in self.references: if ref.referenceType.i == 40 and ref.isForward: del self.references[ref] return ref return Reference(NodeId(), NodeId(), NodeId(), False)
def popTypeDef(self): for ref in self.references: if ref.referenceType.i == 40 and ref.isForward: self.references.remove(ref) return ref return Reference(NodeId(), NodeId(), NodeId(), False)