mavgen.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. #!/usr/bin/env python
  2. '''
  3. parse a MAVLink protocol XML file and generate a python implementation
  4. Copyright Andrew Tridgell 2011
  5. Released under GNU GPL version 3 or later
  6. '''
  7. from __future__ import print_function
  8. from future import standard_library
  9. standard_library.install_aliases()
  10. from builtins import object
  11. import os
  12. import re
  13. import sys
  14. from . import mavparse
  15. # XSD schema file
  16. schemaFile = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mavschema.xsd")
  17. # Set defaults for generating MAVLink code
  18. DEFAULT_WIRE_PROTOCOL = mavparse.PROTOCOL_1_0
  19. DEFAULT_LANGUAGE = 'Python'
  20. DEFAULT_ERROR_LIMIT = 200
  21. DEFAULT_VALIDATE = True
  22. DEFAULT_STRICT_UNITS = False
  23. MAXIMUM_INCLUDE_FILE_NESTING = 5
  24. # List the supported languages. This is done globally because it's used by the GUI wrapper too
  25. supportedLanguages = ["C", "CS", "JavaScript", "Python", "WLua", "ObjC", "Swift", "Java", "C++11"]
  26. def mavgen(opts, args):
  27. """Generate mavlink message formatters and parsers (C and Python ) using options
  28. and args where args are a list of xml files. This function allows python
  29. scripts under Windows to control mavgen using the same interface as
  30. shell scripts under Unix"""
  31. xml = []
  32. all_files = set()
  33. # Enable validation by default, disabling it if explicitly requested
  34. if opts.validate:
  35. try:
  36. from lxml import etree
  37. with open(schemaFile, 'r') as f:
  38. xmlschema_root = etree.parse(f)
  39. if not opts.strict_units:
  40. # replace the strict "SI_Unit" list of known unit strings with a more generic "xs:string" type
  41. for elem in xmlschema_root.iterfind('xs:attribute[@name="units"]', xmlschema_root.getroot().nsmap):
  42. elem.set("type", "xs:string")
  43. xmlschema = etree.XMLSchema(xmlschema_root)
  44. except ImportError:
  45. print("WARNING: Failed to import lxml module etree. Are lxml, libxml2 and libxslt installed? XML validation will not be performed", file=sys.stderr)
  46. opts.validate = False
  47. except etree.XMLSyntaxError as err:
  48. print("WARNING: XML Syntax Errors detected in %s XML schema file. XML validation will not be performed" % schemaFile, file=sys.stderr)
  49. print(str(err.error_log), file=sys.stderr)
  50. opts.validate = False
  51. except:
  52. print("WARNING: Unable to load XML validator libraries. XML validation will not be performed", file=sys.stderr)
  53. opts.validate = False
  54. def expand_includes():
  55. """Expand includes in current list of all files, ignoring those already parsed."""
  56. for x in xml[:]:
  57. for i in x.include:
  58. fname = os.path.join(os.path.dirname(x.filename), i)
  59. # Only parse new include files
  60. if fname in all_files:
  61. continue
  62. all_files.add(fname)
  63. # Validate XML file with XSD file if possible.
  64. if opts.validate:
  65. print("Validating %s" % fname)
  66. if not mavgen_validate(fname):
  67. return False
  68. else:
  69. print("Validation skipped for %s." % fname)
  70. # Parsing
  71. print("Parsing %s" % fname)
  72. xml.append(mavparse.MAVXML(fname, opts.wire_protocol))
  73. # include message lengths and CRCs too
  74. x.message_crcs.update(xml[-1].message_crcs)
  75. x.message_lengths.update(xml[-1].message_lengths)
  76. x.message_min_lengths.update(xml[-1].message_min_lengths)
  77. x.message_flags.update(xml[-1].message_flags)
  78. x.message_target_system_ofs.update(xml[-1].message_target_system_ofs)
  79. x.message_target_component_ofs.update(xml[-1].message_target_component_ofs)
  80. x.message_names.update(xml[-1].message_names)
  81. x.largest_payload = max(x.largest_payload, xml[-1].largest_payload)
  82. def mavgen_validate(xmlfile):
  83. """Uses lxml to validate an XML file. We define mavgen_validate
  84. here because it relies on the XML libs that were loaded in mavgen(), so it can't be called standalone"""
  85. xmlvalid = True
  86. try:
  87. with open(xmlfile, 'r') as f:
  88. xmldocument = etree.parse(f)
  89. xmlschema.assertValid(xmldocument)
  90. forbidden_names_re = re.compile("^(break$|case$|class$|catch$|const$|continue$|debugger$|default$|delete$|do$|else$|\
  91. export$|extends$|finally$|for$|function$|if$|import$|in$|instanceof$|let$|new$|\
  92. return$|super$|switch$|this$|throw$|try$|typeof$|var$|void$|while$|with$|yield$|\
  93. enum$|await$|implements$|package$|protected$|static$|interface$|private$|public$|\
  94. abstract$|boolean$|byte$|char$|double$|final$|float$|goto$|int$|long$|native$|\
  95. short$|synchronized$|transient$|volatile$).*", re.IGNORECASE)
  96. for element in xmldocument.iter('enum', 'entry', 'message', 'field'):
  97. if forbidden_names_re.search(element.get('name')):
  98. print("Validation error:", file=sys.stderr)
  99. print("Element : %s at line : %s contains forbidden word" % (element.tag, element.sourceline), file=sys.stderr)
  100. xmlvalid = False
  101. return xmlvalid
  102. except etree.XMLSchemaError:
  103. return False
  104. except etree.DocumentInvalid as err:
  105. sys.exit('ERROR: %s' % str(err.error_log))
  106. return True
  107. # Process all XML files, validating them as necessary.
  108. for fname in args:
  109. # only add each dialect file argument once.
  110. if fname in all_files:
  111. continue
  112. all_files.add(fname)
  113. if opts.validate:
  114. print("Validating %s" % fname)
  115. if not mavgen_validate(fname):
  116. return False
  117. else:
  118. print("Validation skipped for %s." % fname)
  119. print("Parsing %s" % fname)
  120. xml.append(mavparse.MAVXML(fname, opts.wire_protocol))
  121. # expand includes
  122. for i in range(MAXIMUM_INCLUDE_FILE_NESTING):
  123. len_allfiles = len(all_files)
  124. expand_includes()
  125. if len(all_files) == len_allfiles:
  126. # stop when loop doesn't add any more included files
  127. break
  128. # work out max payload size across all includes
  129. largest_payload = max(x.largest_payload for x in xml) if xml else 0
  130. for x in xml:
  131. x.largest_payload = largest_payload
  132. if mavparse.check_duplicates(xml):
  133. sys.exit(1)
  134. print("Found %u MAVLink message types in %u XML files" % (
  135. mavparse.total_msgs(xml), len(xml)))
  136. # convert language option to lowercase and validate
  137. opts.language = opts.language.lower()
  138. if opts.language == 'python':
  139. from . import mavgen_python
  140. mavgen_python.generate(opts.output, xml)
  141. elif opts.language == 'c':
  142. from . import mavgen_c
  143. mavgen_c.generate(opts.output, xml)
  144. elif opts.language == 'wlua':
  145. from . import mavgen_wlua
  146. mavgen_wlua.generate(opts.output, xml)
  147. elif opts.language == 'cs':
  148. from . import mavgen_cs
  149. mavgen_cs.generate(opts.output, xml)
  150. elif opts.language == 'javascript':
  151. from . import mavgen_javascript
  152. mavgen_javascript.generate(opts.output, xml)
  153. elif opts.language == 'objc':
  154. from . import mavgen_objc
  155. mavgen_objc.generate(opts.output, xml)
  156. elif opts.language == 'swift':
  157. from . import mavgen_swift
  158. mavgen_swift.generate(opts.output, xml)
  159. elif opts.language == 'java':
  160. from . import mavgen_java
  161. mavgen_java.generate(opts.output, xml)
  162. elif opts.language == 'c++11':
  163. from . import mavgen_cpp11
  164. mavgen_cpp11.generate(opts.output, xml)
  165. else:
  166. print("Unsupported language %s" % opts.language)
  167. return True
  168. # build all the dialects in the dialects subpackage
  169. class Opts(object):
  170. def __init__(self, output, wire_protocol=DEFAULT_WIRE_PROTOCOL, language=DEFAULT_LANGUAGE, validate=DEFAULT_VALIDATE, error_limit=DEFAULT_ERROR_LIMIT, strict_units=DEFAULT_STRICT_UNITS):
  171. self.wire_protocol = wire_protocol
  172. self.error_limit = error_limit
  173. self.language = language
  174. self.output = output
  175. self.validate = validate
  176. self.strict_units = strict_units
  177. def mavgen_python_dialect(dialect, wire_protocol):
  178. '''generate the python code on the fly for a MAVLink dialect'''
  179. dialects = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'dialects')
  180. mdef = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..', 'message_definitions')
  181. if wire_protocol == mavparse.PROTOCOL_0_9:
  182. py = os.path.join(dialects, 'v09', dialect + '.py')
  183. xml = os.path.join(dialects, 'v09', dialect + '.xml')
  184. if not os.path.exists(xml):
  185. xml = os.path.join(mdef, 'v0.9', dialect + '.xml')
  186. elif wire_protocol == mavparse.PROTOCOL_1_0:
  187. py = os.path.join(dialects, 'v10', dialect + '.py')
  188. xml = os.path.join(dialects, 'v10', dialect + '.xml')
  189. if not os.path.exists(xml):
  190. xml = os.path.join(mdef, 'v1.0', dialect + '.xml')
  191. else:
  192. py = os.path.join(dialects, 'v20', dialect + '.py')
  193. xml = os.path.join(dialects, 'v20', dialect + '.xml')
  194. if not os.path.exists(xml):
  195. xml = os.path.join(mdef, 'v1.0', dialect + '.xml')
  196. opts = Opts(py, wire_protocol)
  197. # Python 2 to 3 compatibility
  198. try:
  199. import StringIO as io
  200. except ImportError:
  201. import io
  202. # throw away stdout while generating
  203. stdout_saved = sys.stdout
  204. sys.stdout = io.StringIO()
  205. try:
  206. xml = os.path.relpath(xml)
  207. if not mavgen(opts, [xml]):
  208. sys.stdout = stdout_saved
  209. return False
  210. except Exception:
  211. sys.stdout = stdout_saved
  212. raise
  213. sys.stdout = stdout_saved
  214. return True
  215. if __name__ == "__main__":
  216. raise DeprecationWarning("Executable was moved to pymavlink.tools.mavgen")