mavlink.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. var mavlink = require('../implementations/mavlink_common_v1.0/mavlink.js'),
  2. should = require('should'),
  3. sinon = require('sinon'),
  4. fs = require('fs');
  5. // Actual data stream taken from APM.
  6. global.fixtures = global.fixtures || {};
  7. global.fixtures.serialStream = fs.readFileSync("test/capture.mavlink");
  8. //global.fixtures.heartbeatBinaryStream = fs.readFileSync("javascript/test/heartbeat-data-fixture");
  9. describe("Generated MAVLink protocol handler object", function() {
  10. beforeEach(function() {
  11. this.m = new MAVLink();
  12. // Valid heartbeat payload
  13. this.heartbeatPayload = new Buffer([0xfe, 0x09, 0x03, 0xff , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x06 , 0x08 , 0x00 , 0x00 , 0x03, 0x9f, 0x5c]);
  14. // Complete but invalid message
  15. this.completeInvalidMessage = new Buffer([0xfe, 0x00, 0xfe, 0x00, 0x00, 0xe0, 0x00, 0x00]);
  16. });
  17. describe("message header handling", function() {
  18. it("IDs and sequence numbers are set on send", function(){
  19. var mav = new MAVLink(null, 42, 99);
  20. var writer = {
  21. write: function(){}
  22. };
  23. mav.file = writer;
  24. var spy = sinon.spy(writer, 'write');
  25. var msg = new mavlink.messages['heartbeat']();
  26. mav.send(msg);
  27. spy.calledOnce.should.be.true;
  28. spy.getCall(0).args[0][2].should.be.eql(0); // seq
  29. spy.getCall(0).args[0][3].should.be.eql(42); // sys
  30. spy.getCall(0).args[0][4].should.be.eql(99); // comp
  31. });
  32. it("sequence number increases on send", function(){
  33. var mav = new MAVLink(null, 42, 99);
  34. var writer = {
  35. write: function(){}
  36. };
  37. mav.file = writer;
  38. var spy = sinon.spy(writer, 'write');
  39. var msg = new mavlink.messages['heartbeat']();
  40. mav.send(msg);
  41. mav.send(msg);
  42. spy.callCount.should.be.eql(2);
  43. spy.getCall(0).args[0][2].should.be.eql(0); // seq
  44. spy.getCall(0).args[0][3].should.be.eql(42); // sys
  45. spy.getCall(0).args[0][4].should.be.eql(99); // comp
  46. spy.getCall(1).args[0][2].should.be.eql(1); // seq
  47. spy.getCall(1).args[0][3].should.be.eql(42); // sys
  48. spy.getCall(1).args[0][4].should.be.eql(99); // comp
  49. });
  50. it("sequence number turns over at 256", function(){
  51. var mav = new MAVLink(null, 42, 99);
  52. var writer = {
  53. write: function(){}
  54. };
  55. mav.file = writer;
  56. var spy = sinon.spy(writer, 'write');
  57. var msg = new mavlink.messages['heartbeat']();
  58. for(var i = 0; i < 258; i++){
  59. mav.send(msg);
  60. var seq = i % 256;
  61. spy.getCall(i).args[0][2].should.be.eql(seq); // seq
  62. }
  63. });
  64. });
  65. describe("buffer decoder (parseBuffer)", function() {
  66. // This test prepopulates a single message as a binary buffer.
  67. it("decodes a binary stream representation of a single message correctly", function() {
  68. this.m.pushBuffer(global.fixtures.heartbeatBinaryStream);
  69. var messages = this.m.parseBuffer();
  70. });
  71. // This test includes a "noisy" signal, with non-mavlink data/messages/noise.
  72. it("decodes a real serial binary stream into an array of MAVLink messages", function() {
  73. this.m.pushBuffer(global.fixtures.serialStream);
  74. var messages = this.m.parseBuffer();
  75. });
  76. it("decodes at most one message, even if there are more in its buffer", function() {
  77. });
  78. it("returns null while no packet is available", function() {
  79. (this.m.parseBuffer() === null).should.equal(true); // should's a bit tortured here
  80. });
  81. });
  82. describe("decoding chain (parseChar)", function() {
  83. it("returns a bad_data message if a borked message is encountered", function() {
  84. var b = new Buffer([3, 0, 1, 2, 3, 4, 5]); // invalid message
  85. var message = this.m.parseChar(b);
  86. message.should.be.an.instanceof(mavlink.messages.bad_data);
  87. });
  88. it("emits a 'message' event, provisioning callbacks with the message", function(done) {
  89. this.m.on('message', function(message) {
  90. message.should.be.an.instanceof(mavlink.messages.heartbeat);
  91. done();
  92. });
  93. this.m.parseChar(this.heartbeatPayload);
  94. });
  95. it("emits a 'message' event for bad messages, provisioning callbacks with the message", function(done) {
  96. var b = new Buffer([3, 0, 1, 2, 3, 4, 5]); // invalid message
  97. this.m.on('message', function(message) {
  98. message.should.be.an.instanceof(mavlink.messages.bad_data);
  99. done();
  100. });
  101. this.m.parseChar(b);
  102. });
  103. it("on bad prefix: cuts-off first char in buffer and returns correct bad data", function() {
  104. var b = new Buffer([3, 0, 1, 2, 3, 4, 5]); // invalid message
  105. var message = this.m.parseChar(b);
  106. message.msgbuf.length.should.be.eql(1);
  107. message.msgbuf[0].should.be.eql(3);
  108. this.m.buf.length.should.be.eql(6);
  109. // should process next char
  110. message = this.m.parseChar();
  111. message.msgbuf.length.should.be.eql(1);
  112. message.msgbuf[0].should.be.eql(0);
  113. this.m.buf.length.should.be.eql(5);
  114. });
  115. it("on bad message: cuts-off message length and returns correct bad data", function() {
  116. var message = this.m.parseChar(this.completeInvalidMessage);
  117. message.msgbuf.length.should.be.eql(8);
  118. message.msgbuf.should.be.eql(this.completeInvalidMessage);
  119. this.m.buf.length.should.be.eql(0);
  120. });
  121. it("error counter is raised on error", function() {
  122. var message = this.m.parseChar(this.completeInvalidMessage);
  123. this.m.total_receive_errors.should.equal(1);
  124. var message = this.m.parseChar(this.completeInvalidMessage);
  125. this.m.total_receive_errors.should.equal(2);
  126. });
  127. // TODO: there is a option in python: robust_parsing. Maybe we should port this as well.
  128. // If robust_parsing is off, the following should be tested:
  129. // - (maybe) not returning subsequent errors for prefix errors
  130. // - errors are thrown instead of catched inside
  131. // TODO: add tests for "try hard" parsing when implemented
  132. });
  133. describe("stream buffer accumulator", function() {
  134. it("increments total bytes received", function() {
  135. this.m.total_bytes_received.should.equal(0);
  136. var b = new Buffer(16);
  137. b.fill("h");
  138. this.m.pushBuffer(b);
  139. this.m.total_bytes_received.should.equal(16);
  140. });
  141. it("appends data to its local buffer", function() {
  142. this.m.buf.length.should.equal(0);
  143. var b = new Buffer(16);
  144. b.fill("h");
  145. this.m.pushBuffer(b);
  146. this.m.buf.should.eql(b); // eql = wiggly equality
  147. });
  148. });
  149. describe("prefix decoder", function() {
  150. it("consumes, unretrievably, the first byte of the buffer, if its a bad prefix", function() {
  151. var b = new Buffer([1, 254]);
  152. this.m.pushBuffer(b);
  153. // eat the exception here.
  154. try {
  155. this.m.parsePrefix();
  156. } catch (e) {
  157. this.m.buf.length.should.equal(1);
  158. this.m.buf[0].should.equal(254);
  159. }
  160. });
  161. it("throws an exception if a malformed prefix is encountered", function() {
  162. var b = new Buffer([15, 254, 1, 7, 7]); // borked system status packet, invalid
  163. this.m.pushBuffer(b);
  164. var m = this.m;
  165. (function() { m.parsePrefix(); }).should.throw('Bad prefix (15)');
  166. });
  167. });
  168. describe("length decoder", function() {
  169. it("updates the expected length to the size of the expected full message", function() {
  170. this.m.expected_length.should.equal(6); // default, header size
  171. var b = new Buffer([254, 1, 1]); // packet length = 1
  172. this.m.pushBuffer(b);
  173. this.m.parseLength();
  174. this.m.expected_length.should.equal(9); // 1+8 bytes for the message header
  175. });
  176. });
  177. describe("payload decoder", function() {
  178. it("resets the expected length of the next packet to 6 (header)", function() {
  179. this.m.pushBuffer(this.heartbeatPayload);
  180. this.m.parseLength(); // expected length should now be 9 (message) + 8 bytes (header) = 17
  181. this.m.expected_length.should.equal(17);
  182. this.m.parsePayload();
  183. this.m.expected_length.should.equal(6);
  184. });
  185. it("submits a candidate message to the mavlink decode function", function() {
  186. var spy = sinon.spy(this.m, 'decode');
  187. this.m.pushBuffer(this.heartbeatPayload);
  188. this.m.parseLength();
  189. this.m.parsePayload();
  190. // could improve this to check the args more closely.
  191. // It'd be better but tricky because the type comparison doesn't quite work.
  192. spy.called.should.be.true;
  193. });
  194. // invalid data should return bad_data message
  195. it("parsePayload throws exception if a borked message is encountered", function() {
  196. var b = new Buffer([3, 0, 1, 2, 3, 4, 5]); // invalid message
  197. this.m.pushBuffer(b);
  198. var message;
  199. (function(){
  200. message = this.m.parsePayload();
  201. }).should.throw();
  202. });
  203. it("returns a valid mavlink packet if everything is OK", function() {
  204. this.m.pushBuffer(this.heartbeatPayload);
  205. this.m.parseLength();
  206. var message = this.m.parsePayload();
  207. message.should.be.an.instanceof(mavlink.messages.heartbeat);
  208. });
  209. it("increments the total packets received if a good packet is decoded", function() {
  210. this.m.total_packets_received.should.equal(0);
  211. this.m.pushBuffer(this.heartbeatPayload);
  212. this.m.parseLength();
  213. var message = this.m.parsePayload();
  214. this.m.total_packets_received.should.equal(1);
  215. });
  216. });
  217. });
  218. describe("MAVLink X25CRC Decoder", function() {
  219. beforeEach(function() {
  220. // Message header + payload, lacks initial MAVLink flag (FE) and CRC.
  221. this.heartbeatMessage = new Buffer([0x09, 0x03, 0xff , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x06 , 0x08 , 0x00 , 0x00 , 0x03]);
  222. });
  223. // This test matches the output directly taken by inspecting what the Python implementation
  224. // generated for the above packet.
  225. it('implements x25crc function', function() {
  226. mavlink.x25Crc(this.heartbeatMessage).should.equal(27276);
  227. });
  228. // Heartbeat crc_extra value is 50.
  229. it('can accumulate further bytes as needed (crc_extra)', function() {
  230. var crc = mavlink.x25Crc(this.heartbeatMessage);
  231. crc = mavlink.x25Crc([50], crc);
  232. crc.should.eql(23711)
  233. });
  234. });