mavgen_cpp11.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. #!/usr/bin/env python
  2. '''
  3. parse a MAVLink protocol XML file and generate a C++ implementation
  4. Based on C implementation and require C-library for framing.
  5. Copyright Andrew Tridgell 2011
  6. Copyright Vladimir Ermakov 2016
  7. Released under GNU GPL version 3 or later
  8. '''
  9. from __future__ import print_function
  10. import sys, textwrap, os, time
  11. from . import mavparse, mavtemplate
  12. import collections
  13. import struct
  14. t = mavtemplate.MAVTemplate()
  15. def tmax(bit):
  16. return (1<<bit) - 1
  17. # numeric limits
  18. TYPE_MAX = {
  19. #'float' : float('+inf'),
  20. #'double' : float('+inf'),
  21. 'char' : tmax(7),
  22. 'int8_t' : tmax(7),
  23. 'uint8_t' : tmax(8),
  24. 'uint8_t_mavlink_version' : tmax(8),
  25. 'int16_t' : tmax(15),
  26. 'uint16_t' : tmax(16),
  27. 'int32_t' : tmax(31),
  28. 'uint32_t' : tmax(32),
  29. 'int64_t' : tmax(63),
  30. 'uint64_t' : tmax(64),
  31. }
  32. # macroses stopwords. Used to replace bad enum entry name.
  33. MACROSES = {
  34. 'MIN': 'MIN_',
  35. 'MAX': 'MAX_',
  36. 'NO_DATA': 'NO_DATA_', # fix uAvionix enum bug
  37. }
  38. EType = collections.namedtuple('EType', ('type', 'max'))
  39. def generate_main_hpp(directory, xml):
  40. '''generate main header per XML file'''
  41. f = open(os.path.join(directory, xml.basename + ".hpp"), mode='w')
  42. t.write(f, '''
  43. /** @file
  44. * @brief MAVLink comm protocol generated from ${basename}.xml
  45. * @see http://mavlink.org
  46. */
  47. #pragma once
  48. #include <array>
  49. #include <cstdint>
  50. #include <sstream>
  51. #ifndef MAVLINK_STX
  52. #define MAVLINK_STX ${protocol_marker}
  53. #endif
  54. #include "../message.hpp"
  55. namespace mavlink {
  56. namespace ${basename} {
  57. /**
  58. * Array of msg_entry needed for @p mavlink_parse_char() (trought @p mavlink_get_msg_entry())
  59. */
  60. constexpr std::array<mavlink_msg_entry_t, ${message_entry_len}> MESSAGE_ENTRIES {{ ${message_entry_array} }};
  61. //! MAVLINK VERSION
  62. constexpr auto MAVLINK_VERSION = ${version};
  63. // ENUM DEFINITIONS
  64. ${{enum:
  65. /** @brief ${description} */
  66. enum class ${name}${cxx_underlying_type}
  67. {
  68. ${{entry_flt: ${name_trim}=${value}, /* ${description} |${{param:${description}| }} */
  69. }}
  70. };
  71. //! ${name} ENUM_END
  72. constexpr auto ${enum_end_name} = ${enum_end_value};
  73. }}
  74. } // namespace ${basename}
  75. } // namespace mavlink
  76. // MESSAGE DEFINITIONS
  77. ${{message:#include "./mavlink_msg_${name_lower}.hpp"
  78. }}
  79. // base include
  80. ${{include_list:#include "../${base}/${base}.hpp"
  81. }}
  82. ''', xml)
  83. f.close()
  84. def generate_message_hpp(directory, m):
  85. '''generate per-message header for a XML file'''
  86. f = open(os.path.join(directory, 'mavlink_msg_%s.hpp' % m.name_lower), mode='w')
  87. t.write(f, '''
  88. // MESSAGE ${name} support class
  89. #pragma once
  90. namespace mavlink {
  91. namespace ${dialect_name} {
  92. namespace msg {
  93. /**
  94. * @brief ${name} message
  95. *
  96. * ${description}
  97. */
  98. struct ${name} : mavlink::Message {
  99. static constexpr msgid_t MSG_ID = ${id};
  100. static constexpr size_t LENGTH = ${wire_length};
  101. static constexpr size_t MIN_LENGTH = ${wire_min_length};
  102. static constexpr uint8_t CRC_EXTRA = ${crc_extra};
  103. static constexpr auto NAME = "${name}";
  104. ${{fields: ${cxx_type} ${name}; /*< ${units} ${description} */
  105. }}
  106. inline std::string get_name(void) const override
  107. {
  108. return NAME;
  109. }
  110. inline Info get_message_info(void) const override
  111. {
  112. return { MSG_ID, LENGTH, MIN_LENGTH, CRC_EXTRA };
  113. }
  114. inline std::string to_yaml(void) const override
  115. {
  116. std::stringstream ss;
  117. ss << NAME << ":" << std::endl;
  118. ${{fields: ${to_yaml_code}
  119. }}
  120. return ss.str();
  121. }
  122. inline void serialize(mavlink::MsgMap &map) const override
  123. {
  124. map.reset(MSG_ID, LENGTH);
  125. ${{ordered_fields: map << ${ser_name};${ser_whitespace}// offset: ${wire_offset}
  126. }}
  127. }
  128. inline void deserialize(mavlink::MsgMap &map) override
  129. {
  130. ${{ordered_fields: map >> ${name};${ser_whitespace}// offset: ${wire_offset}
  131. }}
  132. }
  133. };
  134. } // namespace msg
  135. } // namespace ${dialect_name}
  136. } // namespace mavlink
  137. ''', m)
  138. f.close()
  139. def generate_gtestsuite_hpp(directory, xml):
  140. '''generate gtestsuite.hpp per XML file'''
  141. f = open(os.path.join(directory, "gtestsuite.hpp"), mode='w')
  142. t.write(f, '''
  143. /** @file
  144. * @brief MAVLink comm testsuite protocol generated from ${basename}.xml
  145. * @see http://mavlink.org
  146. */
  147. #pragma once
  148. #include <gtest/gtest.h>
  149. #include "${basename}.hpp"
  150. #ifdef TEST_INTEROP
  151. using namespace mavlink;
  152. #undef MAVLINK_HELPER
  153. #include "mavlink.h"
  154. #endif
  155. ${{message:
  156. TEST(${dialect_name}, ${name})
  157. {
  158. mavlink::mavlink_message_t msg;
  159. mavlink::MsgMap map1(msg);
  160. mavlink::MsgMap map2(msg);
  161. mavlink::${dialect_name}::msg::${name} packet_in{};
  162. ${{fields: packet_in.${name} = ${cxx_test_value};
  163. }}
  164. mavlink::${dialect_name}::msg::${name} packet1{};
  165. mavlink::${dialect_name}::msg::${name} packet2{};
  166. packet1 = packet_in;
  167. //std::cout << packet1.to_yaml() << std::endl;
  168. packet1.serialize(map1);
  169. mavlink::mavlink_finalize_message(&msg, 1, 1, packet1.MIN_LENGTH, packet1.LENGTH, packet1.CRC_EXTRA);
  170. packet2.deserialize(map2);
  171. ${{fields: EXPECT_EQ(packet1.${name}, packet2.${name});
  172. }}
  173. }
  174. #ifdef TEST_INTEROP
  175. TEST(${dialect_name}_interop, ${name})
  176. {
  177. mavlink_message_t msg;
  178. // to get nice print
  179. memset(&msg, 0, sizeof(msg));
  180. mavlink_${name_lower}_t packet_c {
  181. ${{ordered_fields: ${c_test_value},}}
  182. };
  183. mavlink::${dialect_name}::msg::${name} packet_in{};
  184. ${{fields: packet_in.${name} = ${cxx_test_value};
  185. }}
  186. mavlink::${dialect_name}::msg::${name} packet2{};
  187. mavlink_msg_${name_lower}_encode(1, 1, &msg, &packet_c);
  188. // simulate message-handling callback
  189. [&packet2](const mavlink_message_t *cmsg) {
  190. MsgMap map2(cmsg);
  191. packet2.deserialize(map2);
  192. } (&msg);
  193. ${{fields: EXPECT_EQ(packet_in.${name}, packet2.${name});
  194. }}
  195. #ifdef PRINT_MSG
  196. PRINT_MSG(msg);
  197. #endif
  198. }
  199. #endif
  200. }}
  201. ''', xml)
  202. f.close()
  203. def copy_fixed_headers(directory, xml):
  204. '''copy the fixed protocol headers to the target directory'''
  205. import shutil, filecmp
  206. hlist = {
  207. "2.0": ['message.hpp', 'msgmap.hpp']
  208. }
  209. basepath = os.path.dirname(os.path.realpath(__file__))
  210. srcpath = os.path.join(basepath, 'CPP11/include_v%s' % xml.wire_protocol_version)
  211. print("Copying fixed C++ headers for protocol %s to %s" % (xml.wire_protocol_version, directory))
  212. for h in hlist[xml.wire_protocol_version]:
  213. src = os.path.realpath(os.path.join(srcpath, h))
  214. dest = os.path.realpath(os.path.join(directory, h))
  215. if src == dest or (os.path.exists(dest) and filecmp.cmp(src, dest)):
  216. continue
  217. shutil.copy(src, dest)
  218. class mav_include(object):
  219. def __init__(self, base):
  220. self.base = base
  221. def enum_remove_prefix(prefix, s):
  222. '''remove prefix from enum entry'''
  223. pl = prefix.split('_')
  224. sl = s.split('_')
  225. for i in range(len(pl)):
  226. if pl[i] == sl[0]:
  227. sl = sl[1:]
  228. else:
  229. break
  230. if sl[0][0].isdigit():
  231. sl.insert(0, pl[-1])
  232. ret = '_'.join(sl)
  233. return MACROSES.get(ret, ret)
  234. def fix_int8_t(v):
  235. '''convert unsigned char value to signed char'''
  236. return struct.unpack('b', struct.pack('B', v))[0]
  237. def generate_one(basename, xml):
  238. '''generate headers for one XML file'''
  239. directory = os.path.join(basename, xml.basename)
  240. print("Generating C++ implementation in directory %s" % directory)
  241. mavparse.mkdir_p(directory)
  242. if xml.wire_protocol_version != mavparse.PROTOCOL_2_0:
  243. raise ValueError("C++ implementation only support --wire-protocol=2.0")
  244. # work out the included headers
  245. xml.include_list = []
  246. for i in xml.include:
  247. base = i[:-4]
  248. xml.include_list.append(mav_include(base))
  249. # and message metadata array
  250. # we sort with primary key msgid
  251. xml.message_entry_len = len(xml.message_crcs)
  252. xml.message_entry_array = ', '.join([
  253. '{%u, %u, %u, %u, %u, %u}' % (
  254. msgid,
  255. xml.message_crcs[msgid],
  256. xml.message_min_lengths[msgid],
  257. xml.message_flags[msgid],
  258. xml.message_target_system_ofs[msgid],
  259. xml.message_target_component_ofs[msgid])
  260. for msgid in sorted(xml.message_crcs.keys())])
  261. # store types of fields with enum="" attr
  262. enum_types = collections.defaultdict(list)
  263. # add some extra field attributes for convenience with arrays
  264. for m in xml.message:
  265. m.dialect_name = xml.basename
  266. m.msg_name = m.name
  267. for f in m.fields:
  268. spaces = 30 - len(f.name)
  269. f.ser_whitespace = ' ' * (spaces if spaces > 1 else 1)
  270. f.ser_name = f.name # for most of fields it is name
  271. to_yaml_cast = '+' if f.type in ['char', 'uint8_t', 'int8_t'] else ''
  272. if f.enum:
  273. enum_types[f.enum].append(EType(f.type, TYPE_MAX[f.type]))
  274. # XXX use TIMESYNC message to test trimmed message decoding
  275. if m.name == 'TIMESYNC' and f.name == 'ts1':
  276. f.test_value = 0xAA
  277. # XXX use V2_EXTENSION to test 0 in array (to_yaml)
  278. #if m.name == 'V2_EXTENSION' and f.name == 'payload':
  279. # f.test_value[5] = 0
  280. if f.array_length != 0:
  281. f.cxx_type = 'std::array<%s, %s>' % (f.type, f.array_length)
  282. # XXX sometime test_value is > 127 for int8_t, monkeypatch
  283. if f.type == 'int8_t':
  284. f.test_value = [fix_int8_t(v) for v in f.test_value]
  285. if f.type == 'char':
  286. f.to_yaml_code = """ss << " %s: \\"" << to_string(%s) << "\\"" << std::endl;""" % (f.name, f.name)
  287. f.cxx_test_value = 'to_char_array("%s")' % (f.test_value)
  288. f.c_test_value = '"%s"' % f.test_value
  289. else:
  290. f.to_yaml_code = """ss << " %s: [" << to_string(%s) << "]" << std::endl;""" % (f.name, f.name)
  291. f.cxx_test_value = '{{ %s }}' % ', '.join([str(v) for v in f.test_value])
  292. f.c_test_value = '{ %s }' % ', '.join([str(v) for v in f.test_value])
  293. else:
  294. f.cxx_type = f.type
  295. f.to_yaml_code = """ss << " %s: " << %s%s << std::endl;""" % (f.name, to_yaml_cast, f.name)
  296. # XXX sometime test_value is > 127 for int8_t, monkeypatch
  297. if f.type == 'int8_t':
  298. f.test_value = fix_int8_t(f.test_value);
  299. if f.type == 'char':
  300. f.cxx_test_value = "'%s'" % f.test_value
  301. elif f.type == 'int64_t':
  302. f.cxx_test_value = "%sLL" % f.test_value
  303. elif f.type == 'uint64_t':
  304. f.cxx_test_value = "%sULL" % f.test_value
  305. else:
  306. f.cxx_test_value = f.test_value
  307. f.c_test_value = f.cxx_test_value
  308. # cope with uint8_t_mavlink_version
  309. if f.omit_arg:
  310. f.ser_name = "%s(%s)" % (f.type, f.const_value)
  311. # add trimmed filed name to enums
  312. for e in xml.enum:
  313. underlying_type = None
  314. if e.name in enum_types:
  315. types = enum_types[e.name]
  316. types.sort(key=lambda x: x.max)
  317. underlying_type = types[-1]
  318. # template do not support "if"
  319. # filter out ENUM_END, it often > than unterlying type may handle
  320. e.entry_flt = []
  321. for f in e.entry:
  322. f.name_trim = enum_remove_prefix(e.name, f.name)
  323. if not f.end_marker:
  324. e.entry_flt.append(f)
  325. # XXX check all values in acceptable range
  326. if underlying_type and f.value > underlying_type.max:
  327. raise ValueError("Enum %s::%s = %s > MAX(%s)" % (e.name, f.name_trim, f.value, underlying_type.max))
  328. elif not underlying_type and f.value > TYPE_MAX['int32_t']:
  329. # default underlying type is int, usual 32-bit
  330. underlying_type = EType('int64_t', TYPE_MAX['int64_t'])
  331. else:
  332. e.enum_end_name = f.name
  333. e.enum_end_value = f.value
  334. e.cxx_underlying_type = ' : ' + underlying_type.type if underlying_type else ''
  335. generate_main_hpp(directory, xml)
  336. for m in xml.message:
  337. generate_message_hpp(directory, m)
  338. generate_gtestsuite_hpp(directory, xml)
  339. def generate(basename, xml_list):
  340. '''generate serialization MAVLink C++ implemenation'''
  341. print("Generating C headers")
  342. from . import mavgen_c
  343. mavgen_c.generate(basename, xml_list)
  344. for xml in xml_list:
  345. generate_one(basename, xml)
  346. copy_fixed_headers(basename, xml_list[0])