node-red-contrib-ntrip 0.2.9 → 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.
Files changed (39) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +17 -0
  3. package/examples/watchdog.json +1 -0
  4. package/ntrip/lib/ntrip-client.js +107 -74
  5. package/ntrip/nodes/nmea-decoder-node.js +36 -38
  6. package/ntrip/nodes/nmea-encoder-node.js +97 -97
  7. package/ntrip/nodes/ntrip-client-node.js +153 -148
  8. package/ntrip/nodes/rtcm-decoder-node.js +50 -47
  9. package/ntrip/nodes/rtcm-encoder-node.js +33 -32
  10. package/package.json +16 -6
  11. package/.github/dependabot.yml +0 -23
  12. package/.github/workflows/node.js.yml +0 -32
  13. package/.github/workflows/release.yml +0 -65
  14. package/.prettierignore +0 -20
  15. package/.prettierrc.json +0 -9
  16. package/CLAUDE.md +0 -73
  17. package/doc/architecture/README.md +0 -19
  18. package/doc/architecture/adr/0001-single-registration-file.md +0 -64
  19. package/doc/architecture/adr/0002-ntrip-uploader-extension.md +0 -71
  20. package/doc/architecture/adr/0003-two-output-decoder-design.md +0 -73
  21. package/doc/architecture/adr/0004-stateful-handshake-interception.md +0 -74
  22. package/doc/architecture/adr/0005-rtcm-partial-frame-buffer.md +0 -87
  23. package/doc/architecture/adr/0006-nmea-multi-sentence-split.md +0 -66
  24. package/doc/architecture/adr/0007-mocha-test-helper-stack.md +0 -67
  25. package/doc/architecture/adr/0008-coordinate-gating-sentinel.md +0 -76
  26. package/doc/architecture/adr/README.md +0 -18
  27. package/doc/architecture/architecture-decisions.md +0 -60
  28. package/doc/architecture/behavioural-design.md +0 -226
  29. package/doc/architecture/errors-and-weaknesses.md +0 -71
  30. package/doc/architecture/future-improvements.md +0 -130
  31. package/doc/architecture/overview.md +0 -77
  32. package/doc/architecture/refactoring-recommendations.md +0 -114
  33. package/doc/architecture/statistics.md +0 -118
  34. package/doc/architecture/structural-design.md +0 -141
  35. package/eslint.config.js +0 -36
  36. package/test/nmea-decoder.spec.js +0 -146
  37. package/test/nmea-encoder.spec.js +0 -104
  38. package/test/rtcm-decoder.spec.js +0 -116
  39. package/test/rtcm-encoder.spec.js +0 -144
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.11]
5
+ ### Documentation
6
+ - `examples/watchdog.json` now emits a `RECOVERED` message on the first data frame that arrives after a stall, in addition to the existing `STALLED` notification. Per-frame messages are still suppressed — you only see the two transitions.
7
+
8
+ ### Internal — adopted `node-red-standards` (no runtime behaviour change)
9
+ - Migrated the test suite from `mocha` + `chai` to `node --test` + `node:assert` (still uses `node-red-node-test-helper`; specs renamed `*.spec.js` → `*.test.js`).
10
+ - Added portable `AGENTS.md` as the tool-neutral rule source of truth; `CLAUDE.md` slimmed to a thin adapter.
11
+ - Added `.github/workflows/standards-check.yml` — CI-side audit against `node-red-standards`.
12
+ - Consolidated the previous tag-driven `release.yml` into `npm-publish.yml` (same behaviour, standard-conforming filename).
13
+ - Bumped `engines.node` from `>=18.0.0` to `>=20.0.0`; CI/publish Node matrix trimmed to `20.x, 22.x` (Node 18 is EOL).
14
+ - Refactored all node files to single-exit style — every function has exactly one `return` at its final position. Zero behavioural change; 20/20 tests still pass.
15
+
16
+ ## [0.2.10]
17
+ ### NtripClient
18
+ - 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.
19
+
20
+ ### Documentation
21
+ - 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).
22
+
4
23
  ## [0.2.9]
5
24
  ### NtripClient
6
25
  - 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.
package/README.md CHANGED
@@ -299,6 +299,11 @@ A few things that are easy to get wrong:
299
299
  - **NTRIP Client: CR/LF in credentials.** `mountpoint`, `username`, and
300
300
  `password` are interpolated into the handshake string. CR/LF in these fields
301
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.
302
307
  - **NMEA Encoder input shape.** The encoder expects `msg.payload` to be an
303
308
  object with `messageType` and `nmeaMessage` properties — not a NMEA string.
304
309
  To re-encode the output of the NMEA Decoder you can pipe it straight in;
@@ -345,6 +350,18 @@ a local demo version of SNIP by creating a raw TCP input stream.
345
350
  (SNIP can be found here https://www.use-snip.com)
346
351
  See also example flow [**Upload flow**](examples/upload.json)
347
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 after 60 seconds of silence (configurable via the
359
+ trigger's Duration field). A small `function` node then acts as a state
360
+ tracker: it emits a **`STALLED`** notification when the trigger fires and a
361
+ **`RECOVERED`** notification on the first data frame that arrives after a
362
+ stall. Per-frame messages are suppressed — you only see the two transitions.
363
+ See also example flow [**Watchdog flow**](examples/watchdog.json) (closes [#4](https://github.com/windkh/node-red-contrib-ntrip/issues/4))
364
+
348
365
  # License
349
366
 
350
367
  Author: Karl-Heinz Wind
@@ -0,0 +1 @@
1
+ [{"id":"47bd8463328d83d3","type":"comment","z":"a604108643639054","name":"NTRIP stream stall + recovery watchdog","info":"Emits a message when the NTRIP correction stream stops flowing\nAND when it starts flowing again — nothing else.\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; a change\n node tags it with topic=stall and the state tracker function emits a\n STALLED notification (once).\n- When data resumes, the state tracker sees the first data frame after\n the stall and emits a RECOVERED notification (once).\n- While the stream is flowing normally, the state tracker suppresses\n per-frame messages — you only see transitions.\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","1a2b3c4d5e6f7089"]]},{"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":"stall","op1type":"nul","op2type":"str","duration":"60","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":440,"y":220,"wires":[["ab12cd34ef567890"]]},{"id":"ab12cd34ef567890","type":"change","z":"a604108643639054","name":"topic=stall","rules":[{"t":"set","p":"topic","pt":"msg","to":"stall","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":220,"wires":[["1a2b3c4d5e6f7089"]]},{"id":"1a2b3c4d5e6f7089","type":"function","z":"a604108643639054","name":"state tracker","func":"if (msg.topic === 'stall') {\n context.set('stalled', true);\n msg.payload = 'STALLED — no data for 60 s';\n return msg;\n}\n\n// Data frame from NtripClient — check whether we just recovered.\nif (context.get('stalled')) {\n context.set('stalled', false);\n msg.payload = 'RECOVERED — NTRIP stream is flowing again';\n return msg;\n}\n\n// Stream is fine; no transition to report.\nreturn null;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":260,"wires":[["25f028d01cad6b57"]]},{"id":"25f028d01cad6b57","type":"debug","z":"a604108643639054","name":"STATUS","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1020,"y":260,"wires":[]}]
@@ -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';
@@ -21,84 +56,82 @@ class NtripClientUploader extends NtripClient {
21
56
  }
22
57
 
23
58
  _connect() {
24
- if (this.isClose) {
25
- return;
59
+ if (!this.isClose) {
60
+ // init connection of client
61
+ this.client = net.createConnection({
62
+ host: this.host,
63
+ port: this.port,
64
+ });
65
+
66
+ // on timeout event
67
+ this.client.on('timeout', () => {
68
+ this._onError('socket timeout');
69
+ });
70
+
71
+ // on connect event
72
+ this.client.on('connect', () => {
73
+ const mountpoint = this.mountpoint;
74
+ const userAgent = this.userAgent;
75
+ const username = this.username;
76
+ const password = this.password;
77
+ const host = this.host;
78
+ const port = this.port;
79
+ const authmode = this.authmode;
80
+ const authorization = Buffer.from(username + ':' + password, 'utf8').toString('base64');
81
+
82
+ let data;
83
+ switch (authmode) {
84
+ case 'legacy': // Legacy --> rtk2Go accepts this.
85
+ if (username === '' && password === '') {
86
+ data = `SOURCE ${mountpoint}\r\n\r\n`;
87
+ } else {
88
+ data = `SOURCE ${password} /${mountpoint}\r\nSource-Agent: NTRIP ${username}\r\n\r\n`;
89
+ }
90
+ break;
91
+ case 'hybrid': // hybrid (legacy + http auth) --> SNIP accepts this.
92
+ data = `SOURCE ${mountpoint}\r\nAuthorization: Basic ${authorization}\r\n\r\n`;
93
+ break;
94
+ case 'ntripv1': // NTRIP V1 POST
95
+ data = `POST /${mountpoint} HTTP/1.0\r\nUser-Agent: NTRIP ${userAgent}\r\nAuthorization: Basic ${authorization}\r\nContent-Type: gnss/data\r\n\r\n`;
96
+ break;
97
+ case 'ntripv2': // NTRIP V2 POST
98
+ data = `POST /${mountpoint} HTTP/1.1\r\nHost: ${host}:${port}\r\nUser-Agent: NTRIP ${userAgent}\r\nAuthorization: Basic ${authorization}\r\nNtrip-Version: Ntrip/2.0\r\nContent-Type: gnss/data\r\nConnection: keep-alive\r\n\r\n`;
99
+ break;
100
+ default:
101
+ throw new Error('Auth mode is not supported ' + authmode);
102
+ }
103
+
104
+ this.client.write(data);
105
+ });
106
+
107
+ // on data event
108
+ this.client.on('data', (data) => {
109
+ this._onData(data);
110
+ });
111
+
112
+ // on end event
113
+ this.client.on('end', () => {
114
+ this._onError('socket ended');
115
+ });
116
+
117
+ // on close event
118
+ this.client.on('close', () => {
119
+ this._onError('socket closed');
120
+ });
121
+
122
+ // on error event
123
+ this.client.on('error', (err) => {
124
+ // TODO: check for code = ECONNREFUSED
125
+ this._onError(err);
126
+ });
127
+
128
+ this.client.setTimeout(this.timeout);
26
129
  }
27
-
28
- // init connection of client
29
- this.client = net.createConnection({
30
- host: this.host,
31
- port: this.port,
32
- });
33
-
34
- // on timeout event
35
- this.client.on('timeout', () => {
36
- this._onError('socket timeout');
37
- });
38
-
39
- // on connect event
40
- this.client.on('connect', () => {
41
- const mountpoint = this.mountpoint;
42
- const userAgent = this.userAgent;
43
- const username = this.username;
44
- const password = this.password;
45
- const host = this.host;
46
- const port = this.port;
47
- const authmode = this.authmode;
48
- const authorization = Buffer.from(username + ':' + password, 'utf8').toString('base64');
49
-
50
- let data;
51
- switch (authmode) {
52
- case 'legacy': // Legacy --> rtk2Go accepts this.
53
- if (username === '' && password === '') {
54
- data = `SOURCE ${mountpoint}\r\n\r\n`;
55
- } else {
56
- data = `SOURCE ${password} /${mountpoint}\r\nSource-Agent: NTRIP ${username}\r\n\r\n`;
57
- }
58
- break;
59
- case 'hybrid': // hybrid (legacy + http auth) --> SNIP accepts this.
60
- data = `SOURCE ${mountpoint}\r\nAuthorization: Basic ${authorization}\r\n\r\n`;
61
- break;
62
- case 'ntripv1': // NTRIP V1 POST
63
- data = `POST /${mountpoint} HTTP/1.0\r\nUser-Agent: NTRIP ${userAgent}\r\nAuthorization: Basic ${authorization}\r\nContent-Type: gnss/data\r\n\r\n`;
64
- break;
65
- case 'ntripv2': // NTRIP V2 POST
66
- data = `POST /${mountpoint} HTTP/1.1\r\nHost: ${host}:${port}\r\nUser-Agent: NTRIP ${userAgent}\r\nAuthorization: Basic ${authorization}\r\nNtrip-Version: Ntrip/2.0\r\nContent-Type: gnss/data\r\nConnection: keep-alive\r\n\r\n`;
67
- break;
68
- default:
69
- throw new Error('Auth mode is not supported ' + authmode);
70
- }
71
-
72
- this.client.write(data);
73
- });
74
-
75
- // on data event
76
- this.client.on('data', (data) => {
77
- this._onData(data);
78
- });
79
-
80
- // on end event
81
- this.client.on('end', () => {
82
- this._onError('socket ended');
83
- });
84
-
85
- // on close event
86
- this.client.on('close', () => {
87
- this._onError('socket closed');
88
- });
89
-
90
- // on error event
91
- this.client.on('error', (err) => {
92
- // TODO: check for code = ECONNREFUSED
93
- this._onError(err);
94
- });
95
-
96
- this.client.setTimeout(this.timeout);
97
130
  }
98
131
  }
99
132
 
100
133
  function createDownloader(options) {
101
- return new NtripClient(options);
134
+ return new NtripClientWithBackoff(options);
102
135
  }
103
136
 
104
137
  function createUploader(options) {
@@ -49,49 +49,47 @@ module.exports = function (RED) {
49
49
  }
50
50
 
51
51
  this.on('input', function (msg) {
52
- if (msg.payload === undefined || msg.payload === null) {
53
- return;
54
- }
52
+ if (msg.payload !== undefined && msg.payload !== null) {
53
+ // rawInput is the value the user provided (Buffer, string, ...).
54
+ // Keep it untouched so the error output can return it as-is.
55
+ let rawInput = typeof msg.payload === 'object' && msg.payload.nmeaMessage !== undefined ? msg.payload.nmeaMessage : msg.payload;
55
56
 
56
- // rawInput is the value the user provided (Buffer, string, ...).
57
- // Keep it untouched so the error output can return it as-is.
58
- let rawInput = typeof msg.payload === 'object' && msg.payload.nmeaMessage !== undefined ? msg.payload.nmeaMessage : msg.payload;
57
+ let rawString;
58
+ if (Buffer.isBuffer(rawInput)) {
59
+ rawString = rawInput.toString('utf8');
60
+ } else if (typeof rawInput === 'string') {
61
+ rawString = rawInput;
62
+ }
59
63
 
60
- let rawString;
61
- if (Buffer.isBuffer(rawInput)) {
62
- rawString = rawInput.toString('utf8');
63
- } else if (typeof rawInput === 'string') {
64
- rawString = rawInput;
65
- } else {
66
- node.send([
67
- null,
68
- {
69
- payload: {
70
- error: 'Payload is neither string nor Buffer',
71
- input: rawInput,
72
- inputString: String(rawInput),
64
+ if (rawString === undefined) {
65
+ node.send([
66
+ null,
67
+ {
68
+ payload: {
69
+ error: 'Payload is neither string nor Buffer',
70
+ input: rawInput,
71
+ inputString: String(rawInput),
72
+ },
73
73
  },
74
- },
75
- ]);
76
- node.invalidMessagesReceived++;
77
- updateStatus();
78
- return;
79
- }
74
+ ]);
75
+ node.invalidMessagesReceived++;
76
+ updateStatus();
77
+ } else {
78
+ // A single chunk may carry several sentences delimited by \r\n.
79
+ let sentences = rawString.split(/\r?\n/);
80
+ let any = false;
81
+ for (let i = 0; i < sentences.length; i++) {
82
+ let s = sentences[i].trim();
83
+ if (s.length > 0) {
84
+ decodeOne(s, rawInput, rawString);
85
+ any = true;
86
+ }
87
+ }
80
88
 
81
- // A single chunk may carry several sentences delimited by \r\n.
82
- let sentences = rawString.split(/\r?\n/);
83
- let any = false;
84
- for (let i = 0; i < sentences.length; i++) {
85
- let s = sentences[i].trim();
86
- if (s.length === 0) {
87
- continue;
89
+ if (any) {
90
+ updateStatus();
91
+ }
88
92
  }
89
- decodeOne(s, rawInput, rawString);
90
- any = true;
91
- }
92
-
93
- if (any) {
94
- updateStatus();
95
93
  }
96
94
  });
97
95
 
@@ -39,14 +39,15 @@ module.exports = function (RED) {
39
39
  }
40
40
 
41
41
  function safeToString(value) {
42
- if (value === undefined || value === null) {
43
- return '';
44
- }
45
- try {
46
- return String(value);
47
- } catch {
48
- return '';
42
+ let result = '';
43
+ if (value !== undefined && value !== null) {
44
+ try {
45
+ result = String(value);
46
+ } catch {
47
+ result = '';
48
+ }
49
49
  }
50
+ return result;
50
51
  }
51
52
 
52
53
  this.on('input', function (msg) {
@@ -63,102 +64,101 @@ module.exports = function (RED) {
63
64
  node.send([null, errMsg]);
64
65
  node.invalidMessagesReceived++;
65
66
  updateStatus(false);
66
- return;
67
- }
68
-
69
- let input = payload.nmeaMessage;
67
+ } else {
68
+ let input = payload.nmeaMessage;
70
69
 
71
- try {
72
- let messageType = String(payload.messageType).toUpperCase();
70
+ try {
71
+ let messageType = String(payload.messageType).toUpperCase();
73
72
 
74
- let nmeaMessage;
75
- if (input instanceof NmeaMessage) {
76
- nmeaMessage = input;
77
- } else {
78
- switch (messageType) {
79
- case 'OBJECT':
80
- nmeaMessage = NmeaMessageUnknown.construct(input);
81
- break;
82
- case 'DTM':
83
- nmeaMessage = NmeaMessageDtm.construct(input);
84
- break;
85
- case 'GBS':
86
- nmeaMessage = NmeaMessageGbs.construct(input);
87
- break;
88
- case 'GGA':
89
- nmeaMessage = NmeaMessageGga.construct(input);
90
- break;
91
- case 'GLL':
92
- nmeaMessage = NmeaMessageGll.construct(input);
93
- break;
94
- case 'GNS':
95
- nmeaMessage = NmeaMessageGns.construct(input);
96
- break;
97
- case 'GRS':
98
- nmeaMessage = NmeaMessageGrs.construct(input);
99
- break;
100
- case 'GSA':
101
- nmeaMessage = NmeaMessageGsa.construct(input);
102
- break;
103
- case 'GST':
104
- nmeaMessage = NmeaMessageGst.construct(input);
105
- break;
106
- case 'GSV':
107
- nmeaMessage = NmeaMessageGsv.construct(input);
108
- break;
109
- case 'RMC':
110
- nmeaMessage = NmeaMessageRmc.construct(input);
111
- break;
112
- case 'THS':
113
- nmeaMessage = NmeaMessageThs.construct(input);
114
- break;
115
- case 'TXT':
116
- nmeaMessage = NmeaMessageTxt.construct(input);
117
- break;
118
- case 'VHW':
119
- nmeaMessage = NmeaMessageVhw.construct(input);
120
- break;
121
- case 'VLW':
122
- nmeaMessage = NmeaMessageVlw.construct(input);
123
- break;
124
- case 'VPW':
125
- nmeaMessage = NmeaMessageVpw.construct(input);
126
- break;
127
- case 'VTG':
128
- nmeaMessage = NmeaMessageVtg.construct(input);
129
- break;
130
- case 'ZDA':
131
- nmeaMessage = NmeaMessageZda.construct(input);
132
- break;
133
- default:
134
- throw new Error('Unsupported NMEA message type: ' + messageType);
73
+ let nmeaMessage;
74
+ if (input instanceof NmeaMessage) {
75
+ nmeaMessage = input;
76
+ } else {
77
+ switch (messageType) {
78
+ case 'OBJECT':
79
+ nmeaMessage = NmeaMessageUnknown.construct(input);
80
+ break;
81
+ case 'DTM':
82
+ nmeaMessage = NmeaMessageDtm.construct(input);
83
+ break;
84
+ case 'GBS':
85
+ nmeaMessage = NmeaMessageGbs.construct(input);
86
+ break;
87
+ case 'GGA':
88
+ nmeaMessage = NmeaMessageGga.construct(input);
89
+ break;
90
+ case 'GLL':
91
+ nmeaMessage = NmeaMessageGll.construct(input);
92
+ break;
93
+ case 'GNS':
94
+ nmeaMessage = NmeaMessageGns.construct(input);
95
+ break;
96
+ case 'GRS':
97
+ nmeaMessage = NmeaMessageGrs.construct(input);
98
+ break;
99
+ case 'GSA':
100
+ nmeaMessage = NmeaMessageGsa.construct(input);
101
+ break;
102
+ case 'GST':
103
+ nmeaMessage = NmeaMessageGst.construct(input);
104
+ break;
105
+ case 'GSV':
106
+ nmeaMessage = NmeaMessageGsv.construct(input);
107
+ break;
108
+ case 'RMC':
109
+ nmeaMessage = NmeaMessageRmc.construct(input);
110
+ break;
111
+ case 'THS':
112
+ nmeaMessage = NmeaMessageThs.construct(input);
113
+ break;
114
+ case 'TXT':
115
+ nmeaMessage = NmeaMessageTxt.construct(input);
116
+ break;
117
+ case 'VHW':
118
+ nmeaMessage = NmeaMessageVhw.construct(input);
119
+ break;
120
+ case 'VLW':
121
+ nmeaMessage = NmeaMessageVlw.construct(input);
122
+ break;
123
+ case 'VPW':
124
+ nmeaMessage = NmeaMessageVpw.construct(input);
125
+ break;
126
+ case 'VTG':
127
+ nmeaMessage = NmeaMessageVtg.construct(input);
128
+ break;
129
+ case 'ZDA':
130
+ nmeaMessage = NmeaMessageZda.construct(input);
131
+ break;
132
+ default:
133
+ throw new Error('Unsupported NMEA message type: ' + messageType);
134
+ }
135
135
  }
136
- }
137
136
 
138
- let message = NmeaTransport.encode(nmeaMessage);
137
+ let message = NmeaTransport.encode(nmeaMessage);
139
138
 
140
- let outMsg = {
141
- payload: {
142
- nmeaMessage: message,
143
- messageType: payload.messageType,
144
- input: input,
145
- },
146
- };
139
+ let outMsg = {
140
+ payload: {
141
+ nmeaMessage: message,
142
+ messageType: payload.messageType,
143
+ input: input,
144
+ },
145
+ };
147
146
 
148
- node.send([outMsg, null]);
149
- node.nmeaMessagesReceived++;
150
- updateStatus(true);
151
- } catch (ex) {
152
- let errMsg = {
153
- payload: {
154
- error: ex,
155
- input: input,
156
- inputString: safeToString(input),
157
- },
158
- };
159
- node.send([null, errMsg]);
160
- node.invalidMessagesReceived++;
161
- updateStatus(false);
147
+ node.send([outMsg, null]);
148
+ node.nmeaMessagesReceived++;
149
+ updateStatus(true);
150
+ } catch (ex) {
151
+ let errMsg = {
152
+ payload: {
153
+ error: ex,
154
+ input: input,
155
+ inputString: safeToString(input),
156
+ },
157
+ };
158
+ node.send([null, errMsg]);
159
+ node.invalidMessagesReceived++;
160
+ updateStatus(false);
161
+ }
162
162
  }
163
163
  });
164
164