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.
@@ -19,7 +19,7 @@ jobs:
19
19
  # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
20
20
 
21
21
  steps:
22
- - uses: actions/checkout@v6
22
+ - uses: actions/checkout@v7
23
23
  - name: Use Node.js ${{ matrix.node-version }}
24
24
  uses: actions/setup-node@v6
25
25
  with:
@@ -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@v6
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@v6
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@v6
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.** `Rx N Tx M` counts inbound and outbound
296
- messages separately. Handshake replies do not bump the inbound count.
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":[]}]
@@ -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. A red badge indicates a rejected
213
- connection, a missing mountpoint, or a write failure.</p>
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 NtripClient {
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 NtripClient(options);
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.8",
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": "^4.1.10",
46
+ "node-red": "^5.0.1",
47
47
  "node-red-node-test-helper": "^0.3.6",
48
48
  "prettier": "^3.8.3"
49
49
  }