node-mavlink 1.0.8 → 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.
- package/README.md +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -1
- package/dist/lib/logger.d.ts +84 -0
- package/dist/lib/logger.js +118 -0
- package/dist/lib/mavesp.js +2 -2
- package/dist/lib/mavlink.d.ts +24 -5
- package/dist/lib/mavlink.js +72 -20
- package/dist/lib/serialization.js +1 -1
- package/dist/lib/utils.d.ts +35 -0
- package/dist/lib/utils.js +88 -0
- package/examples/GH-5.bin +0 -0
- package/examples/send-receive-file.ts +3 -3
- package/index.ts +2 -0
- package/lib/logger.ts +128 -0
- package/lib/mavesp.ts +2 -1
- package/lib/mavlink.ts +98 -28
- package/lib/serialization.ts +1 -1
- package/lib/utils.ts +75 -0
- package/package.json +8 -4
- package/tests/data.mavlink +0 -0
- package/tests/main.ts +63 -0
- package/tsconfig.json +2 -1
@@ -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,12 +1,12 @@
|
|
1
1
|
#!/usr/bin/env -S npx ts-node
|
2
2
|
|
3
3
|
import { createReadStream } from 'fs'
|
4
|
-
import { MavLinkPacketSplitter, MavLinkPacketParser
|
4
|
+
import { MavLinkPacketSplitter, MavLinkPacketParser } from '..'
|
5
5
|
import { minimal, common, ardupilotmega, uavionix, icarous } from '..'
|
6
6
|
|
7
|
-
const file = createReadStream('./
|
7
|
+
const file = createReadStream('./GH-5.bin')
|
8
8
|
|
9
|
-
const splitter = new MavLinkPacketSplitter(
|
9
|
+
const splitter = new MavLinkPacketSplitter()
|
10
10
|
|
11
11
|
const reader = file
|
12
12
|
.pipe(splitter)
|
package/index.ts
CHANGED
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 {
|
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,16 +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
10
|
|
10
11
|
/**
|
11
12
|
* Header definition of the MavLink packet
|
12
13
|
*/
|
13
|
-
|
14
|
+
export class MavLinkPacketHeader {
|
14
15
|
magic: number = 0
|
15
16
|
payloadLength: uint8_t = 0
|
16
17
|
incompatibilityFlags: uint8_t = 0
|
@@ -28,6 +29,8 @@ import { SERIALIZERS, DESERIALIZERS } from './serialization'
|
|
28
29
|
* data classes from the given payload buffer
|
29
30
|
*/
|
30
31
|
export abstract class MavLinkProtocol {
|
32
|
+
protected readonly log = Logger.getLogger(this)
|
33
|
+
|
31
34
|
static NAME = 'unknown'
|
32
35
|
static START_BYTE = 0
|
33
36
|
static PAYLOAD_OFFSET = 0
|
@@ -64,6 +67,8 @@ export abstract class MavLinkProtocol {
|
|
64
67
|
* Deserialize payload into actual data class
|
65
68
|
*/
|
66
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
|
+
|
67
72
|
const instance = new clazz()
|
68
73
|
clazz.FIELDS.forEach(field => {
|
69
74
|
const deserialize = DESERIALIZERS[field.type]
|
@@ -107,6 +112,8 @@ export class MavLinkProtocolV1 extends MavLinkProtocol {
|
|
107
112
|
}
|
108
113
|
|
109
114
|
serialize(message: MavLinkData, seq: number): Buffer {
|
115
|
+
this.log.trace('Serializing message (seq:', seq, ')')
|
116
|
+
|
110
117
|
const definition: MavLinkDataConstructor<MavLinkData> = <any>message.constructor
|
111
118
|
const buffer = Buffer.from(new Uint8Array(MavLinkProtocolV1.PAYLOAD_OFFSET + definition.PAYLOAD_LENGTH + MavLinkProtocol.CHECKSUM_LENGTH))
|
112
119
|
|
@@ -133,6 +140,8 @@ export class MavLinkProtocolV1 extends MavLinkProtocol {
|
|
133
140
|
}
|
134
141
|
|
135
142
|
header(buffer: Buffer): MavLinkPacketHeader {
|
143
|
+
this.log.trace('Reading header from buffer (len:', buffer.length, ')')
|
144
|
+
|
136
145
|
const startByte = buffer.readUInt8(0)
|
137
146
|
if (startByte !== MavLinkProtocolV1.START_BYTE) {
|
138
147
|
throw new Error(`Invalid start byte (expected: ${MavLinkProtocolV1.START_BYTE}, got ${startByte})`)
|
@@ -152,12 +161,16 @@ export class MavLinkProtocolV1 extends MavLinkProtocol {
|
|
152
161
|
/**
|
153
162
|
* Deserialize packet checksum
|
154
163
|
*/
|
155
|
-
|
164
|
+
crc(buffer: Buffer): uint16_t {
|
165
|
+
this.log.trace('Reading crc from buffer (len:', buffer.length, ')')
|
166
|
+
|
156
167
|
const plen = buffer.readUInt8(1)
|
157
168
|
return buffer.readUInt16LE(MavLinkProtocolV1.PAYLOAD_OFFSET + plen)
|
158
169
|
}
|
159
170
|
|
160
171
|
payload(buffer: Buffer): Buffer {
|
172
|
+
this.log.trace('Reading payload from buffer (len:', buffer.length, ')')
|
173
|
+
|
161
174
|
const plen = buffer.readUInt8(1)
|
162
175
|
const payload = buffer.slice(MavLinkProtocolV1.PAYLOAD_OFFSET, MavLinkProtocolV1.PAYLOAD_OFFSET + plen)
|
163
176
|
const padding = Buffer.from(new Uint8Array(255 - payload.length))
|
@@ -188,6 +201,8 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
|
|
188
201
|
}
|
189
202
|
|
190
203
|
serialize(message: MavLinkData, seq: number): Buffer {
|
204
|
+
this.log.trace('Serializing message (seq:', seq, ')')
|
205
|
+
|
191
206
|
const definition: MavLinkDataConstructor<MavLinkData> = <any>message.constructor
|
192
207
|
const buffer = Buffer.from(new Uint8Array(MavLinkProtocolV2.PAYLOAD_OFFSET + definition.PAYLOAD_LENGTH + MavLinkProtocol.CHECKSUM_LENGTH))
|
193
208
|
|
@@ -224,9 +239,12 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
|
|
224
239
|
* @param buffer buffer with the original, unsigned package
|
225
240
|
* @param linkId id of the link
|
226
241
|
* @param key key to sign the package with
|
242
|
+
* @param timestamp optional timestamp for packet signing (default: Date.now())
|
227
243
|
* @returns signed package
|
228
244
|
*/
|
229
|
-
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
|
+
|
230
248
|
const result = Buffer.concat([
|
231
249
|
buffer,
|
232
250
|
Buffer.from(new Uint8Array(MavLinkPacketSignature.SIGNATURE_LENGTH))
|
@@ -234,7 +252,7 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
|
|
234
252
|
|
235
253
|
const signer = new MavLinkPacketSignature(result)
|
236
254
|
signer.linkId = linkId
|
237
|
-
signer.timestamp =
|
255
|
+
signer.timestamp = timestamp
|
238
256
|
signer.signature = signer.calculate(key)
|
239
257
|
|
240
258
|
return result
|
@@ -255,6 +273,8 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
|
|
255
273
|
}
|
256
274
|
|
257
275
|
header(buffer: Buffer): MavLinkPacketHeader {
|
276
|
+
this.log.trace('Reading header from buffer (len:', buffer.length, ')')
|
277
|
+
|
258
278
|
const startByte = buffer.readUInt8(0)
|
259
279
|
if (startByte !== MavLinkProtocolV2.START_BYTE) {
|
260
280
|
throw new Error(`Invalid start byte (expected: ${MavLinkProtocolV2.START_BYTE}, got ${startByte})`)
|
@@ -277,11 +297,15 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
|
|
277
297
|
* Deserialize packet checksum
|
278
298
|
*/
|
279
299
|
crc(buffer: Buffer): uint16_t {
|
300
|
+
this.log.trace('Reading crc from buffer (len:', buffer.length, ')')
|
301
|
+
|
280
302
|
const plen = buffer.readUInt8(1)
|
281
303
|
return buffer.readUInt16LE(MavLinkProtocolV2.PAYLOAD_OFFSET + plen)
|
282
304
|
}
|
283
305
|
|
284
306
|
payload(buffer: Buffer): Buffer {
|
307
|
+
this.log.trace('Reading payload from buffer (len:', buffer.length, ')')
|
308
|
+
|
285
309
|
const plen = buffer.readUInt8(1)
|
286
310
|
const payload = buffer.slice(MavLinkProtocolV2.PAYLOAD_OFFSET, MavLinkProtocolV2.PAYLOAD_OFFSET + plen)
|
287
311
|
const padding = Buffer.from(new Uint8Array(255 - payload.length))
|
@@ -289,6 +313,8 @@ export class MavLinkProtocolV2 extends MavLinkProtocol {
|
|
289
313
|
}
|
290
314
|
|
291
315
|
signature(buffer: Buffer, header: MavLinkPacketHeader): MavLinkPacketSignature {
|
316
|
+
this.log.trace('Reading signature from buffer (len:', buffer.length, ')')
|
317
|
+
|
292
318
|
if (header.incompatibilityFlags & MavLinkProtocolV2.IFLAG_SIGNED) {
|
293
319
|
return new MavLinkPacketSignature(buffer)
|
294
320
|
} else {
|
@@ -432,7 +458,8 @@ export class MavLinkPacket {
|
|
432
458
|
+ `msgid: ${this.header.msgid}, `
|
433
459
|
+ `seq: ${this.header.seq}, `
|
434
460
|
+ `plen: ${this.header.payloadLength}, `
|
435
|
-
+ `
|
461
|
+
+ `magic: ${MSG_ID_MAGIC_NUMBER[this.header.msgid]} (${hex(MSG_ID_MAGIC_NUMBER[this.header.msgid])}), `
|
462
|
+
+ `crc: ${hex(this.crc, 4)}`
|
436
463
|
+ this.signatureToString(this.signature)
|
437
464
|
+ ')'
|
438
465
|
}
|
@@ -447,19 +474,28 @@ export class MavLinkPacket {
|
|
447
474
|
*/
|
448
475
|
enum PacketValidationResult { VALID, INVALID, UNKNOWN }
|
449
476
|
|
477
|
+
type BufferCallback = (buffer: Buffer) => void
|
478
|
+
|
450
479
|
/**
|
451
480
|
* A transform stream that splits the incomming data stream into chunks containing full MavLink messages
|
452
481
|
*/
|
453
482
|
export class MavLinkPacketSplitter extends Transform {
|
483
|
+
protected readonly log = Logger.getLogger(this)
|
484
|
+
|
454
485
|
private buffer = Buffer.from([])
|
455
|
-
private
|
486
|
+
private onCrcError = null
|
456
487
|
private _validPackagesCount = 0
|
457
488
|
private _unknownPackagesCount = 0
|
458
489
|
private _invalidPackagesCount = 0
|
459
490
|
|
460
|
-
|
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 = () => {}) {
|
461
497
|
super(opts)
|
462
|
-
this.
|
498
|
+
this.onCrcError = onCrcError
|
463
499
|
}
|
464
500
|
|
465
501
|
_transform(chunk: Buffer, encoding, callback: TransformCallback) {
|
@@ -477,38 +513,51 @@ export class MavLinkPacketSplitter extends Transform {
|
|
477
513
|
this.buffer = this.buffer.slice(offset)
|
478
514
|
}
|
479
515
|
|
516
|
+
this.log.debug('Found potential packet start at', offset)
|
517
|
+
|
480
518
|
// get protocol this buffer is encoded with
|
481
519
|
const Protocol = this.getPacketProtocol(this.buffer)
|
482
520
|
|
521
|
+
this.log.debug('Packet protocol is', Protocol.NAME)
|
522
|
+
|
483
523
|
// check if the buffer contains at least the minumum size of data
|
484
524
|
if (this.buffer.length < Protocol.PAYLOAD_OFFSET + MavLinkProtocol.CHECKSUM_LENGTH) {
|
485
525
|
// current buffer shorter than the shortest message - skipping
|
526
|
+
this.log.debug('Current buffer shorter than the shortest message - skipping')
|
486
527
|
break
|
487
528
|
}
|
488
529
|
|
489
530
|
// check if the current buffer contains the entire message
|
490
531
|
const expectedBufferLength = this.readPacketLength(this.buffer, Protocol)
|
532
|
+
this.log.debug('Expected buffer length:', expectedBufferLength, `(${hex(expectedBufferLength)})`)
|
491
533
|
if (this.buffer.length < expectedBufferLength) {
|
492
534
|
// current buffer is not fully retrieved yet - skipping
|
535
|
+
this.log.debug('Current buffer is not fully retrieved yet - skipping')
|
493
536
|
break
|
537
|
+
} else {
|
538
|
+
this.log.debug('Current buffer length:', this.buffer.length, `(${hex(this.buffer.length, 4)})`)
|
494
539
|
}
|
495
540
|
|
496
541
|
// retrieve the buffer based on payload size
|
497
542
|
const buffer = this.buffer.slice(0, expectedBufferLength)
|
543
|
+
this.log.debug('Recognized buffer length:', buffer.length, `(${hex(buffer.length, 2)})`)
|
498
544
|
|
499
545
|
switch (this.validatePacket(buffer, Protocol)) {
|
500
546
|
case PacketValidationResult.VALID:
|
547
|
+
this.log.debug('Found a valid packet')
|
501
548
|
this._validPackagesCount++
|
502
549
|
this.push(buffer)
|
503
550
|
// truncate the buffer to remove the current message
|
504
551
|
this.buffer = this.buffer.slice(expectedBufferLength)
|
505
552
|
break
|
506
553
|
case PacketValidationResult.INVALID:
|
554
|
+
this.log.debug('Found an invalid packet - skipping')
|
507
555
|
this._invalidPackagesCount++
|
508
556
|
// truncate the buffer to remove the wrongly identified STX
|
509
557
|
this.buffer = this.buffer.slice(1)
|
510
558
|
break
|
511
559
|
case PacketValidationResult.UNKNOWN:
|
560
|
+
this.log.debug('Found an unknown packet - skipping')
|
512
561
|
this._unknownPackagesCount++
|
513
562
|
// truncate the buffer to remove the current message
|
514
563
|
this.buffer = this.buffer.slice(expectedBufferLength)
|
@@ -570,21 +619,22 @@ export class MavLinkPacketSplitter extends Transform {
|
|
570
619
|
return PacketValidationResult.VALID
|
571
620
|
} else {
|
572
621
|
// CRC mismatch
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
)
|
579
|
-
|
580
|
-
|
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
|
+
|
581
632
|
return PacketValidationResult.INVALID
|
582
633
|
}
|
583
634
|
} else {
|
584
635
|
// unknown message (as in not generated from the XML sources)
|
585
|
-
|
586
|
-
|
587
|
-
}
|
636
|
+
this.log.debug(`Unknown message with id ${header.msgid} (magic number not found) - skipping`)
|
637
|
+
|
588
638
|
return PacketValidationResult.UNKNOWN
|
589
639
|
}
|
590
640
|
}
|
@@ -613,7 +663,7 @@ export class MavLinkPacketSplitter extends Transform {
|
|
613
663
|
* Reset the number of valid packages
|
614
664
|
*/
|
615
665
|
resetValidPackagesCount() {
|
616
|
-
this
|
666
|
+
this._validPackagesCount = 0
|
617
667
|
}
|
618
668
|
|
619
669
|
/**
|
@@ -627,7 +677,7 @@ export class MavLinkPacketSplitter extends Transform {
|
|
627
677
|
* Reset the number of invalid packages
|
628
678
|
*/
|
629
679
|
resetInvalidPackagesCount() {
|
630
|
-
this
|
680
|
+
this._invalidPackagesCount = 0
|
631
681
|
}
|
632
682
|
|
633
683
|
/**
|
@@ -641,7 +691,7 @@ export class MavLinkPacketSplitter extends Transform {
|
|
641
691
|
* Reset the number of invalid packages
|
642
692
|
*/
|
643
693
|
resetUnknownPackagesCount() {
|
644
|
-
this
|
694
|
+
this._unknownPackagesCount = 0
|
645
695
|
}
|
646
696
|
}
|
647
697
|
|
@@ -649,6 +699,8 @@ export class MavLinkPacketSplitter extends Transform {
|
|
649
699
|
* A transform stream that takes a buffer with data and converts it to MavLinkPacket object
|
650
700
|
*/
|
651
701
|
export class MavLinkPacketParser extends Transform {
|
702
|
+
protected readonly log = Logger.getLogger(this)
|
703
|
+
|
652
704
|
constructor(opts = {}) {
|
653
705
|
super({ ...opts, objectMode: true })
|
654
706
|
}
|
@@ -661,7 +713,7 @@ export class MavLinkPacketParser extends Transform {
|
|
661
713
|
case MavLinkProtocolV2.START_BYTE:
|
662
714
|
return new MavLinkProtocolV2()
|
663
715
|
default:
|
664
|
-
throw new Error(`Unknown protocol '${startByte
|
716
|
+
throw new Error(`Unknown protocol '${hex(startByte)}'`)
|
665
717
|
}
|
666
718
|
}
|
667
719
|
|
@@ -680,6 +732,17 @@ export class MavLinkPacketParser extends Transform {
|
|
680
732
|
}
|
681
733
|
}
|
682
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
|
+
|
683
746
|
let seq = 0
|
684
747
|
|
685
748
|
/**
|
@@ -710,14 +773,21 @@ export async function send(stream: Writable, msg: MavLinkData, protocol: MavLink
|
|
710
773
|
* @param linkId link id for the signature
|
711
774
|
* @param sysid system id
|
712
775
|
* @param compid component id
|
776
|
+
* @param timestamp optional timestamp for packet signing (default: Date.now())
|
713
777
|
* @returns number of bytes sent
|
714
778
|
*/
|
715
|
-
export async function sendSigned(
|
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
|
+
) {
|
716
786
|
return new Promise((resolve, reject) => {
|
717
787
|
const protocol = new MavLinkProtocolV2(sysid, compid, MavLinkProtocolV2.IFLAG_SIGNED)
|
718
788
|
const b1 = protocol.serialize(msg, seq++)
|
719
789
|
seq &= 255
|
720
|
-
const b2 = protocol.sign(b1, linkId, key)
|
790
|
+
const b2 = protocol.sign(b1, linkId, key, timestamp)
|
721
791
|
stream.write(b2, err => {
|
722
792
|
if (err) reject(err)
|
723
793
|
else resolve(b2.length)
|
package/lib/serialization.ts
CHANGED
@@ -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 *
|
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) => {
|