00001
00002 '''
00003 mavlink python parse functions
00004
00005 Copyright Andrew Tridgell 2011
00006 Released under GNU GPL version 3 or later
00007 '''
00008
00009 import xml.parsers.expat, os, errno, time, sys, operator, struct
00010
00011 PROTOCOL_0_9 = "0.9"
00012 PROTOCOL_1_0 = "1.0"
00013 PROTOCOL_2_0 = "2.0"
00014
00015
00016 FLAG_HAVE_TARGET_SYSTEM = 1
00017 FLAG_HAVE_TARGET_COMPONENT = 2
00018
00019 class MAVParseError(Exception):
00020 def __init__(self, message, inner_exception=None):
00021 self.message = message
00022 self.inner_exception = inner_exception
00023 self.exception_info = sys.exc_info()
00024 def __str__(self):
00025 return self.message
00026
00027 class MAVField(object):
00028 def __init__(self, name, type, print_format, xml, description='', enum=''):
00029 self.name = name
00030 self.name_upper = name.upper()
00031 self.description = description
00032 self.array_length = 0
00033 self.enum = enum
00034 self.omit_arg = False
00035 self.const_value = None
00036 self.print_format = print_format
00037 lengths = {
00038 'float' : 4,
00039 'double' : 8,
00040 'char' : 1,
00041 'int8_t' : 1,
00042 'uint8_t' : 1,
00043 'uint8_t_mavlink_version' : 1,
00044 'int16_t' : 2,
00045 'uint16_t' : 2,
00046 'int32_t' : 4,
00047 'uint32_t' : 4,
00048 'int64_t' : 8,
00049 'uint64_t' : 8,
00050 }
00051
00052 if type=='uint8_t_mavlink_version':
00053 type = 'uint8_t'
00054 self.omit_arg = True
00055 self.const_value = xml.version
00056
00057 aidx = type.find("[")
00058 if aidx != -1:
00059 assert type[-1:] == ']'
00060 self.array_length = int(type[aidx+1:-1])
00061 type = type[0:aidx]
00062 if type == 'array':
00063 type = 'int8_t'
00064 if type in lengths:
00065 self.type_length = lengths[type]
00066 self.type = type
00067 elif (type+"_t") in lengths:
00068 self.type_length = lengths[type+"_t"]
00069 self.type = type+'_t'
00070 else:
00071 raise MAVParseError("unknown type '%s'" % type)
00072 if self.array_length != 0:
00073 self.wire_length = self.array_length * self.type_length
00074 else:
00075 self.wire_length = self.type_length
00076 self.type_upper = self.type.upper()
00077
00078 def gen_test_value(self, i):
00079 '''generate a testsuite value for a MAVField'''
00080 if self.const_value:
00081 return self.const_value
00082 elif self.type == 'float':
00083 return 17.0 + self.wire_offset*7 + i
00084 elif self.type == 'double':
00085 return 123.0 + self.wire_offset*7 + i
00086 elif self.type == 'char':
00087 return chr(ord('A') + (self.wire_offset + i)%26)
00088 elif self.type in [ 'int8_t', 'uint8_t' ]:
00089 return (5 + self.wire_offset*67 + i) & 0xFF
00090 elif self.type in ['int16_t', 'uint16_t']:
00091 return (17235 + self.wire_offset*52 + i) & 0xFFFF
00092 elif self.type in ['int32_t', 'uint32_t']:
00093 return (963497464 + self.wire_offset*52 + i)&0xFFFFFFFF
00094 elif self.type in ['int64_t', 'uint64_t']:
00095 return 93372036854775807 + self.wire_offset*63 + i
00096 else:
00097 raise MAVError('unknown type %s' % self.type)
00098
00099 def set_test_value(self):
00100 '''set a testsuite value for a MAVField'''
00101 if self.array_length:
00102 self.test_value = []
00103 for i in range(self.array_length):
00104 self.test_value.append(self.gen_test_value(i))
00105 else:
00106 self.test_value = self.gen_test_value(0)
00107 if self.type == 'char' and self.array_length:
00108 v = ""
00109 for c in self.test_value:
00110 v += c
00111 self.test_value = v[:-1]
00112
00113
00114 class MAVType(object):
00115 def __init__(self, name, id, linenumber, description=''):
00116 self.name = name
00117 self.name_lower = name.lower()
00118 self.linenumber = linenumber
00119 self.id = int(id)
00120 self.description = description
00121 self.fields = []
00122 self.fieldnames = []
00123 self.extensions_start = None
00124
00125 def base_fields(self):
00126 '''return number of non-extended fields'''
00127 if self.extensions_start is None:
00128 return len(self.fields)
00129 return len(self.fields[:self.extensions_start])
00130
00131 class MAVEnumParam(object):
00132 def __init__(self, index, description=''):
00133 self.index = index
00134 self.description = description
00135
00136 class MAVEnumEntry(object):
00137 def __init__(self, name, value, description='', end_marker=False, autovalue=False, origin_file='', origin_line=0):
00138 self.name = name
00139 self.value = value
00140 self.description = description
00141 self.param = []
00142 self.end_marker = end_marker
00143 self.autovalue = autovalue
00144 self.origin_file = origin_file
00145 self.origin_line = origin_line
00146
00147 class MAVEnum(object):
00148 def __init__(self, name, linenumber, description=''):
00149 self.name = name
00150 self.description = description
00151 self.entry = []
00152 self.start_value = None
00153 self.highest_value = 0
00154 self.linenumber = linenumber
00155
00156 class MAVXML(object):
00157 '''parse a mavlink XML file'''
00158 def __init__(self, filename, wire_protocol_version=PROTOCOL_0_9):
00159 self.filename = filename
00160 self.basename = os.path.basename(filename)
00161 if self.basename.lower().endswith(".xml"):
00162 self.basename = self.basename[:-4]
00163 self.basename_upper = self.basename.upper()
00164 self.message = []
00165 self.enum = []
00166
00167
00168 self.parse_time = time.strftime("%a %b %d %Y")
00169 self.version = 2
00170 self.include = []
00171 self.wire_protocol_version = wire_protocol_version
00172
00173
00174 if wire_protocol_version == PROTOCOL_0_9:
00175 self.protocol_marker = ord('U')
00176 self.sort_fields = False
00177 self.little_endian = False
00178 self.crc_extra = False
00179 self.crc_struct = False
00180 self.command_24bit = False
00181 self.allow_extensions = False
00182 elif wire_protocol_version == PROTOCOL_1_0:
00183 self.protocol_marker = 0xFE
00184 self.sort_fields = True
00185 self.little_endian = True
00186 self.crc_extra = True
00187 self.crc_struct = False
00188 self.command_24bit = False
00189 self.allow_extensions = False
00190 elif wire_protocol_version == PROTOCOL_2_0:
00191 self.protocol_marker = 0xFD
00192 self.sort_fields = True
00193 self.little_endian = True
00194 self.crc_extra = True
00195 self.crc_struct = True
00196 self.command_24bit = True
00197 self.allow_extensions = True
00198 else:
00199 print("Unknown wire protocol version")
00200 print("Available versions are: %s %s" % (PROTOCOL_0_9, PROTOCOL_1_0, PROTOCOL_2_0))
00201 raise MAVParseError('Unknown MAVLink wire protocol version %s' % wire_protocol_version)
00202
00203 in_element_list = []
00204
00205 def check_attrs(attrs, check, where):
00206 for c in check:
00207 if not c in attrs:
00208 raise MAVParseError('expected missing %s "%s" attribute at %s:%u' % (
00209 where, c, filename, p.CurrentLineNumber))
00210
00211 def start_element(name, attrs):
00212 in_element_list.append(name)
00213 in_element = '.'.join(in_element_list)
00214
00215 if in_element == "mavlink.messages.message":
00216 check_attrs(attrs, ['name', 'id'], 'message')
00217 self.message.append(MAVType(attrs['name'], attrs['id'], p.CurrentLineNumber))
00218 elif in_element == "mavlink.messages.message.extensions":
00219 self.message[-1].extensions_start = len(self.message[-1].fields)
00220 elif in_element == "mavlink.messages.message.field":
00221 check_attrs(attrs, ['name', 'type'], 'field')
00222 if 'print_format' in attrs:
00223 print_format = attrs['print_format']
00224 else:
00225 print_format = None
00226 if 'enum' in attrs:
00227 enum = attrs['enum']
00228 else:
00229 enum = ''
00230 new_field = MAVField(attrs['name'], attrs['type'], print_format, self, enum=enum)
00231 if self.message[-1].extensions_start is None or self.allow_extensions:
00232 self.message[-1].fields.append(new_field)
00233 elif in_element == "mavlink.enums.enum":
00234 check_attrs(attrs, ['name'], 'enum')
00235 self.enum.append(MAVEnum(attrs['name'], p.CurrentLineNumber))
00236 elif in_element == "mavlink.enums.enum.entry":
00237 check_attrs(attrs, ['name'], 'enum entry')
00238
00239 if 'value' in attrs:
00240 value = eval(attrs['value'])
00241 autovalue = False
00242 else:
00243 value = self.enum[-1].highest_value + 1
00244 autovalue = True
00245
00246 if (self.enum[-1].start_value == None or value < self.enum[-1].start_value):
00247 self.enum[-1].start_value = value
00248
00249 if (value > self.enum[-1].highest_value):
00250 self.enum[-1].highest_value = value
00251
00252 self.enum[-1].entry.append(MAVEnumEntry(attrs['name'], value, '', False, autovalue, self.filename, p.CurrentLineNumber))
00253 elif in_element == "mavlink.enums.enum.entry.param":
00254 check_attrs(attrs, ['index'], 'enum param')
00255 self.enum[-1].entry[-1].param.append(MAVEnumParam(attrs['index']))
00256
00257 def end_element(name):
00258 in_element_list.pop()
00259
00260 def char_data(data):
00261 in_element = '.'.join(in_element_list)
00262 if in_element == "mavlink.messages.message.description":
00263 self.message[-1].description += data
00264 elif in_element == "mavlink.messages.message.field":
00265 if self.message[-1].extensions_start is None or self.allow_extensions:
00266 self.message[-1].fields[-1].description += data
00267 elif in_element == "mavlink.enums.enum.description":
00268 self.enum[-1].description += data
00269 elif in_element == "mavlink.enums.enum.entry.description":
00270 self.enum[-1].entry[-1].description += data
00271 elif in_element == "mavlink.enums.enum.entry.param":
00272 self.enum[-1].entry[-1].param[-1].description += data
00273 elif in_element == "mavlink.version":
00274 self.version = int(data)
00275 elif in_element == "mavlink.include":
00276 self.include.append(data)
00277
00278 f = open(filename, mode='rb')
00279 p = xml.parsers.expat.ParserCreate()
00280 p.StartElementHandler = start_element
00281 p.EndElementHandler = end_element
00282 p.CharacterDataHandler = char_data
00283 p.ParseFile(f)
00284 f.close()
00285
00286 self.message_lengths = {}
00287 self.message_min_lengths = {}
00288 self.message_flags = {}
00289 self.message_target_system_ofs = {}
00290 self.message_target_component_ofs = {}
00291 self.message_crcs = {}
00292 self.message_names = {}
00293 self.largest_payload = 0
00294
00295 if not self.command_24bit:
00296
00297 m2 = []
00298 for m in self.message:
00299 if m.id <= 255:
00300 m2.append(m)
00301 else:
00302 print("Ignoring MAVLink2 message %s" % m.name)
00303 self.message = m2
00304
00305 for m in self.message:
00306 if not self.command_24bit and m.id > 255:
00307 continue
00308
00309 m.wire_length = 0
00310 m.wire_min_length = 0
00311 m.fieldnames = []
00312 m.fieldlengths = []
00313 m.ordered_fieldnames = []
00314 m.message_flags = 0
00315 m.target_system_ofs = 0
00316 m.target_component_ofs = 0
00317
00318 if self.sort_fields:
00319
00320 sort_end = m.base_fields()
00321 m.ordered_fields = sorted(m.fields[:sort_end],
00322 key=operator.attrgetter('type_length'),
00323 reverse=True)
00324 m.ordered_fields.extend(m.fields[sort_end:])
00325 else:
00326 m.ordered_fields = m.fields
00327 for f in m.fields:
00328 m.fieldnames.append(f.name)
00329 L = f.array_length
00330 if L == 0:
00331 m.fieldlengths.append(1)
00332 elif L > 1 and f.type == 'char':
00333 m.fieldlengths.append(1)
00334 else:
00335 m.fieldlengths.append(L)
00336 for i in range(len(m.ordered_fields)):
00337 f = m.ordered_fields[i]
00338 f.wire_offset = m.wire_length
00339 m.wire_length += f.wire_length
00340 if m.extensions_start is None or i < m.extensions_start:
00341 m.wire_min_length = m.wire_length
00342 m.ordered_fieldnames.append(f.name)
00343 f.set_test_value()
00344 if f.name.find('[') != -1:
00345 raise MAVParseError("invalid field name with array descriptor %s" % f.name)
00346
00347 if f.name == 'target_system':
00348 m.message_flags |= FLAG_HAVE_TARGET_SYSTEM
00349 m.target_system_ofs = f.wire_offset
00350 elif f.name == 'target_component':
00351 m.message_flags |= FLAG_HAVE_TARGET_COMPONENT
00352 m.target_component_ofs = f.wire_offset
00353 m.num_fields = len(m.fieldnames)
00354 if m.num_fields > 64:
00355 raise MAVParseError("num_fields=%u : Maximum number of field names allowed is" % (
00356 m.num_fields, 64))
00357 m.crc_extra = message_checksum(m)
00358
00359 key = m.id
00360 self.message_crcs[key] = m.crc_extra
00361 self.message_lengths[key] = m.wire_length
00362 self.message_min_lengths[key] = m.wire_min_length
00363 self.message_names[key] = m.name
00364 self.message_flags[key] = m.message_flags
00365 self.message_target_system_ofs[key] = m.target_system_ofs
00366 self.message_target_component_ofs[key] = m.target_component_ofs
00367
00368 if m.wire_length > self.largest_payload:
00369 self.largest_payload = m.wire_length
00370
00371 if m.wire_length+8 > 64:
00372 print("Note: message %s is longer than 64 bytes long (%u bytes), which can cause fragmentation since many radio modems use 64 bytes as maximum air transfer unit." % (m.name, m.wire_length+8))
00373
00374 def __str__(self):
00375 return "MAVXML for %s from %s (%u message, %u enums)" % (
00376 self.basename, self.filename, len(self.message), len(self.enum))
00377
00378
00379 def message_checksum(msg):
00380 '''calculate a 8-bit checksum of the key fields of a message, so we
00381 can detect incompatible XML changes'''
00382 from .mavcrc import x25crc
00383 crc = x25crc()
00384 crc.accumulate_str(msg.name + ' ')
00385
00386
00387 crc_end = msg.base_fields()
00388 for i in range(crc_end):
00389 f = msg.ordered_fields[i]
00390 crc.accumulate_str(f.type + ' ')
00391 crc.accumulate_str(f.name + ' ')
00392 if f.array_length:
00393 crc.accumulate([f.array_length])
00394 return (crc.crc&0xFF) ^ (crc.crc>>8)
00395
00396 def merge_enums(xml):
00397 '''merge enums between XML files'''
00398 emap = {}
00399 for x in xml:
00400 newenums = []
00401 for enum in x.enum:
00402 if enum.name in emap:
00403 emapitem = emap[enum.name]
00404
00405 if (emapitem.start_value <= enum.highest_value and emapitem.highest_value >= enum.start_value):
00406 for entry in emapitem.entry:
00407
00408 if entry.value <= enum.highest_value and entry.autovalue == True:
00409 entry.value = enum.highest_value + 1
00410 enum.highest_value = entry.value
00411
00412 emapitem.entry.extend(enum.entry)
00413 if not emapitem.description:
00414 emapitem.description = enum.description
00415 print("Merged enum %s" % enum.name)
00416 else:
00417 newenums.append(enum)
00418 emap[enum.name] = enum
00419 x.enum = newenums
00420 for e in emap:
00421
00422 emap[e].entry = sorted(emap[e].entry,
00423 key=operator.attrgetter('value'),
00424 reverse=False)
00425
00426 emap[e].entry.append(MAVEnumEntry("%s_ENUM_END" % emap[e].name,
00427 emap[e].entry[-1].value+1, end_marker=True))
00428
00429 def check_duplicates(xml):
00430 '''check for duplicate message IDs'''
00431
00432 merge_enums(xml)
00433
00434 msgmap = {}
00435 enummap = {}
00436 for x in xml:
00437 for m in x.message:
00438 key = m.id
00439 if key in msgmap:
00440 print("ERROR: Duplicate message id %u for %s (%s:%u) also used by %s" % (
00441 m.id,
00442 m.name,
00443 x.filename, m.linenumber,
00444 msgmap[key]))
00445 return True
00446 fieldset = set()
00447 for f in m.fields:
00448 if f.name in fieldset:
00449 print("ERROR: Duplicate field %s in message %s (%s:%u)" % (
00450 f.name, m.name,
00451 x.filename, m.linenumber))
00452 return True
00453 fieldset.add(f.name)
00454 msgmap[key] = '%s (%s:%u)' % (m.name, x.filename, m.linenumber)
00455 for enum in x.enum:
00456 for entry in enum.entry:
00457 if entry.autovalue == True and "common.xml" not in entry.origin_file:
00458 print("Note: An enum value was auto-generated: %s = %u" % (entry.name, entry.value))
00459 s1 = "%s.%s" % (enum.name, entry.name)
00460 s2 = "%s.%s" % (enum.name, entry.value)
00461 if s1 in enummap or s2 in enummap:
00462 print("ERROR: Duplicate enum %s:\n\t%s = %s @ %s:%u\n\t%s" % (
00463 "names" if s1 in enummap else "values",
00464 s1, entry.value, entry.origin_file, entry.origin_line,
00465 enummap.get(s1) or enummap.get(s2)))
00466 return True
00467 enummap[s1] = enummap[s2] = "%s.%s = %s @ %s:%u" % (enum.name, entry.name, entry.value, entry.origin_file, entry.origin_line)
00468
00469 return False
00470
00471
00472
00473 def total_msgs(xml):
00474 '''count total number of msgs'''
00475 count = 0
00476 for x in xml:
00477 count += len(x.message)
00478 return count
00479
00480 def mkdir_p(dir):
00481 try:
00482 os.makedirs(dir)
00483 except OSError as exc:
00484 if exc.errno == errno.EEXIST:
00485 pass
00486 else: raise
00487
00488
00489
00490
00491