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.
- package/README.md +11 -1
- 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 +21 -5
- package/index.ts +2 -0
- package/lib/logger.ts +128 -0
- package/lib/mavesp.ts +2 -1
- package/lib/mavlink.ts +98 -29
- 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,18 +1,33 @@
|
|
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
|
+
import { minimal, common, ardupilotmega, uavionix, icarous } from '..'
|
5
6
|
|
6
|
-
const file = createReadStream('./
|
7
|
+
const file = createReadStream('./GH-5.bin')
|
7
8
|
|
8
|
-
const splitter = new MavLinkPacketSplitter(
|
9
|
+
const splitter = new MavLinkPacketSplitter()
|
9
10
|
|
10
11
|
const reader = file
|
11
12
|
.pipe(splitter)
|
12
13
|
.pipe(new MavLinkPacketParser())
|
13
14
|
|
14
|
-
|
15
|
-
|
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
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,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
|
-
|
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
|
-
|
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 =
|
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
|
-
+ `
|
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
|
486
|
+
private onCrcError = null
|
457
487
|
private _validPackagesCount = 0
|
458
488
|
private _unknownPackagesCount = 0
|
459
489
|
private _invalidPackagesCount = 0
|
460
490
|
|
461
|
-
|
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.
|
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
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
)
|
580
|
-
|
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
|
-
|
587
|
-
|
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
|
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
|
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
|
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
|
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(
|
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)
|
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) => {
|