node-mavlink 1.0.6 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * Convert a number to hexadecimal representation with a minumum
4
+ * number of characters and optional prefix (0x by default)
5
+ *
6
+ * @param n value to convert
7
+ * @param len length of the converted string (without prefix)
8
+ * @param prefix prefix to prepend the generated string with
9
+ */
10
+ export declare function hex(n: number, len?: number, prefix?: string): string;
11
+ /**
12
+ * Dump a buffer in a readable form
13
+ *
14
+ * @param buffer buffer to dump
15
+ * @param lineWidth width of the line, in bytes of buffer
16
+ */
17
+ export declare function dump(buffer: Buffer, lineWidth?: number): void;
18
+ /**
19
+ * Sleep for a given number of miliseconds
20
+ *
21
+ * @param ms number of miliseconds to sleep
22
+ */
23
+ export declare function sleep(ms: any): Promise<unknown>;
24
+ /**
25
+ * Execute a callback every <code>interval</code>ms and if it will not return
26
+ * a truthy value in the <code>timeout<code>ms then throw a Timeout exception.
27
+ * This is a very useful utility that will allow you to specify how often
28
+ * a particular expression should be evaluated and how long will it take to end
29
+ * the execution without success. Great for time-sensitive operations.
30
+ *
31
+ * @param cb callback to call every <code>interval</code>ms
32
+ * @param timeout number of miliseconds that need to pass before the Timeout exception is thrown
33
+ * @param interval number of miliseconds before re-running the callback
34
+ */
35
+ export declare function waitFor(cb: any, timeout?: number, interval?: number): Promise<unknown>;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.waitFor = exports.sleep = exports.dump = exports.hex = void 0;
13
+ /**
14
+ * Convert a number to hexadecimal representation with a minumum
15
+ * number of characters and optional prefix (0x by default)
16
+ *
17
+ * @param n value to convert
18
+ * @param len length of the converted string (without prefix)
19
+ * @param prefix prefix to prepend the generated string with
20
+ */
21
+ function hex(n, len = 2, prefix = '0x') {
22
+ return `${prefix}${n.toString(16).padStart(len, '0')}`;
23
+ }
24
+ exports.hex = hex;
25
+ /**
26
+ * Dump a buffer in a readable form
27
+ *
28
+ * @param buffer buffer to dump
29
+ * @param lineWidth width of the line, in bytes of buffer
30
+ */
31
+ function dump(buffer, lineWidth = 16) {
32
+ const line = [];
33
+ let address = 0;
34
+ for (let i = 0; i < buffer.length; i++) {
35
+ line.push(hex(buffer[i], 2, '0x'));
36
+ if (line.length === lineWidth) {
37
+ console.log(hex(address, 4), '|', line.join(' '));
38
+ address += lineWidth;
39
+ line.length = 0;
40
+ }
41
+ }
42
+ if (line.length > 0) {
43
+ console.log(hex(address, 4), '|', line.join(' '));
44
+ }
45
+ }
46
+ exports.dump = dump;
47
+ /**
48
+ * Sleep for a given number of miliseconds
49
+ *
50
+ * @param ms number of miliseconds to sleep
51
+ */
52
+ function sleep(ms) {
53
+ return new Promise(resolve => setTimeout(resolve, ms));
54
+ }
55
+ exports.sleep = sleep;
56
+ /**
57
+ * Execute a callback every <code>interval</code>ms and if it will not return
58
+ * a truthy value in the <code>timeout<code>ms then throw a Timeout exception.
59
+ * This is a very useful utility that will allow you to specify how often
60
+ * a particular expression should be evaluated and how long will it take to end
61
+ * the execution without success. Great for time-sensitive operations.
62
+ *
63
+ * @param cb callback to call every <code>interval</code>ms
64
+ * @param timeout number of miliseconds that need to pass before the Timeout exception is thrown
65
+ * @param interval number of miliseconds before re-running the callback
66
+ */
67
+ function waitFor(cb, timeout = 10000, interval = 100) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ return new Promise((resolve, reject) => {
70
+ const timeoutTimer = setTimeout(() => {
71
+ cleanup();
72
+ reject('Timeout');
73
+ }, timeout);
74
+ const intervalTimer = setInterval(() => {
75
+ const result = cb();
76
+ if (result) {
77
+ cleanup();
78
+ resolve(result);
79
+ }
80
+ });
81
+ const cleanup = () => {
82
+ clearTimeout(timeoutTimer);
83
+ clearTimeout(intervalTimer);
84
+ };
85
+ });
86
+ });
87
+ }
88
+ exports.waitFor = waitFor;
Binary file
@@ -1,18 +1,33 @@
1
1
  #!/usr/bin/env -S npx ts-node
2
2
 
3
3
  import { createReadStream } from 'fs'
4
- import { MavLinkPacketSplitter, MavLinkPacketParser, MavLinkPacket } from '..'
4
+ import { MavLinkPacketSplitter, MavLinkPacketParser } from '..'
5
+ import { minimal, common, ardupilotmega, uavionix, icarous } from '..'
5
6
 
6
- const file = createReadStream('./mavlink-v2-3412-packets.bin')
7
+ const file = createReadStream('./GH-5.bin')
7
8
 
8
- const splitter = new MavLinkPacketSplitter({}, true)
9
+ const splitter = new MavLinkPacketSplitter()
9
10
 
10
11
  const reader = file
11
12
  .pipe(splitter)
12
13
  .pipe(new MavLinkPacketParser())
13
14
 
14
- reader.on('data', (packet: MavLinkPacket) => {
15
- console.log(packet.debug())
15
+
16
+ // create a registry of mappings between a message id and a data class
17
+ const REGISTRY = {
18
+ ...minimal.REGISTRY,
19
+ ...common.REGISTRY,
20
+ ...ardupilotmega.REGISTRY,
21
+ ...uavionix.REGISTRY,
22
+ ...icarous.REGISTRY,
23
+ }
24
+
25
+ reader.on('data', packet => {
26
+ const clazz = REGISTRY[packet.header.msgid]
27
+ if (clazz) {
28
+ const data = packet.protocol.data(packet.payload, clazz)
29
+ console.log(data)
30
+ }
16
31
  })
17
32
 
18
33
  file.on('close', () => {
@@ -20,3 +35,4 @@ file.on('close', () => {
20
35
  console.log('Number of unknown packages:', splitter.unknownPackagesCount)
21
36
  console.log('\nTotal number of consumed packets:', splitter.validPackages)
22
37
  })
38
+
package/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from 'mavlink-mappings'
2
+ export * from './lib/utils'
3
+ export * from './lib/logger'
2
4
  export * from './lib/mavlink'
3
5
  export * from './lib/mavesp'
package/lib/logger.ts ADDED
@@ -0,0 +1,128 @@
1
+ import EventEmitter = require('events')
2
+
3
+ /**
4
+ * Level of the log entry
5
+ */
6
+ export enum LogLevel {
7
+ trace = 5,
8
+ debug = 4,
9
+ info = 3,
10
+ warn = 2,
11
+ error = 1,
12
+ fatal = 0,
13
+ }
14
+
15
+ type LoggerRegistry = { [x: string]: Logger }
16
+ type LoggerEvents = 'log'
17
+ type LoggerEventHandler = (context: string, level: LogLevel, message: any[]) => void
18
+
19
+ /**
20
+ * Simplified interface for logging facilities
21
+ */
22
+ export class Logger {
23
+ private static readonly events: EventEmitter = new EventEmitter()
24
+ private static registry: LoggerRegistry = {}
25
+
26
+ /**
27
+ * Gets a logger by name
28
+ *
29
+ * @param context logger context
30
+ */
31
+ static getLogger(context) {
32
+ let name = ''
33
+ if (typeof context === 'function') name = context.name
34
+ else if (typeof context === 'object') name = (<any>context).constructor.name
35
+ else if (typeof context === 'string') name = context
36
+ else throw new Error(`Do not know how to get logger for ${context} (${typeof context})`)
37
+
38
+ if (!Logger.registry[name]) Logger.registry[name] = new Logger(name)
39
+
40
+ return Logger.registry[name]
41
+ }
42
+
43
+ /**
44
+ * Binds an event handler
45
+ *
46
+ * @param event event to react to
47
+ * @param handler event handler
48
+ */
49
+ static on(event: LoggerEvents, handler: (context, level, message) => void) {
50
+ this.events.on(event, handler)
51
+ }
52
+
53
+ /**
54
+ * Removes an event handler
55
+ *
56
+ * @param event event to react to
57
+ * @param handler event handler
58
+ */
59
+ static off(event: LoggerEvents, handler: LoggerEventHandler) {
60
+ this.events.off(event, handler)
61
+ }
62
+
63
+ private context: string
64
+
65
+ /**
66
+ * Constructs a new logger instance
67
+ *
68
+ * @param context logger context
69
+ */
70
+ constructor(context: string) {
71
+ this.context = context
72
+ Logger.events.emit('logger-created', Logger.registry[context])
73
+ }
74
+
75
+ /**
76
+ * Sends a log message if the trace level is enabled for this logger
77
+ *
78
+ * @param args parameters for the log entry
79
+ */
80
+ trace(...args: any) {
81
+ Logger.events.emit('log', { context: this.context, level: LogLevel.trace, message: args })
82
+ }
83
+
84
+ /**
85
+ * Sends a log message if the debug level is enabled for this logger
86
+ *
87
+ * @param args parameters for the log entry
88
+ */
89
+ debug(...args: any) {
90
+ Logger.events.emit('log', { context: this.context, level: LogLevel.debug, message: args })
91
+ }
92
+
93
+ /**
94
+ * Sends a log message if the info level is enabled for this logger
95
+ *
96
+ * @param args parameters for the log entry
97
+ */
98
+ info(...args: any) {
99
+ Logger.events.emit('log', { context: this.context, level: LogLevel.info, message: args })
100
+ }
101
+
102
+ /**
103
+ * Sends a log message if the warn level is enabled for this logger
104
+ *
105
+ * @param args parameters for the log entry
106
+ */
107
+ warn(...args: any) {
108
+ Logger.events.emit('log', { context: this.context, level: LogLevel.warn, message: args })
109
+ }
110
+
111
+ /**
112
+ * Sends a log message if the error level is enabled for this logger
113
+ *
114
+ * @param args parameters for the log entry
115
+ */
116
+ error(...args: any) {
117
+ Logger.events.emit('log', { context: this.context, level: LogLevel.error, message: args })
118
+ }
119
+
120
+ /**
121
+ * Sends a log message if the fatal level is enabled for this logger
122
+ *
123
+ * @param args parameters for the log entry
124
+ */
125
+ fatal(...args: any) {
126
+ Logger.events.emit('log', { context: this.context, level: LogLevel.fatal, message: args })
127
+ }
128
+ }
package/lib/mavesp.ts CHANGED
@@ -4,7 +4,8 @@ import { Socket, createSocket } from 'dgram'
4
4
  import { Writable, PassThrough } from 'stream'
5
5
  import { MavLinkPacketSplitter, MavLinkPacketParser, MavLinkPacketSignature } from './mavlink'
6
6
  import { MavLinkProtocol, MavLinkProtocolV2 } from './mavlink'
7
- import { uint8_t, waitFor, MavLinkData } from 'mavlink-mappings'
7
+ import { waitFor } from './utils'
8
+ import { uint8_t, MavLinkData } from 'mavlink-mappings'
8
9
 
9
10
  /**
10
11
  * Encapsulation of communication with MavEsp8266
package/lib/mavlink.ts CHANGED
@@ -1,17 +1,17 @@
1
- import { Transform, TransformCallback, Writable } from 'stream'
1
+ import { Transform, TransformCallback, Readable, Writable } from 'stream'
2
2
  import { createHash } from 'crypto'
3
- import { uint8_t, uint16_t } from 'mavlink-mappings'
4
- import { x25crc, dump } from 'mavlink-mappings'
3
+ import { uint8_t, uint16_t, x25crc } from 'mavlink-mappings'
5
4
  import { MSG_ID_MAGIC_NUMBER } from 'mavlink-mappings'
6
5
  import { MavLinkData, MavLinkDataConstructor } from 'mavlink-mappings'
7
6
 
7
+ import { hex } from './utils'
8
+ import { Logger } from './logger'
8
9
  import { SERIALIZERS, DESERIALIZERS } from './serialization'
9
- import { start } from 'repl'
10
10
 
11
11
  /**
12
12
  * Header definition of the MavLink packet
13
13
  */
14
- export class MavLinkPacketHeader {
14
+ export class MavLinkPacketHeader {
15
15
  magic: number = 0
16
16
  payloadLength: uint8_t = 0
17
17
  incompatibilityFlags: uint8_t = 0
@@ -29,6 +29,8 @@ import { start } from 'repl'
29
29
  * data classes from the given payload buffer
30
30
  */
31
31
  export abstract class MavLinkProtocol {
32
+ protected readonly log = Logger.getLogger(this)
33
+
32
34
  static NAME = 'unknown'
33
35
  static START_BYTE = 0
34
36
  static PAYLOAD_OFFSET = 0
@@ -65,6 +67,8 @@ export abstract class MavLinkProtocol {
65
67
  * Deserialize payload into actual data class
66
68
  */
67
69
  data<T extends MavLinkData>(payload: Buffer, clazz: MavLinkDataConstructor<T>): T {
70
+ this.log.trace('Deserializing', clazz.MSG_NAME, 'with payload of size', payload.length)
71
+
68
72
  const instance = new clazz()
69
73
  clazz.FIELDS.forEach(field => {
70
74
  const deserialize = DESERIALIZERS[field.type]
@@ -108,6 +112,8 @@ export class MavLinkProtocolV1 extends MavLinkProtocol {
108
112
  }
109
113
 
110
114
  serialize(message: MavLinkData, seq: number): Buffer {
115
+ this.log.trace('Serializing message (seq:', seq, ')')
116
+
111
117
  const definition: MavLinkDataConstructor<MavLinkData> = <any>message.constructor
112
118
  const buffer = Buffer.from(new Uint8Array(MavLinkProtocolV1.PAYLOAD_OFFSET + definition.PAYLOAD_LENGTH + MavLinkProtocol.CHECKSUM_LENGTH))
113
119
 
@@ -134,6 +140,8 @@ export class MavLinkProtocolV1 extends MavLinkProtocol {
134
140
  }
135
141
 
136
142
  header(buffer: Buffer): MavLinkPacketHeader {
143
+ this.log.trace('Reading header from buffer (len:', buffer.length, ')')
144
+
137
145
  const startByte = buffer.readUInt8(0)
138
146
  if (startByte !== MavLinkProtocolV1.START_BYTE) {
139
147
  throw new Error(`Invalid start byte (expected: ${MavLinkProtocolV1.START_BYTE}, got ${startByte})`)
@@ -153,12 +161,16 @@ export class MavLinkProtocolV1 extends MavLinkProtocol {
153
161
  /**
154
162
  * Deserialize packet checksum
155
163
  */
156
- crc(buffer: Buffer): uint16_t {
164
+ crc(buffer: Buffer): uint16_t {
165
+ this.log.trace('Reading crc from buffer (len:', buffer.length, ')')
166
+
157
167
  const plen = buffer.readUInt8(1)
158
168
  return buffer.readUInt16LE(MavLinkProtocolV1.PAYLOAD_OFFSET + plen)
159
169
  }
160
170
 
161
171
  payload(buffer: Buffer): Buffer {
172
+ this.log.trace('Reading payload from buffer (len:', buffer.length, ')')
173
+
162
174
  const plen = buffer.readUInt8(1)
163
175
  const payload = buffer.slice(MavLinkProtocolV1.PAYLOAD_OFFSET, MavLinkProtocolV1.PAYLOAD_OFFSET + plen)
164
176
  const padding = Buffer.from(new Uint8Array(255 - payload.length))
@@ -189,6 +201,8 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
189
201
  }
190
202
 
191
203
  serialize(message: MavLinkData, seq: number): Buffer {
204
+ this.log.trace('Serializing message (seq:', seq, ')')
205
+
192
206
  const definition: MavLinkDataConstructor<MavLinkData> = <any>message.constructor
193
207
  const buffer = Buffer.from(new Uint8Array(MavLinkProtocolV2.PAYLOAD_OFFSET + definition.PAYLOAD_LENGTH + MavLinkProtocol.CHECKSUM_LENGTH))
194
208
 
@@ -225,9 +239,12 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
225
239
  * @param buffer buffer with the original, unsigned package
226
240
  * @param linkId id of the link
227
241
  * @param key key to sign the package with
242
+ * @param timestamp optional timestamp for packet signing (default: Date.now())
228
243
  * @returns signed package
229
244
  */
230
- sign(buffer: Buffer, linkId: number, key: Buffer) {
245
+ sign(buffer: Buffer, linkId: number, key: Buffer, timestamp = Date.now()) {
246
+ this.log.trace('Signing message')
247
+
231
248
  const result = Buffer.concat([
232
249
  buffer,
233
250
  Buffer.from(new Uint8Array(MavLinkPacketSignature.SIGNATURE_LENGTH))
@@ -235,7 +252,7 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
235
252
 
236
253
  const signer = new MavLinkPacketSignature(result)
237
254
  signer.linkId = linkId
238
- signer.timestamp = Date.now()
255
+ signer.timestamp = timestamp
239
256
  signer.signature = signer.calculate(key)
240
257
 
241
258
  return result
@@ -256,6 +273,8 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
256
273
  }
257
274
 
258
275
  header(buffer: Buffer): MavLinkPacketHeader {
276
+ this.log.trace('Reading header from buffer (len:', buffer.length, ')')
277
+
259
278
  const startByte = buffer.readUInt8(0)
260
279
  if (startByte !== MavLinkProtocolV2.START_BYTE) {
261
280
  throw new Error(`Invalid start byte (expected: ${MavLinkProtocolV2.START_BYTE}, got ${startByte})`)
@@ -278,11 +297,15 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
278
297
  * Deserialize packet checksum
279
298
  */
280
299
  crc(buffer: Buffer): uint16_t {
300
+ this.log.trace('Reading crc from buffer (len:', buffer.length, ')')
301
+
281
302
  const plen = buffer.readUInt8(1)
282
303
  return buffer.readUInt16LE(MavLinkProtocolV2.PAYLOAD_OFFSET + plen)
283
304
  }
284
305
 
285
306
  payload(buffer: Buffer): Buffer {
307
+ this.log.trace('Reading payload from buffer (len:', buffer.length, ')')
308
+
286
309
  const plen = buffer.readUInt8(1)
287
310
  const payload = buffer.slice(MavLinkProtocolV2.PAYLOAD_OFFSET, MavLinkProtocolV2.PAYLOAD_OFFSET + plen)
288
311
  const padding = Buffer.from(new Uint8Array(255 - payload.length))
@@ -290,6 +313,8 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
290
313
  }
291
314
 
292
315
  signature(buffer: Buffer, header: MavLinkPacketHeader): MavLinkPacketSignature {
316
+ this.log.trace('Reading signature from buffer (len:', buffer.length, ')')
317
+
293
318
  if (header.incompatibilityFlags & MavLinkProtocolV2.IFLAG_SIGNED) {
294
319
  return new MavLinkPacketSignature(buffer)
295
320
  } else {
@@ -433,7 +458,8 @@ export class MavLinkPacket {
433
458
  + `msgid: ${this.header.msgid}, `
434
459
  + `seq: ${this.header.seq}, `
435
460
  + `plen: ${this.header.payloadLength}, `
436
- + `crc: ${this.crc.toString(16).padStart(2, '0')}`
461
+ + `magic: ${MSG_ID_MAGIC_NUMBER[this.header.msgid]} (${hex(MSG_ID_MAGIC_NUMBER[this.header.msgid])}), `
462
+ + `crc: ${hex(this.crc, 4)}`
437
463
  + this.signatureToString(this.signature)
438
464
  + ')'
439
465
  }
@@ -448,19 +474,28 @@ export class MavLinkPacket {
448
474
  */
449
475
  enum PacketValidationResult { VALID, INVALID, UNKNOWN }
450
476
 
477
+ type BufferCallback = (buffer: Buffer) => void
478
+
451
479
  /**
452
480
  * A transform stream that splits the incomming data stream into chunks containing full MavLink messages
453
481
  */
454
482
  export class MavLinkPacketSplitter extends Transform {
483
+ protected readonly log = Logger.getLogger(this)
484
+
455
485
  private buffer = Buffer.from([])
456
- private verbose = false
486
+ private onCrcError = null
457
487
  private _validPackagesCount = 0
458
488
  private _unknownPackagesCount = 0
459
489
  private _invalidPackagesCount = 0
460
490
 
461
- constructor(opts = {}, verbose = false) {
491
+ /**
492
+ * @param opts options to pass on to the Transform constructor
493
+ * @param verbose print diagnostic information
494
+ * @param onCrcError callback executed if there is a CRC error (mostly for debugging)
495
+ */
496
+ constructor(opts = {}, onCrcError: BufferCallback = () => {}) {
462
497
  super(opts)
463
- this.verbose = verbose
498
+ this.onCrcError = onCrcError
464
499
  }
465
500
 
466
501
  _transform(chunk: Buffer, encoding, callback: TransformCallback) {
@@ -478,38 +513,51 @@ export class MavLinkPacketSplitter extends Transform {
478
513
  this.buffer = this.buffer.slice(offset)
479
514
  }
480
515
 
516
+ this.log.debug('Found potential packet start at', offset)
517
+
481
518
  // get protocol this buffer is encoded with
482
519
  const Protocol = this.getPacketProtocol(this.buffer)
483
520
 
521
+ this.log.debug('Packet protocol is', Protocol.NAME)
522
+
484
523
  // check if the buffer contains at least the minumum size of data
485
524
  if (this.buffer.length < Protocol.PAYLOAD_OFFSET + MavLinkProtocol.CHECKSUM_LENGTH) {
486
525
  // current buffer shorter than the shortest message - skipping
526
+ this.log.debug('Current buffer shorter than the shortest message - skipping')
487
527
  break
488
528
  }
489
529
 
490
530
  // check if the current buffer contains the entire message
491
531
  const expectedBufferLength = this.readPacketLength(this.buffer, Protocol)
532
+ this.log.debug('Expected buffer length:', expectedBufferLength, `(${hex(expectedBufferLength)})`)
492
533
  if (this.buffer.length < expectedBufferLength) {
493
534
  // current buffer is not fully retrieved yet - skipping
535
+ this.log.debug('Current buffer is not fully retrieved yet - skipping')
494
536
  break
537
+ } else {
538
+ this.log.debug('Current buffer length:', this.buffer.length, `(${hex(this.buffer.length, 4)})`)
495
539
  }
496
540
 
497
541
  // retrieve the buffer based on payload size
498
542
  const buffer = this.buffer.slice(0, expectedBufferLength)
543
+ this.log.debug('Recognized buffer length:', buffer.length, `(${hex(buffer.length, 2)})`)
499
544
 
500
545
  switch (this.validatePacket(buffer, Protocol)) {
501
546
  case PacketValidationResult.VALID:
547
+ this.log.debug('Found a valid packet')
502
548
  this._validPackagesCount++
503
549
  this.push(buffer)
504
550
  // truncate the buffer to remove the current message
505
551
  this.buffer = this.buffer.slice(expectedBufferLength)
506
552
  break
507
553
  case PacketValidationResult.INVALID:
554
+ this.log.debug('Found an invalid packet - skipping')
508
555
  this._invalidPackagesCount++
509
556
  // truncate the buffer to remove the wrongly identified STX
510
557
  this.buffer = this.buffer.slice(1)
511
558
  break
512
559
  case PacketValidationResult.UNKNOWN:
560
+ this.log.debug('Found an unknown packet - skipping')
513
561
  this._unknownPackagesCount++
514
562
  // truncate the buffer to remove the current message
515
563
  this.buffer = this.buffer.slice(expectedBufferLength)
@@ -571,21 +619,22 @@ export class MavLinkPacketSplitter extends Transform {
571
619
  return PacketValidationResult.VALID
572
620
  } else {
573
621
  // CRC mismatch
574
- if (this.verbose) {
575
- console.error(
576
- 'CRC error; expected', crc2, `(0x${crc2.toString(16).padStart(4, '0')})`,
577
- 'got', crc, `(0x${crc.toString(16).padStart(4, '0')})`,
578
- '; msgid:', header.msgid, ', magic:', magic
579
- )
580
- dump(buffer)
581
- }
622
+ const message = [
623
+ `CRC error; expected: ${crc2} (${hex(crc2, 4)}), got ${crc} (${hex(crc, 4)});`,
624
+ `msgid: ${header.msgid} (${hex(header.msgid)}),`,
625
+ `seq: ${header.seq} (${hex(header.seq)}),`,
626
+ `plen: ${header.payloadLength} (${hex(header.payloadLength)}),`,
627
+ `magic: ${magic} (${hex(magic)})`,
628
+ ]
629
+ this.log.warn(message.join(' '))
630
+ this.onCrcError(buffer)
631
+
582
632
  return PacketValidationResult.INVALID
583
633
  }
584
634
  } else {
585
635
  // unknown message (as in not generated from the XML sources)
586
- if (this.verbose) {
587
- console.error(`Unknown message with id ${header.msgid} (magic number not found) - skipping`)
588
- }
636
+ this.log.debug(`Unknown message with id ${header.msgid} (magic number not found) - skipping`)
637
+
589
638
  return PacketValidationResult.UNKNOWN
590
639
  }
591
640
  }
@@ -614,7 +663,7 @@ export class MavLinkPacketSplitter extends Transform {
614
663
  * Reset the number of valid packages
615
664
  */
616
665
  resetValidPackagesCount() {
617
- this,this._validPackagesCount = 0
666
+ this._validPackagesCount = 0
618
667
  }
619
668
 
620
669
  /**
@@ -628,7 +677,7 @@ export class MavLinkPacketSplitter extends Transform {
628
677
  * Reset the number of invalid packages
629
678
  */
630
679
  resetInvalidPackagesCount() {
631
- this,this._invalidPackagesCount = 0
680
+ this._invalidPackagesCount = 0
632
681
  }
633
682
 
634
683
  /**
@@ -642,7 +691,7 @@ export class MavLinkPacketSplitter extends Transform {
642
691
  * Reset the number of invalid packages
643
692
  */
644
693
  resetUnknownPackagesCount() {
645
- this,this._unknownPackagesCount = 0
694
+ this._unknownPackagesCount = 0
646
695
  }
647
696
  }
648
697
 
@@ -650,6 +699,8 @@ export class MavLinkPacketSplitter extends Transform {
650
699
  * A transform stream that takes a buffer with data and converts it to MavLinkPacket object
651
700
  */
652
701
  export class MavLinkPacketParser extends Transform {
702
+ protected readonly log = Logger.getLogger(this)
703
+
653
704
  constructor(opts = {}) {
654
705
  super({ ...opts, objectMode: true })
655
706
  }
@@ -662,7 +713,7 @@ export class MavLinkPacketParser extends Transform {
662
713
  case MavLinkProtocolV2.START_BYTE:
663
714
  return new MavLinkProtocolV2()
664
715
  default:
665
- throw new Error(`Unknown protocol '${startByte.toString(16).padStart(2, '0')}'`)
716
+ throw new Error(`Unknown protocol '${hex(startByte)}'`)
666
717
  }
667
718
  }
668
719
 
@@ -681,6 +732,17 @@ export class MavLinkPacketParser extends Transform {
681
732
  }
682
733
  }
683
734
 
735
+ /**
736
+ * Creates a MavLink packet stream reader that is reading packets from the given input
737
+ *
738
+ * @param input input stream to read from
739
+ */
740
+ export function createMavLinkStream(input: Readable, onCrcError: BufferCallback) {
741
+ return input
742
+ .pipe(new MavLinkPacketSplitter({}, onCrcError))
743
+ .pipe(new MavLinkPacketParser())
744
+ }
745
+
684
746
  let seq = 0
685
747
 
686
748
  /**
@@ -711,14 +773,21 @@ export async function send(stream: Writable, msg: MavLinkData, protocol: MavLink
711
773
  * @param linkId link id for the signature
712
774
  * @param sysid system id
713
775
  * @param compid component id
776
+ * @param timestamp optional timestamp for packet signing (default: Date.now())
714
777
  * @returns number of bytes sent
715
778
  */
716
- export async function sendSigned(stream: Writable, msg: MavLinkData, key: Buffer, linkId: uint8_t = 1, sysid: uint8_t = MavLinkProtocol.SYS_ID, compid: uint8_t = MavLinkProtocol.COMP_ID) {
779
+ export async function sendSigned(
780
+ stream: Writable,
781
+ msg: MavLinkData,
782
+ key: Buffer,
783
+ linkId: uint8_t = 1, sysid: uint8_t = MavLinkProtocol.SYS_ID, compid: uint8_t = MavLinkProtocol.COMP_ID,
784
+ timestamp = Date.now()
785
+ ) {
717
786
  return new Promise((resolve, reject) => {
718
787
  const protocol = new MavLinkProtocolV2(sysid, compid, MavLinkProtocolV2.IFLAG_SIGNED)
719
788
  const b1 = protocol.serialize(msg, seq++)
720
789
  seq &= 255
721
- const b2 = protocol.sign(b1, linkId, key)
790
+ const b2 = protocol.sign(b1, linkId, key, timestamp)
722
791
  stream.write(b2, err => {
723
792
  if (err) reject(err)
724
793
  else resolve(b2.length)
@@ -165,7 +165,7 @@ export const DESERIALIZERS = {
165
165
  },
166
166
  'float[]': (buffer: Buffer, offset: number, length: number) => {
167
167
  const result = new Array<number>(length)
168
- for (let i = 0; i < length; i++) result[i] = buffer.readFloatLE(offset + i * 8)
168
+ for (let i = 0; i < length; i++) result[i] = buffer.readFloatLE(offset + i * 4)
169
169
  return result
170
170
  },
171
171
  'double[]': (buffer: Buffer, offset: number, length: number) => {