generate_open62541CCode.py 7.5 KB

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