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


mavlink
Author(s): Lorenz Meier
autogenerated on Sun Apr 7 2019 02:06:02