node-red-contrib-ntrip 0.2.8 → 0.2.10
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/workflows/node.js.yml +1 -1
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +19 -0
- package/README.md +19 -2
- package/examples/watchdog.json +1 -0
- package/ntrip/99-ntrip.html +5 -2
- package/ntrip/lib/ntrip-client.js +37 -2
- package/ntrip/nodes/ntrip-client-node.js +24 -1
- package/package.json +2 -2
|
@@ -23,7 +23,7 @@ jobs:
|
|
|
23
23
|
matrix:
|
|
24
24
|
node-version: [18.x, 20.x, 22.x]
|
|
25
25
|
steps:
|
|
26
|
-
- uses: actions/checkout@
|
|
26
|
+
- uses: actions/checkout@v7
|
|
27
27
|
- name: Use Node.js ${{ matrix.node-version }}
|
|
28
28
|
uses: actions/setup-node@v6
|
|
29
29
|
with:
|
|
@@ -38,7 +38,7 @@ jobs:
|
|
|
38
38
|
needs: test
|
|
39
39
|
runs-on: ubuntu-latest
|
|
40
40
|
steps:
|
|
41
|
-
- uses: actions/checkout@
|
|
41
|
+
- uses: actions/checkout@v7
|
|
42
42
|
- uses: actions/setup-node@v6
|
|
43
43
|
with:
|
|
44
44
|
node-version: 20
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
permissions:
|
|
55
55
|
contents: write
|
|
56
56
|
steps:
|
|
57
|
-
- uses: actions/checkout@
|
|
57
|
+
- uses: actions/checkout@v7
|
|
58
58
|
with:
|
|
59
59
|
fetch-depth: 0
|
|
60
60
|
- name: Create GitHub Release
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
All notable changes to this project will be documented in this file.
|
|
3
3
|
|
|
4
|
+
## [0.2.10]
|
|
5
|
+
### NtripClient
|
|
6
|
+
- Reconnect attempts now use an exponential-ish backoff schedule (`1 s`, `2 s`, `5 s`, `10 s`, then capped at `10 s`) instead of the upstream library's fixed `2 s` interval. The schedule resets to `1 s` the moment the caster completes the `ICY 200 OK` handshake, so a brief outage restarts from the short end and a caster that stays down is no longer hammered.
|
|
7
|
+
|
|
8
|
+
### Documentation
|
|
9
|
+
- Added `examples/watchdog.json` — an example flow that alerts (via a built-in `trigger` node) when the NTRIP correction stream stops delivering data for more than 60 seconds. Closes [#4](https://github.com/windkh/node-red-contrib-ntrip/issues/4).
|
|
10
|
+
|
|
11
|
+
## [0.2.9]
|
|
12
|
+
### NtripClient
|
|
13
|
+
- Status badge now shows the inbound data rate alongside the Rx/Tx message counters (e.g. `2.4 kbps Rx 123 Tx 4`). The rate is sampled once per second and formatted as `bps`, `kbps`, or `Mbps` depending on magnitude.
|
|
14
|
+
|
|
15
|
+
### Internal — dependency bumps (dev tree, no runtime changes)
|
|
16
|
+
- `node-red` 4.1.11 → 5.0.1 (via the sigstore/node-red bundle).
|
|
17
|
+
- `eslint` 10.4.1 → 10.6.0.
|
|
18
|
+
- `prettier` 3.8.3 → 3.9.3.
|
|
19
|
+
- `mocha` 11.7.5 → 11.7.6.
|
|
20
|
+
- `globals` 17.6.0 → 17.7.0.
|
|
21
|
+
- `actions/checkout` v6 → v7 in the CI + release workflows.
|
|
22
|
+
|
|
4
23
|
## [0.2.8]
|
|
5
24
|
### Added RTCM Encoder node
|
|
6
25
|
- New `RtcmEncoder` node — inverse of `RtcmDecoder`. Accepts an `RtcmMessage` instance (or `msg.payload.message` from the decoder) and emits the encoded RTCM 3 frame as `msg.payload.rtcmMessage` (Buffer).
|
package/README.md
CHANGED
|
@@ -292,11 +292,18 @@ A few things that are easy to get wrong:
|
|
|
292
292
|
interleaved with bytes received from the caster. Downstream nodes will see
|
|
293
293
|
both streams on the same wire — distinguish them with a tag in a function
|
|
294
294
|
node if you need to.
|
|
295
|
-
- **NTRIP Client: status badge.**
|
|
296
|
-
|
|
295
|
+
- **NTRIP Client: status badge.** `<rate> Rx N Tx M` shows the inbound data
|
|
296
|
+
rate (sampled over a 1-second window; formatted as `bps`, `kbps`, or `Mbps`)
|
|
297
|
+
followed by the inbound and outbound message counts. Handshake replies do
|
|
298
|
+
not bump the inbound count.
|
|
297
299
|
- **NTRIP Client: CR/LF in credentials.** `mountpoint`, `username`, and
|
|
298
300
|
`password` are interpolated into the handshake string. CR/LF in these fields
|
|
299
301
|
is rejected at startup to prevent header injection.
|
|
302
|
+
- **NTRIP Client: reconnect backoff.** When the socket to the caster drops the
|
|
303
|
+
client waits `1 s`, then `2 s`, `5 s`, `10 s` before each successive retry
|
|
304
|
+
(capped at `10 s`). The schedule resets to `1 s` as soon as the caster
|
|
305
|
+
completes the `ICY 200 OK` handshake again — so a brief outage restarts at
|
|
306
|
+
the low end, and a caster that stays down doesn't get hammered.
|
|
300
307
|
- **NMEA Encoder input shape.** The encoder expects `msg.payload` to be an
|
|
301
308
|
object with `messageType` and `nmeaMessage` properties — not a NMEA string.
|
|
302
309
|
To re-encode the output of the NMEA Decoder you can pipe it straight in;
|
|
@@ -343,6 +350,16 @@ a local demo version of SNIP by creating a raw TCP input stream.
|
|
|
343
350
|
(SNIP can be found here https://www.use-snip.com)
|
|
344
351
|
See also example flow [**Upload flow**](examples/upload.json)
|
|
345
352
|
|
|
353
|
+
## Detect a stalled correction stream
|
|
354
|
+
NTRIP casters occasionally stop emitting data without closing the TCP socket
|
|
355
|
+
— the NtripClient node stays "connected" but no new frames arrive, and any
|
|
356
|
+
downstream rover eventually drops out of RTK-fixed mode without an obvious
|
|
357
|
+
cause. This example wires the NtripClient output into a built-in `trigger`
|
|
358
|
+
node configured to fire an alert message after 60 seconds of silence
|
|
359
|
+
(configurable via the trigger's Duration field). Every incoming frame resets
|
|
360
|
+
the timer, so the alert only fires when the stream is genuinely stalled.
|
|
361
|
+
See also example flow [**Watchdog flow**](examples/watchdog.json) (closes [#4](https://github.com/windkh/node-red-contrib-ntrip/issues/4))
|
|
362
|
+
|
|
346
363
|
# License
|
|
347
364
|
|
|
348
365
|
Author: Karl-Heinz Wind
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[{"id":"47bd8463328d83d3","type":"comment","z":"a604108643639054","name":"NTRIP stream stall watchdog — alert after 60s of silence","info":"Alerts when the NTRIP correction stream stops delivering data.\n\nHow it works:\n- Every message from the NTRIP Client resets the trigger node's timer.\n- If no message arrives within 60 seconds, the trigger fires and the STALL\n debug pane shows an alert.\n- When data resumes, the next message resets the timer — no explicit\n \"recovered\" notification is emitted (extend the flow with a function\n node if you need that).\n\nAdjust the timeout in the trigger node's Duration field.\n","x":260,"y":80,"wires":[]},{"id":"9f622b9ab6a72772","type":"NtripClient","z":"a604108643639054","description":"RTK2Go","passthrough":false,"port":"2101","host":"rtk2go.com","mountpoint":"Foo","interval":1000,"xcoordinate":"","ycoordinate":"","zcoordinate":"","x":140,"y":180,"wires":[["5107785e11b2425a","eb42f2cd60fcdc8f"]]},{"id":"5107785e11b2425a","type":"debug","z":"a604108643639054","name":"data (each frame)","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":430,"y":140,"wires":[]},{"id":"eb42f2cd60fcdc8f","type":"trigger","z":"a604108643639054","name":"60s stall watchdog","op1":"","op2":"NTRIP stream stalled — no data for 60 s","op1type":"nul","op2type":"str","duration":"60","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":440,"y":220,"wires":[["25f028d01cad6b57"]]},{"id":"25f028d01cad6b57","type":"debug","z":"a604108643639054","name":"STALL","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":660,"y":220,"wires":[]}]
|
package/ntrip/99-ntrip.html
CHANGED
|
@@ -209,8 +209,11 @@
|
|
|
209
209
|
<h3>Status</h3>
|
|
210
210
|
<p>The node shows <code>connecting…</code> until the caster acknowledges the
|
|
211
211
|
handshake with <code>ICY 200 OK</code>. After that it shows the running
|
|
212
|
-
<code>Rx</code> / <code>Tx</code> counters
|
|
213
|
-
|
|
212
|
+
<code>Rx</code> / <code>Tx</code> message counters plus the inbound data
|
|
213
|
+
rate in <code>bps</code> / <code>kbps</code> / <code>Mbps</code>, sampled
|
|
214
|
+
once per second over a 1-second window
|
|
215
|
+
(e.g. <code>2.4 kbps Rx 123 Tx 4</code>). A red badge indicates a
|
|
216
|
+
rejected connection, a missing mountpoint, or a write failure.</p>
|
|
214
217
|
|
|
215
218
|
<h3>Details</h3>
|
|
216
219
|
<p>The first <code>ICY 200 OK</code>, <code>ICY 406</code>, or
|
|
@@ -4,8 +4,43 @@
|
|
|
4
4
|
const { NtripClient } = require('ntrip-client');
|
|
5
5
|
const net = require('net');
|
|
6
6
|
|
|
7
|
+
// Backoff schedule for reconnect attempts, in milliseconds. The upstream
|
|
8
|
+
// client's default is a fixed 2000ms interval which flooded casters (and the
|
|
9
|
+
// Node-RED error log) when a server was down for a while. See issue on repeated
|
|
10
|
+
// error output during outages. The last entry acts as a cap for further retries.
|
|
11
|
+
const RECONNECT_BACKOFF_MS = [1000, 2000, 5000, 10000];
|
|
12
|
+
|
|
13
|
+
// Extends the upstream client with an exponential backoff on reconnect.
|
|
14
|
+
// The schedule advances on each failed reconnect and resets to the first
|
|
15
|
+
// entry as soon as the caster completes the handshake (`ICY 200 OK`).
|
|
16
|
+
class NtripClientWithBackoff extends NtripClient {
|
|
17
|
+
constructor(options) {
|
|
18
|
+
super(options);
|
|
19
|
+
this._backoffIndex = 0;
|
|
20
|
+
this.reconnectInterval = RECONNECT_BACKOFF_MS[0];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_onData(data) {
|
|
24
|
+
const wasReady = this.isReady;
|
|
25
|
+
super._onData(data);
|
|
26
|
+
// Reset the backoff schedule the moment the handshake completes —
|
|
27
|
+
// subsequent connection failures start over at the first entry.
|
|
28
|
+
if (!wasReady && this.isReady) {
|
|
29
|
+
this._backoffIndex = 0;
|
|
30
|
+
this.reconnectInterval = RECONNECT_BACKOFF_MS[0];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async _reconnect() {
|
|
35
|
+
const step = Math.min(this._backoffIndex, RECONNECT_BACKOFF_MS.length - 1);
|
|
36
|
+
this.reconnectInterval = RECONNECT_BACKOFF_MS[step];
|
|
37
|
+
this._backoffIndex++;
|
|
38
|
+
return super._reconnect();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
7
42
|
// Orginal class is extended to be able to send data to the caster.
|
|
8
|
-
class NtripClientUploader extends
|
|
43
|
+
class NtripClientUploader extends NtripClientWithBackoff {
|
|
9
44
|
constructor(options) {
|
|
10
45
|
super(options);
|
|
11
46
|
this.authmode = options.authmode || 'legacy';
|
|
@@ -98,7 +133,7 @@ class NtripClientUploader extends NtripClient {
|
|
|
98
133
|
}
|
|
99
134
|
|
|
100
135
|
function createDownloader(options) {
|
|
101
|
-
return new
|
|
136
|
+
return new NtripClientWithBackoff(options);
|
|
102
137
|
}
|
|
103
138
|
|
|
104
139
|
function createUploader(options) {
|
|
@@ -7,11 +7,23 @@ module.exports = function (RED) {
|
|
|
7
7
|
const NtripServerMissingMountpoint = 'SOURCETABLE 200 OK';
|
|
8
8
|
const HandshakePrefixBytes = 64;
|
|
9
9
|
|
|
10
|
+
function formatBps(bps) {
|
|
11
|
+
if (bps < 1000) {
|
|
12
|
+
return Math.round(bps) + ' bps';
|
|
13
|
+
}
|
|
14
|
+
if (bps < 1000000) {
|
|
15
|
+
return (bps / 1000).toFixed(1) + ' kbps';
|
|
16
|
+
}
|
|
17
|
+
return (bps / 1000000).toFixed(2) + ' Mbps';
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
function NtripClientNode(config) {
|
|
11
21
|
RED.nodes.createNode(this, config);
|
|
12
22
|
let node = this;
|
|
13
23
|
node.messagesReceived = 0;
|
|
14
24
|
node.messagesSent = 0;
|
|
25
|
+
node.bytesAccum = 0;
|
|
26
|
+
node.currentBps = 0;
|
|
15
27
|
node.passthrough = config.passthrough || false;
|
|
16
28
|
|
|
17
29
|
let host = typeof config.host === 'string' ? config.host.trim() : '';
|
|
@@ -70,11 +82,21 @@ module.exports = function (RED) {
|
|
|
70
82
|
node.status({
|
|
71
83
|
fill: 'green',
|
|
72
84
|
shape: 'ring',
|
|
73
|
-
text: 'Rx ' + node.messagesReceived + ' Tx ' + node.messagesSent,
|
|
85
|
+
text: formatBps(node.currentBps) + ' Rx ' + node.messagesReceived + ' Tx ' + node.messagesSent,
|
|
74
86
|
});
|
|
75
87
|
}
|
|
76
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
|
+
|
|
77
98
|
client.on('data', (data) => {
|
|
99
|
+
node.bytesAccum += data.length;
|
|
78
100
|
if (!connected) {
|
|
79
101
|
let prefix = data.toString('utf8', 0, Math.min(data.length, HandshakePrefixBytes));
|
|
80
102
|
if (prefix.startsWith(NtripServerOkReply)) {
|
|
@@ -163,6 +185,7 @@ module.exports = function (RED) {
|
|
|
163
185
|
});
|
|
164
186
|
|
|
165
187
|
this.on('close', function (done) {
|
|
188
|
+
clearInterval(bpsSampler);
|
|
166
189
|
try {
|
|
167
190
|
client.removeAllListeners();
|
|
168
191
|
client.close();
|
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.10",
|
|
4
4
|
"description": "Node for ntrip and rtcm handling.",
|
|
5
5
|
"node-red": {
|
|
6
6
|
"version": ">=0.1.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"eslint-config-prettier": "^10.1.8",
|
|
44
44
|
"globals": "^17.6.0",
|
|
45
45
|
"mocha": "^11.7.5",
|
|
46
|
-
"node-red": "^
|
|
46
|
+
"node-red": "^5.0.1",
|
|
47
47
|
"node-red-node-test-helper": "^0.3.6",
|
|
48
48
|
"prettier": "^3.8.3"
|
|
49
49
|
}
|