node-red-contrib-ntrip 0.2.3 → 0.2.8
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/.github/dependabot.yml +23 -0
- package/.github/workflows/node.js.yml +22 -21
- package/.github/workflows/release.yml +65 -0
- package/.prettierignore +20 -0
- package/.prettierrc.json +9 -0
- package/CHANGELOG.md +69 -0
- package/CLAUDE.md +73 -0
- package/README.md +185 -18
- package/doc/architecture/README.md +19 -0
- package/doc/architecture/adr/0001-single-registration-file.md +64 -0
- package/doc/architecture/adr/0002-ntrip-uploader-extension.md +71 -0
- package/doc/architecture/adr/0003-two-output-decoder-design.md +73 -0
- package/doc/architecture/adr/0004-stateful-handshake-interception.md +74 -0
- package/doc/architecture/adr/0005-rtcm-partial-frame-buffer.md +87 -0
- package/doc/architecture/adr/0006-nmea-multi-sentence-split.md +66 -0
- package/doc/architecture/adr/0007-mocha-test-helper-stack.md +67 -0
- package/doc/architecture/adr/0008-coordinate-gating-sentinel.md +76 -0
- package/doc/architecture/adr/README.md +18 -0
- package/doc/architecture/architecture-decisions.md +60 -0
- package/doc/architecture/behavioural-design.md +226 -0
- package/doc/architecture/errors-and-weaknesses.md +71 -0
- package/doc/architecture/future-improvements.md +130 -0
- package/doc/architecture/overview.md +77 -0
- package/doc/architecture/refactoring-recommendations.md +114 -0
- package/doc/architecture/statistics.md +118 -0
- package/doc/architecture/structural-design.md +141 -0
- package/eslint.config.js +36 -0
- package/examples/rtcm-encode.json +1 -0
- package/ntrip/99-ntrip.html +289 -31
- package/ntrip/99-ntrip.js +15 -12
- package/ntrip/lib/ntrip-client.js +23 -18
- package/ntrip/nodes/nmea-decoder-node.js +77 -34
- package/ntrip/nodes/nmea-encoder-node.js +68 -47
- package/ntrip/nodes/ntrip-client-node.js +143 -123
- package/ntrip/nodes/rtcm-decoder-node.js +65 -47
- package/ntrip/nodes/rtcm-encoder-node.js +90 -0
- package/package.json +20 -2
- package/test/nmea-decoder.spec.js +146 -0
- package/test/nmea-encoder.spec.js +104 -0
- package/test/rtcm-decoder.spec.js +116 -0
- package/test/rtcm-encoder.spec.js +144 -0
- package/.github/workflows/npm-publish.yml +0 -33
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const {
|
|
5
|
-
NmeaTransport,
|
|
4
|
+
const {
|
|
5
|
+
NmeaTransport,
|
|
6
6
|
NmeaMessageUnknown,
|
|
7
7
|
NmeaMessage,
|
|
8
8
|
NmeaMessageDtm,
|
|
9
9
|
NmeaMessageGbs,
|
|
10
10
|
NmeaMessageGga,
|
|
11
11
|
NmeaMessageGll,
|
|
12
|
-
NmeaMessageGns,
|
|
13
|
-
NmeaMessageGrs,
|
|
12
|
+
NmeaMessageGns,
|
|
13
|
+
NmeaMessageGrs,
|
|
14
14
|
NmeaMessageGsa,
|
|
15
15
|
NmeaMessageGst,
|
|
16
16
|
NmeaMessageGsv,
|
|
@@ -30,24 +30,53 @@ module.exports = function (RED) {
|
|
|
30
30
|
node.nmeaMessagesReceived = 0;
|
|
31
31
|
node.invalidMessagesReceived = 0;
|
|
32
32
|
|
|
33
|
+
function updateStatus(ok) {
|
|
34
|
+
node.status({
|
|
35
|
+
fill: ok ? 'green' : 'red',
|
|
36
|
+
shape: 'ring',
|
|
37
|
+
text: 'NMEA: ' + node.nmeaMessagesReceived + ' Invalid: ' + node.invalidMessagesReceived,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function safeToString(value) {
|
|
42
|
+
if (value === undefined || value === null) {
|
|
43
|
+
return '';
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
return String(value);
|
|
47
|
+
} catch {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
this.on('input', function (msg) {
|
|
53
|
+
let payload = msg.payload;
|
|
54
|
+
|
|
55
|
+
if (payload == null || payload.nmeaMessage == null || payload.messageType == null) {
|
|
56
|
+
let errMsg = {
|
|
57
|
+
payload: {
|
|
58
|
+
error: 'Invalid input. Expected payload with nmeaMessage and messageType properties.',
|
|
59
|
+
input: payload,
|
|
60
|
+
inputString: safeToString(payload),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
node.send([null, errMsg]);
|
|
64
|
+
node.invalidMessagesReceived++;
|
|
65
|
+
updateStatus(false);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let input = payload.nmeaMessage;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
let messageType = String(payload.messageType).toUpperCase();
|
|
34
73
|
|
|
35
|
-
let input = msg.payload.nmeaMessage;
|
|
36
|
-
let isNmeaMessage = input.prototype instanceof NmeaMessage;
|
|
37
|
-
let messageType = msg.payload.messageType;
|
|
38
|
-
messageType = messageType.toUpperCase();
|
|
39
|
-
|
|
40
|
-
try
|
|
41
|
-
{
|
|
42
|
-
let message;
|
|
43
74
|
let nmeaMessage;
|
|
44
|
-
if(
|
|
45
|
-
nmeaMessage = input;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
|
|
75
|
+
if (input instanceof NmeaMessage) {
|
|
76
|
+
nmeaMessage = input;
|
|
77
|
+
} else {
|
|
49
78
|
switch (messageType) {
|
|
50
|
-
case '
|
|
79
|
+
case 'OBJECT':
|
|
51
80
|
nmeaMessage = NmeaMessageUnknown.construct(input);
|
|
52
81
|
break;
|
|
53
82
|
case 'DTM':
|
|
@@ -102,50 +131,42 @@ module.exports = function (RED) {
|
|
|
102
131
|
nmeaMessage = NmeaMessageZda.construct(input);
|
|
103
132
|
break;
|
|
104
133
|
default:
|
|
105
|
-
|
|
106
|
-
break;
|
|
134
|
+
throw new Error('Unsupported NMEA message type: ' + messageType);
|
|
107
135
|
}
|
|
108
136
|
}
|
|
109
137
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
input : input
|
|
138
|
+
let message = NmeaTransport.encode(nmeaMessage);
|
|
139
|
+
|
|
140
|
+
let outMsg = {
|
|
141
|
+
payload: {
|
|
142
|
+
nmeaMessage: message,
|
|
143
|
+
messageType: payload.messageType,
|
|
144
|
+
input: input,
|
|
118
145
|
},
|
|
119
146
|
};
|
|
120
147
|
|
|
121
|
-
node.send([
|
|
148
|
+
node.send([outMsg, null]);
|
|
122
149
|
node.nmeaMessagesReceived++;
|
|
123
|
-
|
|
124
|
-
catch(ex)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
150
|
+
updateStatus(true);
|
|
151
|
+
} catch (ex) {
|
|
152
|
+
let errMsg = {
|
|
153
|
+
payload: {
|
|
154
|
+
error: ex,
|
|
155
|
+
input: input,
|
|
156
|
+
inputString: safeToString(input),
|
|
157
|
+
},
|
|
132
158
|
};
|
|
133
|
-
node.send([null,
|
|
159
|
+
node.send([null, errMsg]);
|
|
134
160
|
node.invalidMessagesReceived++;
|
|
161
|
+
updateStatus(false);
|
|
135
162
|
}
|
|
136
|
-
|
|
137
|
-
node.status({
|
|
138
|
-
fill: 'green',
|
|
139
|
-
shape: 'ring',
|
|
140
|
-
text: 'NMEA: ' + node.nmeaMessagesReceived + ' Invalid: ' + node.invalidMessagesReceived,
|
|
141
|
-
});
|
|
142
163
|
});
|
|
143
164
|
|
|
144
|
-
this.on('close', function(done) {
|
|
165
|
+
this.on('close', function (done) {
|
|
145
166
|
node.status({});
|
|
146
167
|
done();
|
|
147
168
|
});
|
|
148
169
|
}
|
|
149
170
|
|
|
150
171
|
return NmeaEncoderNode;
|
|
151
|
-
};
|
|
172
|
+
};
|
|
@@ -1,158 +1,178 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const NtripClient = require(
|
|
4
|
+
const NtripClient = require('../lib/ntrip-client.js');
|
|
5
5
|
const NtripServerOkReply = 'ICY 200 OK';
|
|
6
6
|
const NtripServerNotOkReply = 'ICY 406';
|
|
7
7
|
const NtripServerMissingMountpoint = 'SOURCETABLE 200 OK';
|
|
8
|
+
const HandshakePrefixBytes = 64;
|
|
8
9
|
|
|
9
10
|
function NtripClientNode(config) {
|
|
10
11
|
RED.nodes.createNode(this, config);
|
|
11
12
|
let node = this;
|
|
12
13
|
node.messagesReceived = 0;
|
|
14
|
+
node.messagesSent = 0;
|
|
13
15
|
node.passthrough = config.passthrough || false;
|
|
14
|
-
|
|
15
|
-
let host = config.host;
|
|
16
|
-
let port = config.port || 2101;
|
|
16
|
+
|
|
17
|
+
let host = typeof config.host === 'string' ? config.host.trim() : '';
|
|
18
|
+
let port = parseInt(config.port, 10) || 2101;
|
|
17
19
|
let mountpoint = config.mountpoint;
|
|
18
20
|
let mode = config.mode || 'download';
|
|
19
21
|
let authmode = config.authmode || 'legacy';
|
|
20
|
-
|
|
22
|
+
let interval = parseInt(config.interval, 10) || 1000;
|
|
23
|
+
|
|
24
|
+
if (host === '') {
|
|
25
|
+
node.status({ fill: 'red', shape: 'ring', text: 'Host not configured' });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let options = {
|
|
30
|
+
host: host,
|
|
31
|
+
port: port,
|
|
32
|
+
mountpoint: mountpoint,
|
|
33
|
+
username: node.credentials.username,
|
|
34
|
+
password: node.credentials.password,
|
|
35
|
+
interval: interval,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Depending on the caster the clients need to provide the location via GGA sentence.
|
|
39
|
+
let x = parseFloat(config.xcoordinate);
|
|
40
|
+
let y = parseFloat(config.ycoordinate);
|
|
41
|
+
let z = parseFloat(config.zcoordinate);
|
|
42
|
+
|
|
43
|
+
if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) && !(x === 0 && y === 0 && z === 0)) {
|
|
44
|
+
options.xyz = [x, y, z];
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
let client;
|
|
22
|
-
if(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
mountpoint: mountpoint,
|
|
28
|
-
username: node.credentials.username,
|
|
29
|
-
password: node.credentials.password,
|
|
30
|
-
interval: config.interval,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// Depending on the caster the clients need to provide the location via GGA sentence.
|
|
34
|
-
let x = parseFloat(config.xcoordinate || 0);
|
|
35
|
-
let y = parseFloat(config.ycoordinate || 0);
|
|
36
|
-
let z = parseFloat(config.zcoordinate || 0);
|
|
37
|
-
|
|
38
|
-
if (x !== 0 && y !== 0 && z !== 0) {
|
|
39
|
-
options.xyz = [x, y, z];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (mode === 'download') {
|
|
43
|
-
// Clients only consume ntrip data.
|
|
44
|
-
client = NtripClient.createDownloader(options);
|
|
45
|
-
}
|
|
46
|
-
else if (mode === 'upload') {
|
|
47
|
-
// Uploaders send RTCM data to a mountpoint so that clients can consume it.
|
|
48
|
-
options.authmode = authmode;
|
|
48
|
+
if (mode === 'download') {
|
|
49
|
+
client = NtripClient.createDownloader(options);
|
|
50
|
+
} else if (mode === 'upload') {
|
|
51
|
+
options.authmode = authmode;
|
|
52
|
+
try {
|
|
49
53
|
client = NtripClient.createUploader(options);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
|
|
56
|
+
node.error('Failed to create uploader: ' + (error.message || error));
|
|
57
|
+
return;
|
|
50
58
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
} else {
|
|
60
|
+
node.status({ fill: 'red', shape: 'ring', text: 'Unsupported mode: ' + mode });
|
|
61
|
+
node.error('Mode not supported: ' + mode);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
fill: 'green',
|
|
59
|
-
shape: 'ring',
|
|
60
|
-
text: 'Messages ' + node.messagesReceived,
|
|
61
|
-
});
|
|
65
|
+
// Track handshake state so we only intercept control replies during the handshake,
|
|
66
|
+
// not on every subsequent data packet. Reset on socket close so reconnects work.
|
|
67
|
+
let connected = false;
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
function updateStatus() {
|
|
70
|
+
node.status({
|
|
71
|
+
fill: 'green',
|
|
72
|
+
shape: 'ring',
|
|
73
|
+
text: 'Rx ' + node.messagesReceived + ' Tx ' + node.messagesSent,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
66
76
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
client.on('data', (data) => {
|
|
78
|
+
if (!connected) {
|
|
79
|
+
let prefix = data.toString('utf8', 0, Math.min(data.length, HandshakePrefixBytes));
|
|
80
|
+
if (prefix.startsWith(NtripServerOkReply)) {
|
|
81
|
+
connected = true;
|
|
82
|
+
node.status({ fill: 'green', shape: 'ring', text: 'NTRIP server connected.' });
|
|
83
|
+
// Forward any RTCM bytes that arrived in the same TCP segment as the handshake reply.
|
|
84
|
+
let headerEnd = data.indexOf('\r\n\r\n');
|
|
85
|
+
if (headerEnd !== -1 && headerEnd + 4 < data.length) {
|
|
86
|
+
node.messagesReceived++;
|
|
87
|
+
node.send({ payload: data.slice(headerEnd + 4) });
|
|
88
|
+
updateStatus();
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
75
91
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
node.
|
|
79
|
-
|
|
80
|
-
shape: 'ring',
|
|
81
|
-
text: 'NTRIP server rejected connection.',
|
|
82
|
-
});
|
|
83
|
-
node.error('Server rejected connect: ' + response);
|
|
84
|
-
}
|
|
85
|
-
else if (response.startsWith(NtripServerMissingMountpoint)) {
|
|
86
|
-
node.status({
|
|
87
|
-
fill: 'red',
|
|
88
|
-
shape: 'ring',
|
|
89
|
-
text: 'No mountpont ' + mountpoint,
|
|
90
|
-
});
|
|
91
|
-
node.error('No mountpoint: ' + response);
|
|
92
|
+
if (prefix.startsWith(NtripServerNotOkReply)) {
|
|
93
|
+
node.status({ fill: 'red', shape: 'ring', text: 'NTRIP server rejected connection.' });
|
|
94
|
+
node.error('Server rejected connect: ' + prefix);
|
|
95
|
+
return;
|
|
92
96
|
}
|
|
93
|
-
|
|
94
|
-
node.
|
|
97
|
+
if (prefix.startsWith(NtripServerMissingMountpoint)) {
|
|
98
|
+
node.status({ fill: 'red', shape: 'ring', text: 'No mountpoint ' + mountpoint });
|
|
99
|
+
node.error('No mountpoint: ' + prefix);
|
|
100
|
+
return;
|
|
95
101
|
}
|
|
96
|
-
|
|
102
|
+
// Unexpected: data arrived before any recognised handshake reply.
|
|
103
|
+
// Treat as connected so we don't drop further frames.
|
|
104
|
+
connected = true;
|
|
105
|
+
}
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
node.messagesReceived++;
|
|
108
|
+
node.send({ payload: data });
|
|
109
|
+
updateStatus();
|
|
110
|
+
});
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
node.error('Error: ' + e.message);
|
|
107
|
-
});
|
|
108
|
-
} else {
|
|
109
|
-
node.log(error);
|
|
110
|
-
node.error('Error: ' + error);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
112
|
+
client.on('close', () => {
|
|
113
|
+
connected = false;
|
|
114
|
+
node.log('Closed NTRIP connection.');
|
|
115
|
+
});
|
|
113
116
|
|
|
117
|
+
client.on('error', (error) => {
|
|
118
|
+
connected = false;
|
|
119
|
+
if (typeof AggregateError !== 'undefined' && error instanceof AggregateError) {
|
|
120
|
+
error.errors.forEach((e, i) => {
|
|
121
|
+
node.log('Error ' + (i + 1) + ': ' + e.message);
|
|
122
|
+
node.error('Error: ' + e.message);
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
let text = error && error.message ? error.message : String(error);
|
|
126
|
+
node.log(text);
|
|
127
|
+
node.error('Error: ' + text);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
try {
|
|
114
132
|
client.run();
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
133
|
+
} catch (error) {
|
|
134
|
+
node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
|
|
135
|
+
node.error('Failed to start client: ' + (error.message || error));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
node.status({ fill: 'yellow', shape: 'ring', text: 'connecting...' });
|
|
140
|
+
|
|
141
|
+
this.on('input', function (msg) {
|
|
142
|
+
let payload = msg.payload;
|
|
143
|
+
if (payload === undefined || payload === null) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
120
146
|
|
|
121
|
-
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
let data = msg.payload;
|
|
132
|
-
if (Array.isArray(data)) {
|
|
133
|
-
client.setXYZ(data);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
client.write(data);
|
|
137
|
-
|
|
138
|
-
if(node.passthrough) {
|
|
139
|
-
node.send(msg);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
} catch (error) {
|
|
143
|
-
node.status({ fill: 'red', shape: 'ring', text: error });
|
|
144
|
-
node.error('Failed to write data: ' + error, error);
|
|
147
|
+
try {
|
|
148
|
+
if (Array.isArray(payload)) {
|
|
149
|
+
client.setXYZ(payload);
|
|
150
|
+
} else {
|
|
151
|
+
client.write(payload);
|
|
152
|
+
node.messagesSent++;
|
|
153
|
+
if (node.passthrough) {
|
|
154
|
+
node.send(msg);
|
|
145
155
|
}
|
|
156
|
+
updateStatus();
|
|
146
157
|
}
|
|
147
|
-
})
|
|
158
|
+
} catch (error) {
|
|
159
|
+
let text = error && error.message ? error.message : String(error);
|
|
160
|
+
node.status({ fill: 'red', shape: 'ring', text: text });
|
|
161
|
+
node.error('Failed to write data: ' + text, msg);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
148
164
|
|
|
149
|
-
|
|
165
|
+
this.on('close', function (done) {
|
|
166
|
+
try {
|
|
167
|
+
client.removeAllListeners();
|
|
150
168
|
client.close();
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
169
|
+
} catch {
|
|
170
|
+
// ignore — node is shutting down anyway
|
|
171
|
+
}
|
|
172
|
+
node.status({});
|
|
173
|
+
done();
|
|
174
|
+
});
|
|
155
175
|
}
|
|
156
|
-
|
|
176
|
+
|
|
157
177
|
return NtripClientNode;
|
|
158
|
-
};
|
|
178
|
+
};
|
|
@@ -1,71 +1,89 @@
|
|
|
1
1
|
module.exports = function (RED) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const { RtcmTransport
|
|
5
|
-
|
|
4
|
+
const { RtcmTransport } = require('@gnss/rtcm');
|
|
5
|
+
|
|
6
|
+
// Cap on pending bytes when a decode keeps failing — guards against runaway memory
|
|
7
|
+
// if upstream feeds us garbage rather than truly-partial RTCM frames.
|
|
8
|
+
const MaxPendingBytes = 65536;
|
|
9
|
+
|
|
6
10
|
function RtcmDecoderNode(config) {
|
|
7
11
|
RED.nodes.createNode(this, config);
|
|
8
12
|
let node = this;
|
|
9
13
|
node.rtcmMessagesReceived = 0;
|
|
10
14
|
node.invalidMessagesReceived = 0;
|
|
15
|
+
node.pendingBuffer = Buffer.alloc(0);
|
|
16
|
+
|
|
17
|
+
function updateStatus() {
|
|
18
|
+
node.status({
|
|
19
|
+
fill: 'green',
|
|
20
|
+
shape: 'ring',
|
|
21
|
+
text: 'RTCM: ' + node.rtcmMessagesReceived + ' Invalid: ' + node.invalidMessagesReceived,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
this.on('input', function (msg) {
|
|
26
|
+
if (msg.payload === undefined || msg.payload === null) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
13
29
|
|
|
14
|
-
let
|
|
15
|
-
let buffer =
|
|
16
|
-
try
|
|
17
|
-
{
|
|
18
|
-
do {
|
|
19
|
-
let message;
|
|
20
|
-
let length;
|
|
21
|
-
[message, length] = RtcmTransport.decode(buffer);
|
|
22
|
-
|
|
23
|
-
let messageType = message.constructor.name.replace('RtcmMessage', '');
|
|
30
|
+
let chunk = Buffer.isBuffer(msg.payload) ? msg.payload : Buffer.from(msg.payload);
|
|
31
|
+
let buffer = node.pendingBuffer.length > 0 ? Buffer.concat([node.pendingBuffer, chunk]) : chunk;
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
while (buffer.length > 0) {
|
|
34
|
+
let message;
|
|
35
|
+
let length;
|
|
36
|
+
try {
|
|
37
|
+
[message, length] = RtcmTransport.decode(buffer);
|
|
38
|
+
} catch (ex) {
|
|
39
|
+
// Likely a partial frame at the tail of this chunk — keep the bytes
|
|
40
|
+
// for the next input event. If we've accumulated too much without a
|
|
41
|
+
// successful decode, drop and emit an error.
|
|
42
|
+
if (buffer.length > MaxPendingBytes) {
|
|
43
|
+
let errMsg = {
|
|
44
|
+
payload: {
|
|
45
|
+
error: ex,
|
|
46
|
+
input: buffer,
|
|
47
|
+
inputString: buffer.toString(),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
node.send([null, errMsg]);
|
|
51
|
+
node.invalidMessagesReceived++;
|
|
52
|
+
buffer = Buffer.alloc(0);
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
if (!Number.isInteger(length) || length <= 0 || length > buffer.length) {
|
|
58
|
+
// Defensive — guarantees forward progress even if the decoder ever
|
|
59
|
+
// returns a non-positive length.
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
36
62
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
let msg = {
|
|
47
|
-
payload : {
|
|
48
|
-
error : ex,
|
|
49
|
-
input : buffer,
|
|
50
|
-
inputString : buffer.toString()
|
|
51
|
-
}
|
|
63
|
+
let messageType = message.constructor.name.replace('RtcmMessage', '');
|
|
64
|
+
let outMsg = {
|
|
65
|
+
payload: {
|
|
66
|
+
rtcm: message.messageType,
|
|
67
|
+
messageType: messageType,
|
|
68
|
+
message: message,
|
|
69
|
+
input: buffer.slice(0, length),
|
|
70
|
+
},
|
|
52
71
|
};
|
|
53
|
-
node.send([
|
|
54
|
-
node.
|
|
72
|
+
node.send([outMsg, null]);
|
|
73
|
+
node.rtcmMessagesReceived++;
|
|
74
|
+
buffer = buffer.slice(length);
|
|
55
75
|
}
|
|
56
76
|
|
|
57
|
-
node.
|
|
58
|
-
|
|
59
|
-
shape: 'ring',
|
|
60
|
-
text: 'RTCM: ' + node.rtcmMessagesReceived + ' Invalid: ' + node.invalidMessagesReceived,
|
|
61
|
-
});
|
|
77
|
+
node.pendingBuffer = buffer;
|
|
78
|
+
updateStatus();
|
|
62
79
|
});
|
|
63
80
|
|
|
64
|
-
this.on('close', function(done) {
|
|
81
|
+
this.on('close', function (done) {
|
|
82
|
+
node.pendingBuffer = Buffer.alloc(0);
|
|
65
83
|
node.status({});
|
|
66
84
|
done();
|
|
67
85
|
});
|
|
68
86
|
}
|
|
69
|
-
|
|
87
|
+
|
|
70
88
|
return RtcmDecoderNode;
|
|
71
|
-
};
|
|
89
|
+
};
|