mavgen_javascript.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. #!/usr/bin/env python
  2. '''
  3. parse a MAVLink protocol XML file and generate a Node.js javascript module implementation
  4. Based on original work Copyright Andrew Tridgell 2011
  5. Released under GNU GPL version 3 or later
  6. '''
  7. from __future__ import print_function
  8. from builtins import range
  9. import os
  10. import textwrap
  11. from . import mavtemplate
  12. t = mavtemplate.MAVTemplate()
  13. def generate_preamble(outf, msgs, args, xml):
  14. print("Generating preamble")
  15. t.write(outf, """
  16. /*
  17. MAVLink protocol implementation for node.js (auto-generated by mavgen_javascript.py)
  18. Generated from: ${FILELIST}
  19. Note: this file has been auto-generated. DO NOT EDIT
  20. */
  21. jspack = require("jspack").jspack,
  22. _ = require("underscore"),
  23. events = require("events"),
  24. util = require("util");
  25. // Add a convenience method to Buffer
  26. Buffer.prototype.toByteArray = function () {
  27. return Array.prototype.slice.call(this, 0)
  28. }
  29. mavlink = function(){};
  30. // Implement the X25CRC function (present in the Python version through the mavutil.py package)
  31. mavlink.x25Crc = function(buffer, crc) {
  32. var bytes = buffer;
  33. var crc = crc || 0xffff;
  34. _.each(bytes, function(e) {
  35. var tmp = e ^ (crc & 0xff);
  36. tmp = (tmp ^ (tmp << 4)) & 0xff;
  37. crc = (crc >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4);
  38. crc = crc & 0xffff;
  39. });
  40. return crc;
  41. }
  42. mavlink.WIRE_PROTOCOL_VERSION = "${WIRE_PROTOCOL_VERSION}";
  43. mavlink.MAVLINK_TYPE_CHAR = 0
  44. mavlink.MAVLINK_TYPE_UINT8_T = 1
  45. mavlink.MAVLINK_TYPE_INT8_T = 2
  46. mavlink.MAVLINK_TYPE_UINT16_T = 3
  47. mavlink.MAVLINK_TYPE_INT16_T = 4
  48. mavlink.MAVLINK_TYPE_UINT32_T = 5
  49. mavlink.MAVLINK_TYPE_INT32_T = 6
  50. mavlink.MAVLINK_TYPE_UINT64_T = 7
  51. mavlink.MAVLINK_TYPE_INT64_T = 8
  52. mavlink.MAVLINK_TYPE_FLOAT = 9
  53. mavlink.MAVLINK_TYPE_DOUBLE = 10
  54. // Mavlink headers incorporate sequence, source system (platform) and source component.
  55. mavlink.header = function(msgId, mlen, seq, srcSystem, srcComponent) {
  56. this.mlen = ( typeof mlen === 'undefined' ) ? 0 : mlen;
  57. this.seq = ( typeof seq === 'undefined' ) ? 0 : seq;
  58. this.srcSystem = ( typeof srcSystem === 'undefined' ) ? 0 : srcSystem;
  59. this.srcComponent = ( typeof srcComponent === 'undefined' ) ? 0 : srcComponent;
  60. this.msgId = msgId
  61. }
  62. mavlink.header.prototype.pack = function() {
  63. return jspack.Pack('BBBBBB', [${PROTOCOL_MARKER}, this.mlen, this.seq, this.srcSystem, this.srcComponent, this.msgId]);
  64. }
  65. // Base class declaration: mavlink.message will be the parent class for each
  66. // concrete implementation in mavlink.messages.
  67. mavlink.message = function() {};
  68. // Convenience setter to facilitate turning the unpacked array of data into member properties
  69. mavlink.message.prototype.set = function(args) {
  70. _.each(this.fieldnames, function(e, i) {
  71. this[e] = args[i];
  72. }, this);
  73. };
  74. // This pack function builds the header and produces a complete MAVLink message,
  75. // including header and message CRC.
  76. mavlink.message.prototype.pack = function(mav, crc_extra, payload) {
  77. this.payload = payload;
  78. this.header = new mavlink.header(this.id, payload.length, mav.seq, mav.srcSystem, mav.srcComponent);
  79. this.msgbuf = this.header.pack().concat(payload);
  80. var crc = mavlink.x25Crc(this.msgbuf.slice(1));
  81. // For now, assume always using crc_extra = True. TODO: check/fix this.
  82. crc = mavlink.x25Crc([crc_extra], crc);
  83. this.msgbuf = this.msgbuf.concat(jspack.Pack('<H', [crc] ) );
  84. return this.msgbuf;
  85. }
  86. """, {'FILELIST' : ",".join(args),
  87. 'PROTOCOL_MARKER' : xml.protocol_marker,
  88. 'crc_extra' : xml.crc_extra,
  89. 'WIRE_PROTOCOL_VERSION' : xml.wire_protocol_version })
  90. def generate_enums(outf, enums):
  91. print("Generating enums")
  92. outf.write("\n// enums\n")
  93. wrapper = textwrap.TextWrapper(initial_indent="", subsequent_indent=" // ")
  94. for e in enums:
  95. outf.write("\n// %s\n" % e.name)
  96. for entry in e.entry:
  97. outf.write("mavlink.%s = %u // %s\n" % (entry.name, entry.value, wrapper.fill(entry.description)))
  98. def generate_message_ids(outf, msgs):
  99. print("Generating message IDs")
  100. outf.write("\n// message IDs\n")
  101. outf.write("mavlink.MAVLINK_MSG_ID_BAD_DATA = -1\n")
  102. for m in msgs:
  103. outf.write("mavlink.MAVLINK_MSG_ID_%s = %u\n" % (m.name.upper(), m.id))
  104. def generate_classes(outf, msgs):
  105. """
  106. Generate the implementations of the classes representing MAVLink messages.
  107. """
  108. print("Generating class definitions")
  109. wrapper = textwrap.TextWrapper(initial_indent="", subsequent_indent="")
  110. outf.write("\nmavlink.messages = {};\n\n");
  111. def field_descriptions(fields):
  112. ret = ""
  113. for f in fields:
  114. ret += " %-18s : %s (%s)\n" % (f.name, f.description.strip(), f.type)
  115. return ret
  116. for m in msgs:
  117. comment = "%s\n\n%s" % (wrapper.fill(m.description.strip()), field_descriptions(m.fields))
  118. selffieldnames = 'self, '
  119. for f in m.fields:
  120. # if f.omit_arg:
  121. # selffieldnames += '%s=%s, ' % (f.name, f.const_value)
  122. #else:
  123. # -- Omitting the code above because it is rarely used (only once?) and would need some special handling
  124. # in javascript. Specifically, inside the method definition, it needs to check for a value then assign
  125. # a default.
  126. selffieldnames += '%s, ' % f.name
  127. selffieldnames = selffieldnames[:-2]
  128. sub = {'NAMELOWER' : m.name.lower(),
  129. 'SELFFIELDNAMES' : selffieldnames,
  130. 'COMMENT' : comment,
  131. 'FIELDNAMES' : ", ".join(m.fieldnames)}
  132. t.write(outf, """
  133. /*
  134. ${COMMENT}
  135. */
  136. """, sub)
  137. # function signature + declaration
  138. outf.write("mavlink.messages.%s = function(" % (m.name.lower()))
  139. if len(m.fields) != 0:
  140. outf.write(", ".join(m.fieldnames))
  141. outf.write(") {")
  142. # body: set message type properties
  143. outf.write("""
  144. this.format = '%s';
  145. this.id = mavlink.MAVLINK_MSG_ID_%s;
  146. this.order_map = %s;
  147. this.crc_extra = %u;
  148. this.name = '%s';
  149. """ % (m.fmtstr, m.name.upper(), m.order_map, m.crc_extra, m.name.upper()))
  150. # body: set own properties
  151. if len(m.fieldnames) != 0:
  152. outf.write(" this.fieldnames = ['%s'];\n" % "', '".join(m.fieldnames))
  153. outf.write("""
  154. this.set(arguments);
  155. }
  156. """)
  157. # inherit methods from the base message class
  158. outf.write("""
  159. mavlink.messages.%s.prototype = new mavlink.message;
  160. """ % m.name.lower())
  161. # Implement the pack() function for this message
  162. outf.write("""
  163. mavlink.messages.%s.prototype.pack = function(mav) {
  164. return mavlink.message.prototype.pack.call(this, mav, this.crc_extra, jspack.Pack(this.format""" % m.name.lower())
  165. if len(m.fields) != 0:
  166. outf.write(", [ this." + ", this.".join(m.ordered_fieldnames) + ']')
  167. outf.write("));\n}\n\n")
  168. def mavfmt(field):
  169. '''work out the struct format for a type'''
  170. map = {
  171. 'float' : 'f',
  172. 'double' : 'd',
  173. 'char' : 'c',
  174. 'int8_t' : 'b',
  175. 'uint8_t' : 'B',
  176. 'uint8_t_mavlink_version' : 'B',
  177. 'int16_t' : 'h',
  178. 'uint16_t' : 'H',
  179. 'int32_t' : 'i',
  180. 'uint32_t' : 'I',
  181. 'int64_t' : 'q',
  182. 'uint64_t' : 'Q',
  183. }
  184. if field.array_length:
  185. if field.type in ['char', 'int8_t', 'uint8_t']:
  186. return str(field.array_length)+'s'
  187. return str(field.array_length)+map[field.type]
  188. return map[field.type]
  189. def generate_mavlink_class(outf, msgs, xml):
  190. print("Generating MAVLink class")
  191. # Write mapper to enable decoding based on the integer message type
  192. outf.write("\n\nmavlink.map = {\n");
  193. for m in msgs:
  194. outf.write(" %s: { format: '%s', type: mavlink.messages.%s, order_map: %s, crc_extra: %u },\n" % (
  195. m.id, m.fmtstr, m.name.lower(), m.order_map, m.crc_extra))
  196. outf.write("}\n\n")
  197. t.write(outf, """
  198. // Special mavlink message to capture malformed data packets for debugging
  199. mavlink.messages.bad_data = function(data, reason) {
  200. this.id = mavlink.MAVLINK_MSG_ID_BAD_DATA;
  201. this.data = data;
  202. this.reason = reason;
  203. this.msgbuf = data;
  204. }
  205. /* MAVLink protocol handling class */
  206. MAVLink = function(logger, srcSystem, srcComponent) {
  207. this.logger = logger;
  208. this.seq = 0;
  209. this.buf = new Buffer(0);
  210. this.bufInError = new Buffer(0);
  211. this.srcSystem = (typeof srcSystem === 'undefined') ? 0 : srcSystem;
  212. this.srcComponent = (typeof srcComponent === 'undefined') ? 0 : srcComponent;
  213. // The first packet we expect is a valid header, 6 bytes.
  214. this.expected_length = 6;
  215. this.have_prefix_error = false;
  216. this.protocol_marker = 254;
  217. this.little_endian = true;
  218. this.crc_extra = true;
  219. this.sort_fields = true;
  220. this.total_packets_sent = 0;
  221. this.total_bytes_sent = 0;
  222. this.total_packets_received = 0;
  223. this.total_bytes_received = 0;
  224. this.total_receive_errors = 0;
  225. this.startup_time = Date.now();
  226. }
  227. // Implements EventEmitter
  228. util.inherits(MAVLink, events.EventEmitter);
  229. // If the logger exists, this function will add a message to it.
  230. // Assumes the logger is a winston object.
  231. MAVLink.prototype.log = function(message) {
  232. if(this.logger) {
  233. this.logger.info(message);
  234. }
  235. }
  236. MAVLink.prototype.log = function(level, message) {
  237. if(this.logger) {
  238. this.logger.log(level, message);
  239. }
  240. }
  241. MAVLink.prototype.send = function(mavmsg) {
  242. buf = mavmsg.pack(this);
  243. this.file.write(buf);
  244. this.seq = (this.seq + 1) % 256;
  245. this.total_packets_sent +=1;
  246. this.total_bytes_sent += buf.length;
  247. }
  248. // return number of bytes needed for next parsing stage
  249. MAVLink.prototype.bytes_needed = function() {
  250. ret = this.expected_length - this.buf.length;
  251. return ( ret <= 0 ) ? 1 : ret;
  252. }
  253. // add data to the local buffer
  254. MAVLink.prototype.pushBuffer = function(data) {
  255. if(data) {
  256. this.buf = Buffer.concat([this.buf, data]);
  257. this.total_bytes_received += data.length;
  258. }
  259. }
  260. // Decode prefix. Elides the prefix.
  261. MAVLink.prototype.parsePrefix = function() {
  262. // Test for a message prefix.
  263. if( this.buf.length >= 1 && this.buf[0] != 254 ) {
  264. // Strip the offending initial byte and throw an error.
  265. var badPrefix = this.buf[0];
  266. this.bufInError = this.buf.slice(0,1);
  267. this.buf = this.buf.slice(1);
  268. this.expected_length = 6;
  269. // TODO: enable subsequent prefix error suppression if robust_parsing is implemented
  270. //if(!this.have_prefix_error) {
  271. // this.have_prefix_error = true;
  272. throw new Error("Bad prefix ("+badPrefix+")");
  273. //}
  274. }
  275. //else if( this.buf.length >= 1 && this.buf[0] == 254 ) {
  276. // this.have_prefix_error = false;
  277. //}
  278. }
  279. // Determine the length. Leaves buffer untouched.
  280. MAVLink.prototype.parseLength = function() {
  281. if( this.buf.length >= 2 ) {
  282. var unpacked = jspack.Unpack('BB', this.buf.slice(0, 2));
  283. this.expected_length = unpacked[1] + 8; // length of message + header + CRC
  284. }
  285. }
  286. // input some data bytes, possibly returning a new message
  287. MAVLink.prototype.parseChar = function(c) {
  288. var m = null;
  289. try {
  290. this.pushBuffer(c);
  291. this.parsePrefix();
  292. this.parseLength();
  293. m = this.parsePayload();
  294. } catch(e) {
  295. this.log('error', e.message);
  296. this.total_receive_errors += 1;
  297. m = new mavlink.messages.bad_data(this.bufInError, e.message);
  298. this.bufInError = new Buffer(0);
  299. }
  300. if(null != m) {
  301. this.emit(m.name, m);
  302. this.emit('message', m);
  303. }
  304. return m;
  305. }
  306. MAVLink.prototype.parsePayload = function() {
  307. var m = null;
  308. // If we have enough bytes to try and read it, read it.
  309. if( this.expected_length >= 8 && this.buf.length >= this.expected_length ) {
  310. // Slice off the expected packet length, reset expectation to be to find a header.
  311. var mbuf = this.buf.slice(0, this.expected_length);
  312. // TODO: slicing off the buffer should depend on the error produced by the decode() function
  313. // - if a message we find a well formed message, cut-off the expected_length
  314. // - if the message is not well formed (correct prefix by accident), cut-off 1 char only
  315. this.buf = this.buf.slice(this.expected_length);
  316. this.expected_length = 6;
  317. // w.info("Attempting to parse packet, message candidate buffer is ["+mbuf.toByteArray()+"]");
  318. try {
  319. m = this.decode(mbuf);
  320. this.total_packets_received += 1;
  321. }
  322. catch(e) {
  323. // Set buffer in question and re-throw to generic error handling
  324. this.bufInError = mbuf;
  325. throw e;
  326. }
  327. }
  328. return m;
  329. }
  330. // input some data bytes, possibly returning an array of new messages
  331. MAVLink.prototype.parseBuffer = function(s) {
  332. // Get a message, if one is available in the stream.
  333. var m = this.parseChar(s);
  334. // No messages available, bail.
  335. if ( null === m ) {
  336. return null;
  337. }
  338. // While more valid messages can be read from the existing buffer, add
  339. // them to the array of new messages and return them.
  340. var ret = [m];
  341. while(true) {
  342. m = this.parseChar();
  343. if ( null === m ) {
  344. // No more messages left.
  345. return ret;
  346. }
  347. ret.push(m);
  348. }
  349. return ret;
  350. }
  351. /* decode a buffer as a MAVLink message */
  352. MAVLink.prototype.decode = function(msgbuf) {
  353. var magic, mlen, seq, srcSystem, srcComponent, unpacked, msgId;
  354. // decode the header
  355. try {
  356. unpacked = jspack.Unpack('cBBBBB', msgbuf.slice(0, 6));
  357. magic = unpacked[0];
  358. mlen = unpacked[1];
  359. seq = unpacked[2];
  360. srcSystem = unpacked[3];
  361. srcComponent = unpacked[4];
  362. msgId = unpacked[5];
  363. }
  364. catch(e) {
  365. throw new Error('Unable to unpack MAVLink header: ' + e.message);
  366. }
  367. if (magic.charCodeAt(0) != 254) {
  368. throw new Error("Invalid MAVLink prefix ("+magic.charCodeAt(0)+")");
  369. }
  370. if( mlen != msgbuf.length - 8 ) {
  371. throw new Error("Invalid MAVLink message length. Got " + (msgbuf.length - 8) + " expected " + mlen + ", msgId=" + msgId);
  372. }
  373. if( false === _.has(mavlink.map, msgId) ) {
  374. throw new Error("Unknown MAVLink message ID (" + msgId + ")");
  375. }
  376. // decode the payload
  377. // refs: (fmt, type, order_map, crc_extra) = mavlink.map[msgId]
  378. var decoder = mavlink.map[msgId];
  379. // decode the checksum
  380. try {
  381. var receivedChecksum = jspack.Unpack('<H', msgbuf.slice(msgbuf.length - 2));
  382. } catch (e) {
  383. throw new Error("Unable to unpack MAVLink CRC: " + e.message);
  384. }
  385. var messageChecksum = mavlink.x25Crc(msgbuf.slice(1, msgbuf.length - 2));
  386. // Assuming using crc_extra = True. See the message.prototype.pack() function.
  387. messageChecksum = mavlink.x25Crc([decoder.crc_extra], messageChecksum);
  388. if ( receivedChecksum != messageChecksum ) {
  389. throw new Error('invalid MAVLink CRC in msgID ' +msgId+ ', got 0x' + receivedChecksum + ' checksum, calculated payload checkum as 0x'+messageChecksum );
  390. }
  391. // Decode the payload and reorder the fields to match the order map.
  392. try {
  393. var t = jspack.Unpack(decoder.format, msgbuf.slice(6, msgbuf.length));
  394. }
  395. catch (e) {
  396. throw new Error('Unable to unpack MAVLink payload type='+decoder.type+' format='+decoder.format+' payloadLength='+ msgbuf.slice(6, -2).length +': '+ e.message);
  397. }
  398. // Reorder the fields to match the order map
  399. var args = [];
  400. _.each(t, function(e, i, l) {
  401. args[i] = t[decoder.order_map[i]]
  402. });
  403. // construct the message object
  404. try {
  405. var m = new decoder.type(args);
  406. m.set.call(m, args);
  407. }
  408. catch (e) {
  409. throw new Error('Unable to instantiate MAVLink message of type '+decoder.type+' : ' + e.message);
  410. }
  411. m.msgbuf = msgbuf;
  412. m.payload = msgbuf.slice(6);
  413. m.crc = receivedChecksum;
  414. m.header = new mavlink.header(msgId, mlen, seq, srcSystem, srcComponent);
  415. this.log(m);
  416. return m;
  417. }
  418. """, xml)
  419. def generate_footer(outf):
  420. t.write(outf, """
  421. // Expose this code as a module
  422. module.exports = mavlink;
  423. """)
  424. def generate(basename, xml):
  425. '''generate complete javascript implementation'''
  426. if basename.endswith('.js'):
  427. filename = basename
  428. else:
  429. filename = basename + '.js'
  430. msgs = []
  431. enums = []
  432. filelist = []
  433. for x in xml:
  434. msgs.extend(x.message)
  435. enums.extend(x.enum)
  436. filelist.append(os.path.basename(x.filename))
  437. for m in msgs:
  438. if xml[0].little_endian:
  439. m.fmtstr = '<'
  440. else:
  441. m.fmtstr = '>'
  442. for f in m.ordered_fields:
  443. m.fmtstr += mavfmt(f)
  444. m.order_map = [ 0 ] * len(m.fieldnames)
  445. for i in range(0, len(m.fieldnames)):
  446. m.order_map[i] = m.ordered_fieldnames.index(m.fieldnames[i])
  447. print("Generating %s" % filename)
  448. outf = open(filename, "w")
  449. generate_preamble(outf, msgs, filelist, xml[0])
  450. generate_enums(outf, enums)
  451. generate_message_ids(outf, msgs)
  452. generate_classes(outf, msgs)
  453. generate_mavlink_class(outf, msgs, xml[0])
  454. generate_footer(outf)
  455. outf.close()
  456. print("Generated %s OK" % filename)