#!/usr/bin/env/python # -*- coding: utf-8 -*- ### ### Authors: ### - Chris Iatrou (ichrispa@core-vector.net) ### - Julius Pfrommer ### - Stefan Profanter (profanter@fortiss.org) ### ### This program was created for educational purposes and has been ### contributed to the open62541 project by the author. All licensing ### terms for this source is inherited by the terms and conditions ### specified for by the open62541 project (see the projects readme ### file for more information on the LGPL terms and restrictions). ### ### This program is not meant to be used in a production environment. The ### author is not liable for any complications arising due to the use of ### this program. ### from __future__ import print_function from os.path import basename import logging import codecs import os try: from StringIO import StringIO except ImportError: from io import StringIO import sys if sys.version_info[0] >= 3: # strings are already parsed to unicode def unicode(s): return s logger = logging.getLogger(__name__) from datatypes import NodeId from nodes import * from nodeset import * from backend_open62541_nodes import generateNodeCode_begin, generateNodeCode_finish, generateReferenceCode # Kahn's algorithm: https://algocoding.wordpress.com/2015/04/05/topological-sorting-python/ 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 ################### # Generate C Code # ################### def generateOpen62541Code(nodeset, outfilename, generate_ns0=False, internal_headers=False, typesArray=[], encode_binary_size=32000): outfilebase = basename(outfilename) # Printing functions outfileh = codecs.open(outfilename + ".h", r"w+", encoding='utf-8') outfilec = StringIO() def writeh(line): print(unicode(line), end='\n', file=outfileh) def writec(line): print(unicode(line), end='\n', file=outfilec) additionalHeaders = "" if len(typesArray) > 0: for arr in set(typesArray): if arr == "UA_TYPES": continue additionalHeaders += """#include "%s_generated.h"\n""" % arr.lower() # Print the preamble of the generated code writeh("""/* WARNING: This is a generated file. * Any manual changes will be overwritten. */ #ifndef %s_H_ #define %s_H_ """ % (outfilebase.upper(), outfilebase.upper())) if internal_headers: writeh(""" #ifdef UA_ENABLE_AMALGAMATION # include "open62541.h" /* The following declarations are in the open62541.c file so here's needed when compiling nodesets externally */ # ifndef UA_Nodestore_remove //this definition is needed to hide this code in the amalgamated .c file typedef UA_StatusCode (*UA_exchangeEncodeBuffer)(void *handle, UA_Byte **bufPos, const UA_Byte **bufEnd); UA_StatusCode UA_encodeBinary(const void *src, const UA_DataType *type, UA_Byte **bufPos, const UA_Byte **bufEnd, UA_exchangeEncodeBuffer exchangeCallback, void *exchangeHandle) UA_FUNC_ATTR_WARN_UNUSED_RESULT; UA_StatusCode UA_decodeBinary(const UA_ByteString *src, size_t *offset, void *dst, const UA_DataType *type, size_t customTypesSize, const UA_DataType *customTypes) UA_FUNC_ATTR_WARN_UNUSED_RESULT; size_t UA_calcSizeBinary(void *p, const UA_DataType *type); const UA_DataType * UA_findDataTypeByBinary(const UA_NodeId *typeId); # endif // UA_Nodestore_remove #else // UA_ENABLE_AMALGAMATION # include "ua_server.h" #endif %s """ % (additionalHeaders)) else: writeh(""" #ifdef UA_ENABLE_AMALGAMATION # include "open62541.h" #else # include "ua_server.h" #endif %s """ % (additionalHeaders)) writeh(""" _UA_BEGIN_DECLS extern UA_StatusCode %s(UA_Server *server); _UA_END_DECLS #endif /* %s_H_ */""" % \ (outfilebase, outfilebase.upper())) writec("""/* WARNING: This is a generated file. * Any manual changes will be overwritten. */ #include "%s.h" """ % (outfilebase)) # Loop over the sorted nodes logger.info("Reordering nodes for minimal dependencies during printing") sorted_nodes = sortNodes(nodeset) logger.info("Writing code for nodes and references") functionNumber = 0 parentreftypes = getSubTypesOf(nodeset, nodeset.getNodeByBrowseName("HierarchicalReferences")) parentreftypes = list(map(lambda x: x.id, parentreftypes)) printed_ids = set() for node in sorted_nodes: printed_ids.add(node.id) parentref = node.popParentRef(parentreftypes) if not node.hidden: writec("\n/* " + str(node.displayName) + " - " + str(node.id) + " */") code_global = [] code = generateNodeCode_begin(node, nodeset, generate_ns0, parentref, encode_binary_size, code_global) if code is None: writec("/* Ignored. No parent */") nodeset.hide_node(node.id) continue else: if len(code_global) > 0: writec("\n".join(code_global)) writec("\n") writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_begin(UA_Server *server, UA_UInt16* ns) {") if isinstance(node, MethodNode): writec("#ifdef UA_ENABLE_METHODCALLS") writec(code) # Print inverse references leading to this node for ref in node.references: if ref.target not in printed_ids: continue if node.hidden and nodeset.nodes[ref.target].hidden: continue writec(generateReferenceCode(ref)) if node.hidden: continue writec("return retVal;") if isinstance(node, MethodNode): writec("#else") writec("return UA_STATUSCODE_GOOD;") writec("#endif /* UA_ENABLE_METHODCALLS */") writec("}"); writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_finish(UA_Server *server, UA_UInt16* ns) {") if isinstance(node, MethodNode): writec("#ifdef UA_ENABLE_METHODCALLS") writec("return " + generateNodeCode_finish(node)) if isinstance(node, MethodNode): writec("#else") writec("return UA_STATUSCODE_GOOD;") writec("#endif /* UA_ENABLE_METHODCALLS */") writec("}"); functionNumber = functionNumber + 1 writec(""" UA_StatusCode %s(UA_Server *server) { UA_StatusCode retVal = UA_STATUSCODE_GOOD;""" % (outfilebase)) # Generate namespaces (don't worry about duplicates) writec("/* Use namespace ids generated by the server */") writec("UA_UInt16 ns[" + str(len(nodeset.namespaces)) + "];") for i, nsid in enumerate(nodeset.namespaces): nsid = nsid.replace("\"", "\\\"") writec("ns[" + str(i) + "] = UA_Server_addNamespace(server, \"" + nsid + "\");") for i in range(0, functionNumber): writec("retVal |= function_" + outfilebase + "_" + str(i) + "_begin(server, ns);") for i in reversed(range(0, functionNumber)): writec("retVal |= function_" + outfilebase + "_" + str(i) + "_finish(server, ns);") writec("return retVal;\n}") outfileh.flush() os.fsync(outfileh) outfileh.close() fullCode = outfilec.getvalue() outfilec.close() outfilec = codecs.open(outfilename + ".c", r"w+", encoding='utf-8') outfilec.write(fullCode) outfilec.flush() os.fsync(outfilec) outfilec.close()