feat(server): Integrate node id header generation into nodeset compiler

This commit is contained in:
Stefan Profanter 2019-10-01 12:58:17 +02:00
parent 1bbcdcb370
commit 0a6ccdbe44
No known key found for this signature in database
GPG Key ID: FB509DF184C9E89E
4 changed files with 142 additions and 14 deletions

View File

@ -229,6 +229,7 @@ endfunction()
# Options:
#
# [INTERNAL] Optional argument. If given, then the generated node set code will use internal headers.
# [GENERATE_IDS] Optional argument. If given, then the generated code will include define directives for numeric node ids
#
# Arguments taking one value:
#
@ -252,7 +253,7 @@ endfunction()
#
function(ua_generate_nodeset)
set(options INTERNAL )
set(options INTERNAL GENERATE_IDS)
set(oneValueArgs NAME TYPES_ARRAY OUTPUT_DIR IGNORE TARGET_PREFIX BLACKLIST)
set(multiValueArgs FILE DEPENDS_TYPES DEPENDS_NS DEPENDS_TARGET)
cmake_parse_arguments(UA_GEN_NS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
@ -311,6 +312,11 @@ function(ua_generate_nodeset)
set(GEN_IGNORE "--ignore=${UA_GEN_NS_IGNORE}")
endif()
set(GEN_IDS "")
if (UA_GEN_NS_GENERATE_IDS)
set(GEN_IDS "--generate-ids")
endif()
set(TYPES_ARRAY_LIST "")
foreach(f ${UA_GEN_NS_DEPENDS_TYPES})
# Replace dash with underscore to make valid c literal
@ -338,13 +344,17 @@ function(ua_generate_nodeset)
file(MAKE_DIRECTORY ${UA_GEN_NS_OUTPUT_DIR})
endif()
string(REPLACE "-" "_" UA_GEN_NS_NAME ${UA_GEN_NS_NAME})
string(TOUPPER "${UA_GEN_NS_NAME}" GEN_NAME_UPPER)
add_custom_command(OUTPUT ${UA_GEN_NS_OUTPUT_DIR}/namespace${FILE_SUFFIX}.c
${UA_GEN_NS_OUTPUT_DIR}/namespace${FILE_SUFFIX}.h
PRE_BUILD
COMMAND ${PYTHON_EXECUTABLE} ${open62541_TOOLS_DIR}/nodeset_compiler/nodeset_compiler.py
${GEN_INTERNAL_HEADERS}
${GEN_NS0}
${GEN_BIN_SIZE}
${GEN_IDS}
--ids-prefix=${GEN_NAME_UPPER}
${GEN_IGNORE}
${GEN_BLACKLIST}
${TYPES_ARRAY_LIST}
@ -376,9 +386,6 @@ function(ua_generate_nodeset)
set_source_files_properties(${UA_GEN_NS_OUTPUT_DIR}/namespace${FILE_SUFFIX}.c PROPERTIES LANGUAGE CXX)
endif()
string(REPLACE "-" "_" UA_GEN_NS_NAME ${UA_GEN_NS_NAME})
string(TOUPPER "${UA_GEN_NS_NAME}" GEN_NAME_UPPER)
set_property(GLOBAL PROPERTY "UA_GEN_NS_DEPENDS_FILE_${UA_GEN_NS_NAME}" ${UA_GEN_NS_DEPENDS_NS} ${UA_GEN_NS_FILE})
set_property(GLOBAL PROPERTY "UA_GEN_NS_DEPENDS_TYPES_${UA_GEN_NS_NAME}" ${UA_GEN_NS_DEPENDS_TYPES} ${UA_GEN_NS_TYPES_ARRAY})
@ -419,7 +426,7 @@ endfunction()
# NAME Short name of the nodeset. E.g. 'di'
# FILE_NS Path to the NodeSet2.xml file. Multiple values can be passed. These nodesets will be combined into one output.
#
# [FILE_CSV] Optional path to the .csv file containing the node ids, e.g. 'OpcUaDiModel.csv'
# [FILE_CSV] Optional path to the .csv file containing the node ids, e.g. 'OpcUaDiModel.csv'. If not given, the nodeid defines will automatically be generated
# [FILE_BSD] Optional path to the .bsd file containing the type definitions, e.g. 'Opc.Ua.Di.Types.bsd'. Multiple files can be
# passed which will all combined to one resulting code.
# [IMPORT_BSD] Optional combination of types array and path to the .bsd file containing additional type definitions referenced by
@ -552,6 +559,10 @@ function(ua_generate_nodeset_and_datatypes)
if (${UA_GEN_INTERNAL})
set(NODESET_INTERNAL "INTERNAL")
endif()
set(NODESET_GENIDS "")
if("${UA_GEN_FILE_CSV}" STREQUAL "")
set(NODESET_GENIDS "GENERATE_IDS")
endif()
ua_generate_nodeset(
NAME "${UA_GEN_NAME}"
@ -559,6 +570,7 @@ function(ua_generate_nodeset_and_datatypes)
TYPES_ARRAY "${NODESET_TYPES_ARRAY}"
BLACKLIST "${UA_GEN_BLACKLIST}"
${NODESET_INTERNAL}
${NODESET_GENIDS}
DEPENDS_TYPES ${TYPES_DEPENDS}
DEPENDS_NS ${NODESET_DEPENDS}
DEPENDS_TARGET ${NODESET_DEPENDS_TARGET}

View File

@ -14,6 +14,7 @@ from __future__ import print_function
from os.path import basename
import logging
import codecs
import re
import os
try:
from StringIO import StringIO
@ -122,7 +123,86 @@ def sortNodes(nodeset):
# Generate C Code #
###################
def generateOpen62541Code(nodeset, outfilename, internal_headers=False, typesArray=[]):
def generateOpen62541NodeIds(nodeset, generate_ids_prefix, generate_ids_ns_start):
ids = StringIO()
ids.write(u"""#ifndef UA_NODEIDS_{prefix}_H_
#define UA_NODEIDS_{prefix}_H_
/**
* Namespace Zero NodeIds
* ----------------------
* Numeric identifiers of standard-defined nodes in namespace zero. The
* following definitions are autogenerated by the nodeset compiler */
""".format(prefix=generate_ids_prefix))
# get subset of nodes we should print and have numeric id
if (sys.version_info > (3, 0)):
nodes_filtered = {k: node for k, node in nodeset.nodes.items() if node.id.ns >= generate_ids_ns_start and not node.id.i is None}
else:
nodes_filtered = {k: node for k, node in nodeset.nodes.iteritems() if node.id.ns >= generate_ids_ns_start and not node.id.i is None}
# sort the nodes with key numeric identifier
nodes_filtered = sorted(nodes_filtered, key=lambda n: (n.ns, n.i))
def isNodeIdRoot(node):
# If node type is one of these types we just take the name as it is
return node.getTypeAsString() in [ "DataType", "ObjectType", "ReferenceType", "VariableType"]
def cleanDefineName(name):
return re.sub('[=\-\.:\s]', '_', name)
for nid in nodes_filtered:
node = nodeset.nodes[nid]
name = cleanDefineName(node.symbolicName.value if node.symbolicName is not None else node.browseName.name)
n1 = node
# Recursively walk up through the node's parents until we reach the base node and construct the name
# along the path. Base node is determined by isNodeIdRoot
while n1 is not None:
if isNodeIdRoot(n1):
break
parent = n1.parent
parentref = n1.parentReference
if parent is None:
# check if this node is target of a hasEncoding reference. If yes we need to add an additional "Encoding_" part to the identifier
parentreftypes = (getSubTypesOf(nodeset, nodeset.getNodeByBrowseName("HasEncoding"), []))
parentreftypes = list(map(lambda x: x.id, parentreftypes))
parentref = node.getParentReference(parentreftypes)
if parentref is not None:
parent = nodeset.nodes[parentref.target]
name = "Encoding_" + name
parentref = nodeset.nodes[parentref.referenceType]
skipParents = [ NodeId("i=92"), NodeId("i=93") ]
if parent is None or parent.id.ns < generate_ids_ns_start or parent.id in skipParents:
break
# Stop on organizes references
oragnizesId = NodeId("i=35")
if parentref.id == oragnizesId:
break
n1 = parent
name2 = cleanDefineName(parent.symbolicName.value if parent.symbolicName is not None else parent.browseName.name)
name = name2 + "_" + name
ids.write(u"#define UA_{namespace}ID_{name} {id} /* {description} */\n".format(
namespace=generate_ids_prefix,
name=name.upper(),
id=node.id.i,
description=node.getTypeAsString()))
ids.write(u'''#endif /* UA_NODEIDS_{0}_H_ */ \n'''.format(generate_ids_prefix))
return ids.getvalue()
def generateOpen62541Code(
nodeset,
outfilename,
internal_headers=False,
typesArray=[],
nodeIdsDefinitions=""):
outfilebase = basename(outfilename)
# Printing functions
outfileh = codecs.open(outfilename + ".h", r"w+", encoding='utf-8')
@ -200,7 +280,10 @@ UA_findDataTypeByBinary(const UA_NodeId *typeId);
writeh("""
_UA_BEGIN_DECLS
extern UA_StatusCode %s(UA_Server *server);
""")
writeh(nodeIdsDefinitions)
writeh("""extern UA_StatusCode %s(UA_Server *server);
_UA_END_DECLS

View File

@ -185,6 +185,27 @@ class Node(object):
new_refs.add(ref)
self.references = new_refs
def getTypeAsString(self):
if isinstance(self, ReferenceTypeNode):
return "ReferenceType"
elif isinstance(self, ObjectNode):
return "Object"
elif isinstance(self, VariableNode) and not isinstance(self, VariableTypeNode):
return "Variable"
elif isinstance(self, VariableTypeNode):
return "VariableType"
elif isinstance(self, MethodNode):
return "Method"
elif isinstance(self, ObjectTypeNode):
return "ObjectType"
elif isinstance(self, DataTypeNode):
return "DataType"
elif isinstance(self, ViewNode):
return "View"
else:
return "Unknown"
class ReferenceTypeNode(Node):
def __init__(self, xmlelement=None):
Node.__init__(self)

View File

@ -42,6 +42,17 @@ parser.add_argument('--internal-headers',
dest="internal_headers",
help='Include internal headers instead of amalgamated header')
parser.add_argument('--generate-ids',
action='store_true',
dest="generate_ids",
default=False,
help='Generated defines for node ids in header file for nodesets of the --xml parameter. Default True. This will genera')
parser.add_argument('--ids-prefix',
dest="generate_ids_prefix",
default="NS",
help='Prefix for generated node id defines. Format is UA_{PREFIX}ID_{name}.')
parser.add_argument('-b', '--blacklist',
metavar="<blacklistFile>",
type=argparse.FileType('r'),
@ -122,6 +133,10 @@ for xmlfile in args.existing:
logger.info("Preprocessing (existing) " + str(xmlfile.name))
ns.addNodeSet(xmlfile, True, typesArray=getTypesArray(nsCount))
nsCount +=1
# We only generate nodeids for the nodesets passed as the --xml parameter
nodeIdsNamespaceIndexToGenerate = nsCount
for xmlfile in args.infiles:
if xmlfile.name in loadedFiles:
logger.info("Skipping Nodeset since it is already loaded: {} ".format(xmlfile.name))
@ -131,10 +146,6 @@ for xmlfile in args.infiles:
ns.addNodeSet(xmlfile, typesArray=getTypesArray(nsCount))
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.
@ -185,8 +196,9 @@ logger.info("Generating Code for Backend: {}".format(args.backend))
if args.backend == "open62541":
# Create the C code with the open62541 backend of the compiler
from backend_open62541 import generateOpen62541Code
generateOpen62541Code(ns, args.outputFile, args.internal_headers, args.typesArray)
from backend_open62541 import generateOpen62541Code, generateOpen62541NodeIds
nodeIdsDefinitions = generateOpen62541NodeIds(ns, args.generate_ids_prefix, nodeIdsNamespaceIndexToGenerate)
generateOpen62541Code(ns, args.outputFile, args.internal_headers, args.typesArray, nodeIdsDefinitions)
elif args.backend == "graphviz":
from backend_graphviz import generateGraphvizCode
generateGraphvizCode(ns, filename=args.outputFile)