generate_open62541CCode.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. ###
  4. ### Author: Chris Iatrou (ichrispa@core-vector.net)
  5. ### Version: rev 14
  6. ###
  7. ### This program was created for educational purposes and has been
  8. ### contributed to the open62541 project by the author. All licensing
  9. ### terms for this source is inherited by the terms and conditions
  10. ### specified for by the open62541 project (see the projects readme
  11. ### file for more information on the LGPL terms and restrictions).
  12. ###
  13. ### This program is not meant to be used in a production environment. The
  14. ### author is not liable for any complications arising due to the use of
  15. ### this program.
  16. ###
  17. from __future__ import print_function
  18. from ua_namespace import *
  19. import logging
  20. import argparse
  21. from open62541_XMLPreprocessor import open62541_XMLPreprocessor
  22. logger = logging.getLogger(__name__)
  23. parser = argparse.ArgumentParser(
  24. description="""Parse OPC UA NamespaceXML file(s) and create C code for generating nodes in open62541
  25. generate_open62541CCode.py will first read all XML files passed on the command line, then link and check the namespace. All nodes that fulfill the basic requirements will then be printed as C-Code intended to be included in the open62541 OPC UA Server that will initialize the corresponding namespace.""",
  26. formatter_class=argparse.RawDescriptionHelpFormatter)
  27. parser.add_argument('infiles',
  28. metavar="<namespaceXML>",
  29. nargs='+',
  30. type=argparse.FileType('r'),
  31. help='Namespace XML file(s). Note that the last definition of a node encountered will be used and all prior definitions are discarded.')
  32. parser.add_argument('outputFile',
  33. metavar='<outputFile>',
  34. #type=argparse.FileType('w', 0),
  35. help='The basename for the <output file>.c and <output file>.h files to be generated. This will also be the function name used in the header and c-file.')
  36. parser.add_argument('-i','--ignore',
  37. metavar="<ignoreFile>",
  38. type=argparse.FileType('r'),
  39. action='append',
  40. dest="ignoreFiles",
  41. default=[],
  42. help='Loads a list of NodeIDs stored in ignoreFile (one NodeID per line). The compiler will assume that these Nodes have been creathed externally and not generate any code for them. They will however be linked to from other nodes.')
  43. parser.add_argument('-b','--blacklist',
  44. metavar="<blacklistFile>",
  45. type=argparse.FileType('r'),
  46. action='append',
  47. dest="blacklistFiles",
  48. default=[],
  49. help='Loads a list of NodeIDs stored in blacklistFile (one NodeID per line). Any of the nodeIds encountered in this file will be removed from the namespace prior to compilation. Any references to these nodes will also be removed')
  50. parser.add_argument('-s','--suppress',
  51. metavar="<attribute>",
  52. action='append',
  53. dest="suppressedAttributes",
  54. choices=['description', 'browseName', 'displayName', 'writeMask', 'userWriteMask','nodeid'],
  55. default=[],
  56. help="Suppresses the generation of some node attributes. Currently supported options are 'description', 'browseName', 'displayName', 'writeMask', 'userWriteMask' and 'nodeid'.")
  57. parser.add_argument('-v','--verbose', action='count', help='Make the script more verbose. Can be applied up to 4 times')
  58. if __name__ == '__main__':
  59. args = parser.parse_args()
  60. level = logging.CRITICAL
  61. verbosity = 0
  62. if args.verbose:
  63. verbosity = int(args.verbose)
  64. if (verbosity==1):
  65. level = logging.ERROR
  66. elif (verbosity==2):
  67. level = logging.WARNING
  68. elif (verbosity==3):
  69. level = logging.INFO
  70. elif (verbosity>=4):
  71. level = logging.DEBUG
  72. logging.basicConfig(level=level)
  73. logger.setLevel(logging.INFO)
  74. # Creating the header is tendious. We can skip the entire process if
  75. # the header exists.
  76. #if path.exists(argv[-1]+".c") or path.exists(argv[-1]+".h"):
  77. # log(None, "File " + str(argv[-1]) + " does already exists.", LOG_LEVEL_INFO)
  78. # log(None, "Header generation will be skipped. Delete the header and rerun this script if necessary.", LOG_LEVEL_INFO)
  79. # exit(0)
  80. # Open the output file
  81. outfileh = open(args.outputFile+".h", r"w+")
  82. outfilec = open(args.outputFile+".c", r"w+")
  83. # Create a new namespace
  84. # Note that the name is actually completely symbolic, it has no other
  85. # function but to distinguish this specific class.
  86. # A namespace class acts as a container for nodes. The nodes may belong
  87. # to any number of different OPC-UA namespaces.
  88. ns = opcua_namespace("open62541")
  89. # Clean up the XML files by removing duplicate namespaces and unwanted prefixes
  90. preProc = open62541_XMLPreprocessor()
  91. for xmlfile in args.infiles:
  92. logger.info("Preprocessing " + str(xmlfile.name))
  93. preProc.addDocument(xmlfile.name)
  94. preProc.preprocessAll()
  95. for xmlfile in preProc.getPreProcessedFiles():
  96. logger.info("Parsing " + str(xmlfile))
  97. ns.parseXML(xmlfile)
  98. # We need to notify the open62541 server of the namespaces used to be able to use i.e. ns=3
  99. namespaceArrayNames = preProc.getUsedNamespaceArrayNames()
  100. for key in namespaceArrayNames:
  101. ns.addNamespace(key, namespaceArrayNames[key])
  102. # Remove any temp files - they are not needed after the AST is created
  103. # Removed for debugging
  104. preProc.removePreprocessedFiles()
  105. # Remove blacklisted nodes from the namespace
  106. # Doing this now ensures that unlinkable pointers will be cleanly removed
  107. # during sanitation.
  108. for blacklist in args.blacklistFiles:
  109. for line in blacklist.readlines():
  110. line = line.replace(" ","")
  111. id = line.replace("\n","")
  112. if ns.getNodeByIDString(id) == None:
  113. logger.info("Can't blacklist node, namespace does currently not contain a node with id " + str(id))
  114. else:
  115. ns.removeNodeById(line)
  116. blacklist.close()
  117. # Link the references in the namespace
  118. logger.info("Linking namespace nodes and references")
  119. ns.linkOpenPointers()
  120. # Remove nodes that are not printable or contain parsing errors, such as
  121. # unresolvable or no references or invalid NodeIDs
  122. ns.sanitize()
  123. # Parse Datatypes in order to find out what the XML keyed values actually
  124. # represent.
  125. # Ex. <rpm>123</rpm> is not encodable
  126. # only after parsing the datatypes, it is known that
  127. # rpm is encoded as a double
  128. logger.info("Building datatype encoding rules")
  129. ns.buildEncodingRules()
  130. # Allocate/Parse the data values. In order to do this, we must have run
  131. # buidEncodingRules.
  132. logger.info("Allocating variables")
  133. ns.allocateVariables()
  134. # Users may have manually defined some nodes in their code already (such as serverStatus).
  135. # To prevent those nodes from being reprinted, we will simply mark them as already
  136. # converted to C-Code. That way, they will still be reffered to by other nodes, but
  137. # they will not be created themselves.
  138. ignoreNodes = []
  139. for ignore in args.ignoreFiles:
  140. for line in ignore.readlines():
  141. line = line.replace(" ","")
  142. id = line.replace("\n","")
  143. if ns.getNodeByIDString(id) == None:
  144. logger.warn("Can't ignore node, Namespace does currently not contain a node with id " + str(id))
  145. else:
  146. ignoreNodes.append(ns.getNodeByIDString(id))
  147. ignore.close()
  148. # Create the C Code
  149. logger.info("Generating Header")
  150. # Returns a tuple of (["Header","lines"],["Code","lines","generated"])
  151. from os.path import basename
  152. generatedCode=ns.printOpen62541Header(ignoreNodes, args.suppressedAttributes, outfilename=basename(args.outputFile))
  153. for line in generatedCode[0]:
  154. outfileh.write(line+"\n")
  155. for line in generatedCode[1]:
  156. outfilec.write(line+"\n")
  157. outfilec.close()
  158. outfileh.close()
  159. exit(0)