123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- #!/usr/bin/env python
- '''
- parse a MAVLink protocol XML file and generate a C++ implementation
- Based on C implementation and require C-library for framing.
- Copyright Andrew Tridgell 2011
- Copyright Vladimir Ermakov 2016
- Released under GNU GPL version 3 or later
- '''
- from __future__ import print_function
- import sys, textwrap, os, time
- from . import mavparse, mavtemplate
- import collections
- import struct
- t = mavtemplate.MAVTemplate()
- def tmax(bit):
- return (1<<bit) - 1
- # numeric limits
- TYPE_MAX = {
- #'float' : float('+inf'),
- #'double' : float('+inf'),
- 'char' : tmax(7),
- 'int8_t' : tmax(7),
- 'uint8_t' : tmax(8),
- 'uint8_t_mavlink_version' : tmax(8),
- 'int16_t' : tmax(15),
- 'uint16_t' : tmax(16),
- 'int32_t' : tmax(31),
- 'uint32_t' : tmax(32),
- 'int64_t' : tmax(63),
- 'uint64_t' : tmax(64),
- }
- # macroses stopwords. Used to replace bad enum entry name.
- MACROSES = {
- 'MIN': 'MIN_',
- 'MAX': 'MAX_',
- 'NO_DATA': 'NO_DATA_', # fix uAvionix enum bug
- }
- EType = collections.namedtuple('EType', ('type', 'max'))
- def generate_main_hpp(directory, xml):
- '''generate main header per XML file'''
- f = open(os.path.join(directory, xml.basename + ".hpp"), mode='w')
- t.write(f, '''
- /** @file
- * @brief MAVLink comm protocol generated from ${basename}.xml
- * @see http://mavlink.org
- */
- #pragma once
- #include <array>
- #include <cstdint>
- #include <sstream>
- #ifndef MAVLINK_STX
- #define MAVLINK_STX ${protocol_marker}
- #endif
- #include "../message.hpp"
- namespace mavlink {
- namespace ${basename} {
- /**
- * Array of msg_entry needed for @p mavlink_parse_char() (trought @p mavlink_get_msg_entry())
- */
- constexpr std::array<mavlink_msg_entry_t, ${message_entry_len}> MESSAGE_ENTRIES {{ ${message_entry_array} }};
- //! MAVLINK VERSION
- constexpr auto MAVLINK_VERSION = ${version};
- // ENUM DEFINITIONS
- ${{enum:
- /** @brief ${description} */
- enum class ${name}${cxx_underlying_type}
- {
- ${{entry_flt: ${name_trim}=${value}, /* ${description} |${{param:${description}| }} */
- }}
- };
- //! ${name} ENUM_END
- constexpr auto ${enum_end_name} = ${enum_end_value};
- }}
- } // namespace ${basename}
- } // namespace mavlink
- // MESSAGE DEFINITIONS
- ${{message:#include "./mavlink_msg_${name_lower}.hpp"
- }}
- // base include
- ${{include_list:#include "../${base}/${base}.hpp"
- }}
- ''', xml)
- f.close()
- def generate_message_hpp(directory, m):
- '''generate per-message header for a XML file'''
- f = open(os.path.join(directory, 'mavlink_msg_%s.hpp' % m.name_lower), mode='w')
- t.write(f, '''
- // MESSAGE ${name} support class
- #pragma once
- namespace mavlink {
- namespace ${dialect_name} {
- namespace msg {
- /**
- * @brief ${name} message
- *
- * ${description}
- */
- struct ${name} : mavlink::Message {
- static constexpr msgid_t MSG_ID = ${id};
- static constexpr size_t LENGTH = ${wire_length};
- static constexpr size_t MIN_LENGTH = ${wire_min_length};
- static constexpr uint8_t CRC_EXTRA = ${crc_extra};
- static constexpr auto NAME = "${name}";
- ${{fields: ${cxx_type} ${name}; /*< ${units} ${description} */
- }}
- inline std::string get_name(void) const override
- {
- return NAME;
- }
- inline Info get_message_info(void) const override
- {
- return { MSG_ID, LENGTH, MIN_LENGTH, CRC_EXTRA };
- }
- inline std::string to_yaml(void) const override
- {
- std::stringstream ss;
- ss << NAME << ":" << std::endl;
- ${{fields: ${to_yaml_code}
- }}
- return ss.str();
- }
- inline void serialize(mavlink::MsgMap &map) const override
- {
- map.reset(MSG_ID, LENGTH);
- ${{ordered_fields: map << ${ser_name};${ser_whitespace}// offset: ${wire_offset}
- }}
- }
- inline void deserialize(mavlink::MsgMap &map) override
- {
- ${{ordered_fields: map >> ${name};${ser_whitespace}// offset: ${wire_offset}
- }}
- }
- };
- } // namespace msg
- } // namespace ${dialect_name}
- } // namespace mavlink
- ''', m)
- f.close()
- def generate_gtestsuite_hpp(directory, xml):
- '''generate gtestsuite.hpp per XML file'''
- f = open(os.path.join(directory, "gtestsuite.hpp"), mode='w')
- t.write(f, '''
- /** @file
- * @brief MAVLink comm testsuite protocol generated from ${basename}.xml
- * @see http://mavlink.org
- */
- #pragma once
- #include <gtest/gtest.h>
- #include "${basename}.hpp"
- #ifdef TEST_INTEROP
- using namespace mavlink;
- #undef MAVLINK_HELPER
- #include "mavlink.h"
- #endif
- ${{message:
- TEST(${dialect_name}, ${name})
- {
- mavlink::mavlink_message_t msg;
- mavlink::MsgMap map1(msg);
- mavlink::MsgMap map2(msg);
- mavlink::${dialect_name}::msg::${name} packet_in{};
- ${{fields: packet_in.${name} = ${cxx_test_value};
- }}
- mavlink::${dialect_name}::msg::${name} packet1{};
- mavlink::${dialect_name}::msg::${name} packet2{};
- packet1 = packet_in;
- //std::cout << packet1.to_yaml() << std::endl;
- packet1.serialize(map1);
- mavlink::mavlink_finalize_message(&msg, 1, 1, packet1.MIN_LENGTH, packet1.LENGTH, packet1.CRC_EXTRA);
- packet2.deserialize(map2);
- ${{fields: EXPECT_EQ(packet1.${name}, packet2.${name});
- }}
- }
- #ifdef TEST_INTEROP
- TEST(${dialect_name}_interop, ${name})
- {
- mavlink_message_t msg;
- // to get nice print
- memset(&msg, 0, sizeof(msg));
- mavlink_${name_lower}_t packet_c {
- ${{ordered_fields: ${c_test_value},}}
- };
- mavlink::${dialect_name}::msg::${name} packet_in{};
- ${{fields: packet_in.${name} = ${cxx_test_value};
- }}
- mavlink::${dialect_name}::msg::${name} packet2{};
- mavlink_msg_${name_lower}_encode(1, 1, &msg, &packet_c);
- // simulate message-handling callback
- [&packet2](const mavlink_message_t *cmsg) {
- MsgMap map2(cmsg);
- packet2.deserialize(map2);
- } (&msg);
- ${{fields: EXPECT_EQ(packet_in.${name}, packet2.${name});
- }}
- #ifdef PRINT_MSG
- PRINT_MSG(msg);
- #endif
- }
- #endif
- }}
- ''', xml)
- f.close()
- def copy_fixed_headers(directory, xml):
- '''copy the fixed protocol headers to the target directory'''
- import shutil, filecmp
- hlist = {
- "2.0": ['message.hpp', 'msgmap.hpp']
- }
- basepath = os.path.dirname(os.path.realpath(__file__))
- srcpath = os.path.join(basepath, 'CPP11/include_v%s' % xml.wire_protocol_version)
- print("Copying fixed C++ headers for protocol %s to %s" % (xml.wire_protocol_version, directory))
- for h in hlist[xml.wire_protocol_version]:
- src = os.path.realpath(os.path.join(srcpath, h))
- dest = os.path.realpath(os.path.join(directory, h))
- if src == dest or (os.path.exists(dest) and filecmp.cmp(src, dest)):
- continue
- shutil.copy(src, dest)
- class mav_include(object):
- def __init__(self, base):
- self.base = base
- def enum_remove_prefix(prefix, s):
- '''remove prefix from enum entry'''
- pl = prefix.split('_')
- sl = s.split('_')
- for i in range(len(pl)):
- if pl[i] == sl[0]:
- sl = sl[1:]
- else:
- break
- if sl[0][0].isdigit():
- sl.insert(0, pl[-1])
- ret = '_'.join(sl)
- return MACROSES.get(ret, ret)
- def fix_int8_t(v):
- '''convert unsigned char value to signed char'''
- return struct.unpack('b', struct.pack('B', v))[0]
- def generate_one(basename, xml):
- '''generate headers for one XML file'''
- directory = os.path.join(basename, xml.basename)
- print("Generating C++ implementation in directory %s" % directory)
- mavparse.mkdir_p(directory)
- if xml.wire_protocol_version != mavparse.PROTOCOL_2_0:
- raise ValueError("C++ implementation only support --wire-protocol=2.0")
- # work out the included headers
- xml.include_list = []
- for i in xml.include:
- base = i[:-4]
- xml.include_list.append(mav_include(base))
- # and message metadata array
- # we sort with primary key msgid
- xml.message_entry_len = len(xml.message_crcs)
- xml.message_entry_array = ', '.join([
- '{%u, %u, %u, %u, %u, %u}' % (
- msgid,
- xml.message_crcs[msgid],
- xml.message_min_lengths[msgid],
- xml.message_flags[msgid],
- xml.message_target_system_ofs[msgid],
- xml.message_target_component_ofs[msgid])
- for msgid in sorted(xml.message_crcs.keys())])
- # store types of fields with enum="" attr
- enum_types = collections.defaultdict(list)
- # add some extra field attributes for convenience with arrays
- for m in xml.message:
- m.dialect_name = xml.basename
- m.msg_name = m.name
- for f in m.fields:
- spaces = 30 - len(f.name)
- f.ser_whitespace = ' ' * (spaces if spaces > 1 else 1)
- f.ser_name = f.name # for most of fields it is name
- to_yaml_cast = '+' if f.type in ['char', 'uint8_t', 'int8_t'] else ''
- if f.enum:
- enum_types[f.enum].append(EType(f.type, TYPE_MAX[f.type]))
- # XXX use TIMESYNC message to test trimmed message decoding
- if m.name == 'TIMESYNC' and f.name == 'ts1':
- f.test_value = 0xAA
- # XXX use V2_EXTENSION to test 0 in array (to_yaml)
- #if m.name == 'V2_EXTENSION' and f.name == 'payload':
- # f.test_value[5] = 0
- if f.array_length != 0:
- f.cxx_type = 'std::array<%s, %s>' % (f.type, f.array_length)
- # XXX sometime test_value is > 127 for int8_t, monkeypatch
- if f.type == 'int8_t':
- f.test_value = [fix_int8_t(v) for v in f.test_value]
- if f.type == 'char':
- f.to_yaml_code = """ss << " %s: \\"" << to_string(%s) << "\\"" << std::endl;""" % (f.name, f.name)
- f.cxx_test_value = 'to_char_array("%s")' % (f.test_value)
- f.c_test_value = '"%s"' % f.test_value
- else:
- f.to_yaml_code = """ss << " %s: [" << to_string(%s) << "]" << std::endl;""" % (f.name, f.name)
- f.cxx_test_value = '{{ %s }}' % ', '.join([str(v) for v in f.test_value])
- f.c_test_value = '{ %s }' % ', '.join([str(v) for v in f.test_value])
- else:
- f.cxx_type = f.type
- f.to_yaml_code = """ss << " %s: " << %s%s << std::endl;""" % (f.name, to_yaml_cast, f.name)
- # XXX sometime test_value is > 127 for int8_t, monkeypatch
- if f.type == 'int8_t':
- f.test_value = fix_int8_t(f.test_value);
- if f.type == 'char':
- f.cxx_test_value = "'%s'" % f.test_value
- elif f.type == 'int64_t':
- f.cxx_test_value = "%sLL" % f.test_value
- elif f.type == 'uint64_t':
- f.cxx_test_value = "%sULL" % f.test_value
- else:
- f.cxx_test_value = f.test_value
- f.c_test_value = f.cxx_test_value
- # cope with uint8_t_mavlink_version
- if f.omit_arg:
- f.ser_name = "%s(%s)" % (f.type, f.const_value)
- # add trimmed filed name to enums
- for e in xml.enum:
- underlying_type = None
- if e.name in enum_types:
- types = enum_types[e.name]
- types.sort(key=lambda x: x.max)
- underlying_type = types[-1]
- # template do not support "if"
- # filter out ENUM_END, it often > than unterlying type may handle
- e.entry_flt = []
- for f in e.entry:
- f.name_trim = enum_remove_prefix(e.name, f.name)
- if not f.end_marker:
- e.entry_flt.append(f)
- # XXX check all values in acceptable range
- if underlying_type and f.value > underlying_type.max:
- raise ValueError("Enum %s::%s = %s > MAX(%s)" % (e.name, f.name_trim, f.value, underlying_type.max))
- elif not underlying_type and f.value > TYPE_MAX['int32_t']:
- # default underlying type is int, usual 32-bit
- underlying_type = EType('int64_t', TYPE_MAX['int64_t'])
- else:
- e.enum_end_name = f.name
- e.enum_end_value = f.value
- e.cxx_underlying_type = ' : ' + underlying_type.type if underlying_type else ''
- generate_main_hpp(directory, xml)
- for m in xml.message:
- generate_message_hpp(directory, m)
- generate_gtestsuite_hpp(directory, xml)
- def generate(basename, xml_list):
- '''generate serialization MAVLink C++ implemenation'''
- print("Generating C headers")
- from . import mavgen_c
- mavgen_c.generate(basename, xml_list)
- for xml in xml_list:
- generate_one(basename, xml)
- copy_fixed_headers(basename, xml_list[0])
|