node-red-contrib-ntrip 0.2.10 → 0.2.11
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/CHANGELOG.md +12 -0
- package/README.md +5 -3
- package/examples/watchdog.json +1 -1
- package/ntrip/lib/ntrip-client.js +70 -72
- package/ntrip/nodes/nmea-decoder-node.js +36 -38
- package/ntrip/nodes/nmea-encoder-node.js +97 -97
- package/ntrip/nodes/ntrip-client-node.js +153 -148
- package/ntrip/nodes/rtcm-decoder-node.js +50 -47
- package/ntrip/nodes/rtcm-encoder-node.js +33 -32
- package/package.json +16 -6
- package/.github/dependabot.yml +0 -23
- package/.github/workflows/node.js.yml +0 -32
- package/.github/workflows/release.yml +0 -65
- package/.prettierignore +0 -20
- package/.prettierrc.json +0 -9
- package/CLAUDE.md +0 -73
- package/doc/architecture/README.md +0 -19
- package/doc/architecture/adr/0001-single-registration-file.md +0 -64
- package/doc/architecture/adr/0002-ntrip-uploader-extension.md +0 -71
- package/doc/architecture/adr/0003-two-output-decoder-design.md +0 -73
- package/doc/architecture/adr/0004-stateful-handshake-interception.md +0 -74
- package/doc/architecture/adr/0005-rtcm-partial-frame-buffer.md +0 -87
- package/doc/architecture/adr/0006-nmea-multi-sentence-split.md +0 -66
- package/doc/architecture/adr/0007-mocha-test-helper-stack.md +0 -67
- package/doc/architecture/adr/0008-coordinate-gating-sentinel.md +0 -76
- package/doc/architecture/adr/README.md +0 -18
- package/doc/architecture/architecture-decisions.md +0 -60
- package/doc/architecture/behavioural-design.md +0 -226
- package/doc/architecture/errors-and-weaknesses.md +0 -71
- package/doc/architecture/future-improvements.md +0 -130
- package/doc/architecture/overview.md +0 -77
- package/doc/architecture/refactoring-recommendations.md +0 -114
- package/doc/architecture/statistics.md +0 -118
- package/doc/architecture/structural-design.md +0 -141
- package/eslint.config.js +0 -36
- package/test/nmea-decoder.spec.js +0 -146
- package/test/nmea-encoder.spec.js +0 -104
- package/test/rtcm-decoder.spec.js +0 -116
- package/test/rtcm-encoder.spec.js +0 -144
|
@@ -8,13 +8,15 @@ module.exports = function (RED) {
|
|
|
8
8
|
const HandshakePrefixBytes = 64;
|
|
9
9
|
|
|
10
10
|
function formatBps(bps) {
|
|
11
|
+
let text;
|
|
11
12
|
if (bps < 1000) {
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
text = Math.round(bps) + ' bps';
|
|
14
|
+
} else if (bps < 1000000) {
|
|
15
|
+
text = (bps / 1000).toFixed(1) + ' kbps';
|
|
16
|
+
} else {
|
|
17
|
+
text = (bps / 1000000).toFixed(2) + ' Mbps';
|
|
16
18
|
}
|
|
17
|
-
return
|
|
19
|
+
return text;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function NtripClientNode(config) {
|
|
@@ -33,168 +35,171 @@ module.exports = function (RED) {
|
|
|
33
35
|
let authmode = config.authmode || 'legacy';
|
|
34
36
|
let interval = parseInt(config.interval, 10) || 1000;
|
|
35
37
|
|
|
38
|
+
let client = null;
|
|
39
|
+
|
|
36
40
|
if (host === '') {
|
|
37
41
|
node.status({ fill: 'red', shape: 'ring', text: 'Host not configured' });
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let options = {
|
|
42
|
-
host: host,
|
|
43
|
-
port: port,
|
|
44
|
-
mountpoint: mountpoint,
|
|
45
|
-
username: node.credentials.username,
|
|
46
|
-
password: node.credentials.password,
|
|
47
|
-
interval: interval,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Depending on the caster the clients need to provide the location via GGA sentence.
|
|
51
|
-
let x = parseFloat(config.xcoordinate);
|
|
52
|
-
let y = parseFloat(config.ycoordinate);
|
|
53
|
-
let z = parseFloat(config.zcoordinate);
|
|
54
|
-
|
|
55
|
-
if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) && !(x === 0 && y === 0 && z === 0)) {
|
|
56
|
-
options.xyz = [x, y, z];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
let client;
|
|
60
|
-
if (mode === 'download') {
|
|
61
|
-
client = NtripClient.createDownloader(options);
|
|
62
|
-
} else if (mode === 'upload') {
|
|
63
|
-
options.authmode = authmode;
|
|
64
|
-
try {
|
|
65
|
-
client = NtripClient.createUploader(options);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
|
|
68
|
-
node.error('Failed to create uploader: ' + (error.message || error));
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
42
|
} else {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Sample inbound byte counter once per second to compute the bps shown in the
|
|
90
|
-
// status badge. Counter is reset each tick — currentBps reflects the most
|
|
91
|
-
// recent 1-second window.
|
|
92
|
-
const bpsSampler = setInterval(() => {
|
|
93
|
-
node.currentBps = node.bytesAccum * 8;
|
|
94
|
-
node.bytesAccum = 0;
|
|
95
|
-
updateStatus();
|
|
96
|
-
}, 1000);
|
|
97
|
-
|
|
98
|
-
client.on('data', (data) => {
|
|
99
|
-
node.bytesAccum += data.length;
|
|
100
|
-
if (!connected) {
|
|
101
|
-
let prefix = data.toString('utf8', 0, Math.min(data.length, HandshakePrefixBytes));
|
|
102
|
-
if (prefix.startsWith(NtripServerOkReply)) {
|
|
103
|
-
connected = true;
|
|
104
|
-
node.status({ fill: 'green', shape: 'ring', text: 'NTRIP server connected.' });
|
|
105
|
-
// Forward any RTCM bytes that arrived in the same TCP segment as the handshake reply.
|
|
106
|
-
let headerEnd = data.indexOf('\r\n\r\n');
|
|
107
|
-
if (headerEnd !== -1 && headerEnd + 4 < data.length) {
|
|
108
|
-
node.messagesReceived++;
|
|
109
|
-
node.send({ payload: data.slice(headerEnd + 4) });
|
|
110
|
-
updateStatus();
|
|
111
|
-
}
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (prefix.startsWith(NtripServerNotOkReply)) {
|
|
115
|
-
node.status({ fill: 'red', shape: 'ring', text: 'NTRIP server rejected connection.' });
|
|
116
|
-
node.error('Server rejected connect: ' + prefix);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
if (prefix.startsWith(NtripServerMissingMountpoint)) {
|
|
120
|
-
node.status({ fill: 'red', shape: 'ring', text: 'No mountpoint ' + mountpoint });
|
|
121
|
-
node.error('No mountpoint: ' + prefix);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
// Unexpected: data arrived before any recognised handshake reply.
|
|
125
|
-
// Treat as connected so we don't drop further frames.
|
|
126
|
-
connected = true;
|
|
43
|
+
let options = {
|
|
44
|
+
host: host,
|
|
45
|
+
port: port,
|
|
46
|
+
mountpoint: mountpoint,
|
|
47
|
+
username: node.credentials.username,
|
|
48
|
+
password: node.credentials.password,
|
|
49
|
+
interval: interval,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Depending on the caster the clients need to provide the location via GGA sentence.
|
|
53
|
+
let x = parseFloat(config.xcoordinate);
|
|
54
|
+
let y = parseFloat(config.ycoordinate);
|
|
55
|
+
let z = parseFloat(config.zcoordinate);
|
|
56
|
+
|
|
57
|
+
if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z) && !(x === 0 && y === 0 && z === 0)) {
|
|
58
|
+
options.xyz = [x, y, z];
|
|
127
59
|
}
|
|
128
60
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
client.on('error', (error) => {
|
|
140
|
-
connected = false;
|
|
141
|
-
if (typeof AggregateError !== 'undefined' && error instanceof AggregateError) {
|
|
142
|
-
error.errors.forEach((e, i) => {
|
|
143
|
-
node.log('Error ' + (i + 1) + ': ' + e.message);
|
|
144
|
-
node.error('Error: ' + e.message);
|
|
145
|
-
});
|
|
61
|
+
if (mode === 'download') {
|
|
62
|
+
client = NtripClient.createDownloader(options);
|
|
63
|
+
} else if (mode === 'upload') {
|
|
64
|
+
options.authmode = authmode;
|
|
65
|
+
try {
|
|
66
|
+
client = NtripClient.createUploader(options);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
|
|
69
|
+
node.error('Failed to create uploader: ' + (error.message || error));
|
|
70
|
+
}
|
|
146
71
|
} else {
|
|
147
|
-
|
|
148
|
-
node.
|
|
149
|
-
node.error('Error: ' + text);
|
|
72
|
+
node.status({ fill: 'red', shape: 'ring', text: 'Unsupported mode: ' + mode });
|
|
73
|
+
node.error('Mode not supported: ' + mode);
|
|
150
74
|
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
client.run();
|
|
155
|
-
} catch (error) {
|
|
156
|
-
node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
|
|
157
|
-
node.error('Failed to start client: ' + (error.message || error));
|
|
158
|
-
return;
|
|
159
75
|
}
|
|
160
76
|
|
|
161
|
-
|
|
77
|
+
if (client !== null) {
|
|
78
|
+
// Track handshake state so we only intercept control replies during the handshake,
|
|
79
|
+
// not on every subsequent data packet. Reset on socket close so reconnects work.
|
|
80
|
+
let connected = false;
|
|
162
81
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
82
|
+
function updateStatus() {
|
|
83
|
+
node.status({
|
|
84
|
+
fill: 'green',
|
|
85
|
+
shape: 'ring',
|
|
86
|
+
text: formatBps(node.currentBps) + ' Rx ' + node.messagesReceived + ' Tx ' + node.messagesSent,
|
|
87
|
+
});
|
|
167
88
|
}
|
|
168
89
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
90
|
+
// Sample inbound byte counter once per second to compute the bps shown in the
|
|
91
|
+
// status badge. Counter is reset each tick — currentBps reflects the most
|
|
92
|
+
// recent 1-second window.
|
|
93
|
+
const bpsSampler = setInterval(() => {
|
|
94
|
+
node.currentBps = node.bytesAccum * 8;
|
|
95
|
+
node.bytesAccum = 0;
|
|
96
|
+
updateStatus();
|
|
97
|
+
}, 1000);
|
|
98
|
+
|
|
99
|
+
client.on('data', (data) => {
|
|
100
|
+
node.bytesAccum += data.length;
|
|
101
|
+
let handled = false;
|
|
102
|
+
if (!connected) {
|
|
103
|
+
let prefix = data.toString('utf8', 0, Math.min(data.length, HandshakePrefixBytes));
|
|
104
|
+
if (prefix.startsWith(NtripServerOkReply)) {
|
|
105
|
+
connected = true;
|
|
106
|
+
node.status({ fill: 'green', shape: 'ring', text: 'NTRIP server connected.' });
|
|
107
|
+
// Forward any RTCM bytes that arrived in the same TCP segment as the handshake reply.
|
|
108
|
+
let headerEnd = data.indexOf('\r\n\r\n');
|
|
109
|
+
if (headerEnd !== -1 && headerEnd + 4 < data.length) {
|
|
110
|
+
node.messagesReceived++;
|
|
111
|
+
node.send({ payload: data.slice(headerEnd + 4) });
|
|
112
|
+
updateStatus();
|
|
113
|
+
}
|
|
114
|
+
handled = true;
|
|
115
|
+
} else if (prefix.startsWith(NtripServerNotOkReply)) {
|
|
116
|
+
node.status({ fill: 'red', shape: 'ring', text: 'NTRIP server rejected connection.' });
|
|
117
|
+
node.error('Server rejected connect: ' + prefix);
|
|
118
|
+
handled = true;
|
|
119
|
+
} else if (prefix.startsWith(NtripServerMissingMountpoint)) {
|
|
120
|
+
node.status({ fill: 'red', shape: 'ring', text: 'No mountpoint ' + mountpoint });
|
|
121
|
+
node.error('No mountpoint: ' + prefix);
|
|
122
|
+
handled = true;
|
|
123
|
+
} else {
|
|
124
|
+
// Unexpected: data arrived before any recognised handshake reply.
|
|
125
|
+
// Treat as connected so we don't drop further frames.
|
|
126
|
+
connected = true;
|
|
177
127
|
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!handled) {
|
|
131
|
+
node.messagesReceived++;
|
|
132
|
+
node.send({ payload: data });
|
|
178
133
|
updateStatus();
|
|
179
134
|
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
client.on('close', () => {
|
|
138
|
+
connected = false;
|
|
139
|
+
node.log('Closed NTRIP connection.');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
client.on('error', (error) => {
|
|
143
|
+
connected = false;
|
|
144
|
+
if (typeof AggregateError !== 'undefined' && error instanceof AggregateError) {
|
|
145
|
+
error.errors.forEach((e, i) => {
|
|
146
|
+
node.log('Error ' + (i + 1) + ': ' + e.message);
|
|
147
|
+
node.error('Error: ' + e.message);
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
let text = error && error.message ? error.message : String(error);
|
|
151
|
+
node.log(text);
|
|
152
|
+
node.error('Error: ' + text);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
let running = true;
|
|
157
|
+
try {
|
|
158
|
+
client.run();
|
|
180
159
|
} catch (error) {
|
|
181
|
-
|
|
182
|
-
node.status({ fill: 'red', shape: 'ring', text:
|
|
183
|
-
node.error('Failed to
|
|
160
|
+
running = false;
|
|
161
|
+
node.status({ fill: 'red', shape: 'ring', text: String(error.message || error) });
|
|
162
|
+
node.error('Failed to start client: ' + (error.message || error));
|
|
184
163
|
}
|
|
185
|
-
});
|
|
186
164
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
165
|
+
if (running) {
|
|
166
|
+
node.status({ fill: 'yellow', shape: 'ring', text: 'connecting...' });
|
|
167
|
+
|
|
168
|
+
this.on('input', function (msg) {
|
|
169
|
+
let payload = msg.payload;
|
|
170
|
+
if (payload !== undefined && payload !== null) {
|
|
171
|
+
try {
|
|
172
|
+
if (Array.isArray(payload)) {
|
|
173
|
+
client.setXYZ(payload);
|
|
174
|
+
} else {
|
|
175
|
+
client.write(payload);
|
|
176
|
+
node.messagesSent++;
|
|
177
|
+
if (node.passthrough) {
|
|
178
|
+
node.send(msg);
|
|
179
|
+
}
|
|
180
|
+
updateStatus();
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
let text = error && error.message ? error.message : String(error);
|
|
184
|
+
node.status({ fill: 'red', shape: 'ring', text: text });
|
|
185
|
+
node.error('Failed to write data: ' + text, msg);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
this.on('close', function (done) {
|
|
191
|
+
clearInterval(bpsSampler);
|
|
192
|
+
try {
|
|
193
|
+
client.removeAllListeners();
|
|
194
|
+
client.close();
|
|
195
|
+
} catch {
|
|
196
|
+
// ignore — node is shutting down anyway
|
|
197
|
+
}
|
|
198
|
+
node.status({});
|
|
199
|
+
done();
|
|
200
|
+
});
|
|
194
201
|
}
|
|
195
|
-
|
|
196
|
-
done();
|
|
197
|
-
});
|
|
202
|
+
}
|
|
198
203
|
}
|
|
199
204
|
|
|
200
205
|
return NtripClientNode;
|
|
@@ -23,59 +23,62 @@ module.exports = function (RED) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
this.on('input', function (msg) {
|
|
26
|
-
if (msg.payload
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
26
|
+
if (msg.payload !== undefined && msg.payload !== null) {
|
|
27
|
+
let chunk = Buffer.isBuffer(msg.payload) ? msg.payload : Buffer.from(msg.payload);
|
|
28
|
+
let buffer = node.pendingBuffer.length > 0 ? Buffer.concat([node.pendingBuffer, chunk]) : chunk;
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
let keepLooping = true;
|
|
31
|
+
while (keepLooping && buffer.length > 0) {
|
|
32
|
+
let message;
|
|
33
|
+
let length;
|
|
34
|
+
let decoded = true;
|
|
35
|
+
try {
|
|
36
|
+
[message, length] = RtcmTransport.decode(buffer);
|
|
37
|
+
} catch (ex) {
|
|
38
|
+
decoded = false;
|
|
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
|
+
keepLooping = false;
|
|
53
55
|
}
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (decoded) {
|
|
58
|
+
if (!Number.isInteger(length) || length <= 0 || length > buffer.length) {
|
|
59
|
+
// Defensive — guarantees forward progress even if the decoder ever
|
|
60
|
+
// returns a non-positive length.
|
|
61
|
+
keepLooping = false;
|
|
62
|
+
} else {
|
|
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
|
+
},
|
|
71
|
+
};
|
|
72
|
+
node.send([outMsg, null]);
|
|
73
|
+
node.rtcmMessagesReceived++;
|
|
74
|
+
buffer = buffer.slice(length);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
61
77
|
}
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
payload: {
|
|
66
|
-
rtcm: message.messageType,
|
|
67
|
-
messageType: messageType,
|
|
68
|
-
message: message,
|
|
69
|
-
input: buffer.slice(0, length),
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
node.send([outMsg, null]);
|
|
73
|
-
node.rtcmMessagesReceived++;
|
|
74
|
-
buffer = buffer.slice(length);
|
|
79
|
+
node.pendingBuffer = buffer;
|
|
80
|
+
updateStatus();
|
|
75
81
|
}
|
|
76
|
-
|
|
77
|
-
node.pendingBuffer = buffer;
|
|
78
|
-
updateStatus();
|
|
79
82
|
});
|
|
80
83
|
|
|
81
84
|
this.on('close', function (done) {
|
|
@@ -31,7 +31,9 @@ module.exports = function (RED) {
|
|
|
31
31
|
message = payload;
|
|
32
32
|
} else if (payload != null && payload.message instanceof RtcmMessage) {
|
|
33
33
|
message = payload.message;
|
|
34
|
-
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (message === undefined) {
|
|
35
37
|
node.send([
|
|
36
38
|
null,
|
|
37
39
|
{
|
|
@@ -43,40 +45,39 @@ module.exports = function (RED) {
|
|
|
43
45
|
]);
|
|
44
46
|
node.invalidMessagesReceived++;
|
|
45
47
|
updateStatus(false);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
let encoded = buffer.slice(0, length);
|
|
53
|
-
let messageType = message.constructor.name.replace('RtcmMessage', '');
|
|
48
|
+
} else {
|
|
49
|
+
try {
|
|
50
|
+
let buffer = Buffer.alloc(MaxFrameBytes);
|
|
51
|
+
let length = RtcmTransport.encode(message, buffer);
|
|
52
|
+
let encoded = buffer.slice(0, length);
|
|
53
|
+
let messageType = message.constructor.name.replace('RtcmMessage', '');
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
node.send([
|
|
56
|
+
{
|
|
57
|
+
payload: {
|
|
58
|
+
rtcmMessage: encoded,
|
|
59
|
+
rtcm: message.messageType,
|
|
60
|
+
messageType: messageType,
|
|
61
|
+
input: payload,
|
|
62
|
+
},
|
|
62
63
|
},
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
null,
|
|
65
|
+
]);
|
|
66
|
+
node.rtcmMessagesReceived++;
|
|
67
|
+
updateStatus(true);
|
|
68
|
+
} catch (ex) {
|
|
69
|
+
node.send([
|
|
70
|
+
null,
|
|
71
|
+
{
|
|
72
|
+
payload: {
|
|
73
|
+
error: ex,
|
|
74
|
+
input: payload,
|
|
75
|
+
},
|
|
75
76
|
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
]);
|
|
78
|
+
node.invalidMessagesReceived++;
|
|
79
|
+
updateStatus(false);
|
|
80
|
+
}
|
|
80
81
|
}
|
|
81
82
|
});
|
|
82
83
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-ntrip",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Node for ntrip and rtcm handling.",
|
|
5
5
|
"node-red": {
|
|
6
6
|
"version": ">=0.1.0",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"engines": {
|
|
12
|
-
"node": ">=
|
|
12
|
+
"node": ">=20.0.0"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"node-red",
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"gnss",
|
|
19
19
|
"mmea"
|
|
20
20
|
],
|
|
21
|
+
"files": [
|
|
22
|
+
"ntrip/",
|
|
23
|
+
"examples/",
|
|
24
|
+
"images/",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
21
27
|
"repository": {
|
|
22
28
|
"type": "git",
|
|
23
29
|
"url": "git+https://github.com/windkh/node-red-contrib-ntrip.git"
|
|
@@ -25,11 +31,15 @@
|
|
|
25
31
|
"author": "Karl-Heinz Wind",
|
|
26
32
|
"license": "MIT",
|
|
27
33
|
"scripts": {
|
|
28
|
-
"test": "
|
|
34
|
+
"test": "node --test --test-force-exit --test-timeout=30000 --test-concurrency=1",
|
|
29
35
|
"lint": "eslint .",
|
|
30
36
|
"lint:fix": "eslint . --fix",
|
|
31
37
|
"format": "prettier --write .",
|
|
32
|
-
"format:check": "prettier --check ."
|
|
38
|
+
"format:check": "prettier --check .",
|
|
39
|
+
"coverage": "c8 npm test",
|
|
40
|
+
"coverage:check": "c8 --check-coverage npm test",
|
|
41
|
+
"standards:audit": "nrstd audit",
|
|
42
|
+
"standards:sync": "nrstd sync"
|
|
33
43
|
},
|
|
34
44
|
"dependencies": {
|
|
35
45
|
"@gnss/nmea": "^0.1.2",
|
|
@@ -38,13 +48,13 @@
|
|
|
38
48
|
},
|
|
39
49
|
"devDependencies": {
|
|
40
50
|
"@eslint/js": "^10.0.1",
|
|
41
|
-
"
|
|
51
|
+
"c8": "^10.0.0",
|
|
42
52
|
"eslint": "^10.3.0",
|
|
43
53
|
"eslint-config-prettier": "^10.1.8",
|
|
44
54
|
"globals": "^17.6.0",
|
|
45
|
-
"mocha": "^11.7.5",
|
|
46
55
|
"node-red": "^5.0.1",
|
|
47
56
|
"node-red-node-test-helper": "^0.3.6",
|
|
57
|
+
"node-red-standards": "https://codeload.github.com/windkh/node-red-standards/tar.gz/refs/heads/main",
|
|
48
58
|
"prettier": "^3.8.3"
|
|
49
59
|
}
|
|
50
60
|
}
|
package/.github/dependabot.yml
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
updates:
|
|
3
|
-
- package-ecosystem: 'npm'
|
|
4
|
-
directory: '/'
|
|
5
|
-
schedule:
|
|
6
|
-
interval: 'weekly'
|
|
7
|
-
open-pull-requests-limit: 5
|
|
8
|
-
# The following packages are transitive devDependencies pulled in by
|
|
9
|
-
# `node-red` / `node-red-node-test-helper`. They have known advisories
|
|
10
|
-
# but no patched version is currently resolvable, so Dependabot's
|
|
11
|
-
# security-update PRs fail noisily on every retry. Since these packages
|
|
12
|
-
# are dev-only (not reachable from the published runtime tree of
|
|
13
|
-
# @gnss/nmea, @gnss/rtcm, ntrip-client), the advisories don't affect
|
|
14
|
-
# end users. Remove the ignore once upstream ships a fixed version.
|
|
15
|
-
ignore:
|
|
16
|
-
- dependency-name: 'picomatch'
|
|
17
|
-
- dependency-name: 'ip-address'
|
|
18
|
-
- dependency-name: 'serialize-javascript'
|
|
19
|
-
|
|
20
|
-
- package-ecosystem: 'github-actions'
|
|
21
|
-
directory: '/'
|
|
22
|
-
schedule:
|
|
23
|
-
interval: 'monthly'
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
-
|
|
4
|
-
name: Node.js CI
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: ['main']
|
|
9
|
-
pull_request:
|
|
10
|
-
branches: ['main']
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
build:
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
|
|
16
|
-
strategy:
|
|
17
|
-
matrix:
|
|
18
|
-
node-version: [18.x, 20.x, 22.x]
|
|
19
|
-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
20
|
-
|
|
21
|
-
steps:
|
|
22
|
-
- uses: actions/checkout@v7
|
|
23
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
24
|
-
uses: actions/setup-node@v6
|
|
25
|
-
with:
|
|
26
|
-
node-version: ${{ matrix.node-version }}
|
|
27
|
-
cache: 'npm'
|
|
28
|
-
- run: npm ci
|
|
29
|
-
- run: npm run build --if-present
|
|
30
|
-
- run: npm run lint
|
|
31
|
-
- run: npm run format:check
|
|
32
|
-
- run: npm test
|