data/method/mavlink/pymavlink/generator/mavgen_cs.py

388 lines
14 KiB
Python
Raw Permalink Normal View History

2024-07-24 18:30:46 +08:00
#!/usr/bin/env python
'''
parse a MAVLink protocol XML file and generate a C# implementation
Copyright Michael Oborne 2018
Released under GNU GPL version 3 or later
'''
import sys, textwrap, os, time, re
from . import mavparse, mavtemplate
t = mavtemplate.MAVTemplate()
enumtypes = {}
map = {
'float' : 'float',
'double' : 'double',
'char' : 'byte',
'int8_t' : 'sbyte',
'uint8_t' : 'byte',
'uint8_t_mavlink_version' : 'B',
'int16_t' : 'short',
'uint16_t' : 'ushort',
'int32_t' : 'int',
'uint32_t' : 'uint',
'int64_t' : 'long',
'uint64_t' : 'ulong',
}
def generate_message_header(f, xml_list):
dedupe = {}
for xml in xml_list:
print("generate_message_header " + xml.basename)
if xml.little_endian:
xml.mavlink_endian = "MAVLINK_LITTLE_ENDIAN"
else:
xml.mavlink_endian = "MAVLINK_BIG_ENDIAN"
if xml.crc_extra:
xml.crc_extra_define = "1"
else:
xml.crc_extra_define = "0"
if xml.command_24bit:
xml.command_24bit_define = "1"
else:
xml.command_24bit_define = "0"
if xml.sort_fields:
xml.aligned_fields_define = "1"
else:
xml.aligned_fields_define = "0"
# work out the included headers
xml.include_list = []
for i in xml.include:
base = i[:-4]
xml.include_list.append(mav_include(base))
if not hasattr(xml , 'message_names_enum'):
xml.message_names_enum = ''
# and message CRCs array
if not hasattr(xml , 'message_infos_array'):
xml.message_infos_array = ''
if xml.command_24bit:
# we sort with primary key msgid, secondary key dialect
for msgid in sorted(xml.message_names.keys()):
name = xml.message_names[msgid]
if name not in dedupe:
dedupe[name] = 1
xml_list[0].message_infos_array += ' new message_info(%u, "%s", %u, %u, %u, typeof( mavlink_%s_t )),\n' % (msgid,
name,
xml.message_crcs[msgid],
xml.message_min_lengths[msgid],
xml.message_lengths[msgid],
name.lower())
xml_list[0].message_names_enum += '\n %s = %u,' % (name, msgid)
else:
for msgid in range(256):
crc = xml.message_crcs.get(msgid, None)
name = xml.message_names.get(msgid, None)
length = xml.message_lengths.get(msgid, None)
if name is not None and name not in dedupe:
dedupe[name] = 1
xml_list[0].message_infos_array += ' new message_info(%u, "%s", %u, %u, %u, typeof( mavlink_%s_t )), // none 24 bit\n' % (msgid,
name,
crc,
length,
length,
name.lower())
xml_list[0].message_names_enum += '\n %s = %u,' % (name, msgid)
# add some extra field attributes for convenience with arrays
for m in xml.enum:
for fe in m.entry[:]:
fe.name = fe.name.replace("NAV_","")
t.write(f, '''
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
public partial class MAVLink
{
public const string MAVLINK_BUILD_DATE = "${parse_time}";
public const string MAVLINK_WIRE_PROTOCOL_VERSION = "${wire_protocol_version}";
public const int MAVLINK_MAX_PAYLOAD_LEN = ${largest_payload};
public const byte MAVLINK_CORE_HEADER_LEN = 9;///< Length of core header (of the comm. layer)
public const byte MAVLINK_CORE_HEADER_MAVLINK1_LEN = 5;///< Length of MAVLink1 core header (of the comm. layer)
public const byte MAVLINK_NUM_HEADER_BYTES = (MAVLINK_CORE_HEADER_LEN + 1);///< Length of all header bytes, including core and stx
public const byte MAVLINK_NUM_CHECKSUM_BYTES = 2;
public const byte MAVLINK_NUM_NON_PAYLOAD_BYTES = (MAVLINK_NUM_HEADER_BYTES + MAVLINK_NUM_CHECKSUM_BYTES);
public const int MAVLINK_MAX_PACKET_LEN = (MAVLINK_MAX_PAYLOAD_LEN + MAVLINK_NUM_NON_PAYLOAD_BYTES + MAVLINK_SIGNATURE_BLOCK_LEN);///< Maximum packet length
public const byte MAVLINK_SIGNATURE_BLOCK_LEN = 13;
public const int MAVLINK_LITTLE_ENDIAN = 1;
public const int MAVLINK_BIG_ENDIAN = 0;
public const byte MAVLINK_STX = ${protocol_marker};
public const byte MAVLINK_STX_MAVLINK1 = 0xFE;
public const byte MAVLINK_ENDIAN = ${mavlink_endian};
public const bool MAVLINK_ALIGNED_FIELDS = (${aligned_fields_define} == 1);
public const byte MAVLINK_CRC_EXTRA = ${crc_extra_define};
public const byte MAVLINK_COMMAND_24BIT = ${command_24bit_define};
public const bool MAVLINK_NEED_BYTE_SWAP = (MAVLINK_ENDIAN == MAVLINK_LITTLE_ENDIAN);
// msgid, name, crc, minlength, length, type
public static message_info[] MAVLINK_MESSAGE_INFOS = new message_info[] {
${message_infos_array}
};
public const byte MAVLINK_VERSION = ${version};
public const byte MAVLINK_IFLAG_SIGNED= 0x01;
public const byte MAVLINK_IFLAG_MASK = 0x01;
public struct message_info
{
public uint msgid { get; internal set; }
public string name { get; internal set; }
public byte crc { get; internal set; }
public uint minlength { get; internal set; }
public uint length { get; internal set; }
public Type type { get; internal set; }
public message_info(uint msgid, string name, byte crc, uint minlength, uint length, Type type)
{
this.msgid = msgid;
this.name = name;
this.crc = crc;
this.minlength = minlength;
this.length = length;
this.type = type;
}
public override string ToString()
{
return String.Format("{0} - {1}",name,msgid);
}
}
public enum MAVLINK_MSG_ID
{
${message_names_enum}
}
''', xml_list[0])
def generate_message_enum_types(xml):
print("generate_message_enum_types: " + xml.filename)
for m in xml.message:
for fld in m.fields:
if fld.array_length == 0:
fld.type = map[fld.type]
if fld.enum != "" and fld.array_length == 0:
enumtypes[fld.enum] = fld.type
print(fld.enum + " is type " + fld.type)
def cleanText(text):
text = text.replace("\n"," ")
text = text.replace("\r"," ")
return text.replace("\"","'")
def generate_message_enums(f, xml):
print("generate_message_enums: " + xml.filename)
# add some extra field attributes for convenience with arrays
for m in xml.enum:
m.description = cleanText(m.description)
m.flags = ""
if m.description.lower().find("bitmask") >= 0 or m.name.lower().find("_flags") >= 0:
m.flags = "[Flags]\n\t"
m.enumtype = enumtypes.get(m.name,"int /*default*/")
for fe in m.entry:
if fe.name.endswith('ENUM_END'):
m.entry.remove(fe)
continue
fe.description = cleanText(fe.description)
fe.name = fe.name.replace(m.name + "_","")
firstchar = re.search('^([0-9])', fe.name )
if firstchar != None and firstchar.group():
fe.name = '_%s' % fe.name
if hasattr(fe, "deprecated") and fe.deprecated is True:
fe.name = '''[Obsolete]
%s''' % fe.name
for p in fe.param:
p.description = cleanText(p.description)
t.write(f, '''
${{enum:
///<summary> ${description} </summary>
${flags}public enum ${name}: ${enumtype}
{
${{entry:///<summary> ${description} |${{param:${description}| }} </summary>
[Description("${description}")]
${name}=${value},
}}
};
}}
''', xml)
def generate_message_footer(f, xml):
t.write(f, '''
}
''', xml)
f.close()
def generate_message_h(f, directory, m):
'''generate per-message header for a XML file'''
m.obsolete = ""
if hasattr(m, "deprecated") and m.deprecated is True:
m.obsolete = "[Obsolete]"
t.write(f, '''
${obsolete}
/// extensions_start ${extensions_start}
[StructLayout(LayoutKind.Sequential,Pack=1,Size=${wire_length})]
///<summary> ${description} </summary>
public struct mavlink_${name_lower}_t
{
public mavlink_${name_lower}_t(${{ordered_fields:${type} ${name},}})
{
${{ordered_fields: this.${name} = ${name};
}}
}
${{ordered_fields: /// <summary>${description} ${enum} ${units} ${display}</summary>
[Units("${units}")]
[Description("${description}")]
${array_prefix} ${type} ${name};
}}
};
''', m)
class mav_include(object):
def __init__(self, base):
self.base = base
def generate_one(fh, basename, xml):
'''generate headers for one XML file'''
directory = os.path.join(basename, xml.basename)
print("Generating CSharp implementation for %s" % xml.basename)
# add some extra field attributes for convenience with arrays
for m in xml.message:
m.msg_name = m.name
if xml.crc_extra:
m.crc_extra_arg = ", %s" % m.crc_extra
else:
m.crc_extra_arg = ""
m.msg_nameid = "MAVLINK_MSG_ID_${name} = ${id}"
m.description = cleanText(m.description)
if m.extensions_start is None:
m.extensions_start = 0;
for f in m.fields:
f.description = cleanText(f.description)
if f.array_length != 0:
f.array_prefix = '[MarshalAs(UnmanagedType.ByValArray,SizeConst=%u)]\n\t\tpublic' % f.array_length
f.array_arg = ', %u' % f.array_length
f.array_return_arg = '%u, ' % (f.array_length)
f.array_tag = ''
f.array_const = 'const '
f.decode_left = "%s.%s = " % (m.name_lower, f.name)
f.decode_right = ''
f.return_type = 'void'
f.return_value = 'void'
f.type = "%s%s" % (map[f.type], '[]')
else:
if f.enum != "":
f.type = "/*" +f.enum + "*/" + f.type;
#f.type = "/*" +f.type + "*/" + f.enum;
f.array_suffix = ''
f.array_prefix = 'public '
f.array_tag = 'BitConverter.To%s' % f.type
if f.type == 'byte':
f.array_tag = 'getByte'
if f.name == 'fixed': # this is a keyword
f.name = '@fixed'
f.array_arg = ''
f.array_return_arg = ''
f.array_const = ''
f.decode_left = "%s.%s = " % (m.name_lower, f.name)
f.decode_right = ''
f.get_arg = ''
f.c_test_value = f.test_value
f.return_type = f.type
# cope with uint8_t_mavlink_version
for m in xml.message:
m.arg_fields = []
m.array_fields = []
m.scalar_fields = []
for f in m.ordered_fields:
if f.array_length != 0:
m.array_fields.append(f)
else:
m.scalar_fields.append(f)
for f in m.fields:
if not f.omit_arg:
m.arg_fields.append(f)
f.putname = f.name
else:
f.putname = f.const_value
for m in xml.message:
generate_message_h(fh, directory, m)
def copy_fixed_headers(directory, xml):
'''copy the fixed protocol headers to the target directory'''
import shutil, filecmp
hlist = {
"1.0": [ 'MavlinkCRC.cs', 'MAVLinkMessage.cs', 'MavlinkParse.cs', 'MavlinkUtil.cs', 'MAVLink.csproj' ],
"2.0": [ 'MavlinkCRC.cs', 'MAVLinkMessage.cs', 'MavlinkParse.cs', 'MavlinkUtil.cs', 'MAVLink.csproj' ]
}
basepath = os.path.dirname(os.path.realpath(__file__))
srcpath = os.path.join(basepath, 'CS')
print("Copying fixed 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)
def generate(basename, xml_list):
'''generate complete MAVLink CSharp implemenation'''
print("generate for protocol %s to %s" % (xml_list[0].wire_protocol_version, basename))
directory = basename
if not os.path.exists(directory):
os.makedirs(directory)
f = open(os.path.join(directory, "mavlink.cs"), mode='w')
generate_message_header(f, xml_list)
for xml1 in xml_list:
generate_message_enum_types(xml1)
for xml2 in xml_list:
generate_message_enums(f, xml2)
for xml3 in xml_list:
generate_one(f, basename, xml3)
generate_message_footer(f,xml_list[0])
copy_fixed_headers(basename, xml_list[0])