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.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -3
  3. package/examples/watchdog.json +1 -1
  4. package/ntrip/lib/ntrip-client.js +70 -72
  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,18 @@
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
+
4
16
  ## [0.2.10]
5
17
  ### NtripClient
6
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.
package/README.md CHANGED
@@ -355,9 +355,11 @@ NTRIP casters occasionally stop emitting data without closing the TCP socket
355
355
  — the NtripClient node stays "connected" but no new frames arrive, and any
356
356
  downstream rover eventually drops out of RTK-fixed mode without an obvious
357
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.
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.
361
363
  See also example flow [**Watchdog flow**](examples/watchdog.json) (closes [#4](https://github.com/windkh/node-red-contrib-ntrip/issues/4))
362
364
 
363
365
  # License
@@ -1 +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":[]}]
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":[]}]
@@ -56,79 +56,77 @@ class NtripClientUploader extends NtripClientWithBackoff {
56
56
  }
57
57
 
58
58
  _connect() {
59
- if (this.isClose) {
60
- 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);
61
129
  }
62
-
63
- // init connection of client
64
- this.client = net.createConnection({
65
- host: this.host,
66
- port: this.port,
67
- });
68
-
69
- // on timeout event
70
- this.client.on('timeout', () => {
71
- this._onError('socket timeout');
72
- });
73
-
74
- // on connect event
75
- this.client.on('connect', () => {
76
- const mountpoint = this.mountpoint;
77
- const userAgent = this.userAgent;
78
- const username = this.username;
79
- const password = this.password;
80
- const host = this.host;
81
- const port = this.port;
82
- const authmode = this.authmode;
83
- const authorization = Buffer.from(username + ':' + password, 'utf8').toString('base64');
84
-
85
- let data;
86
- switch (authmode) {
87
- case 'legacy': // Legacy --> rtk2Go accepts this.
88
- if (username === '' && password === '') {
89
- data = `SOURCE ${mountpoint}\r\n\r\n`;
90
- } else {
91
- data = `SOURCE ${password} /${mountpoint}\r\nSource-Agent: NTRIP ${username}\r\n\r\n`;
92
- }
93
- break;
94
- case 'hybrid': // hybrid (legacy + http auth) --> SNIP accepts this.
95
- data = `SOURCE ${mountpoint}\r\nAuthorization: Basic ${authorization}\r\n\r\n`;
96
- break;
97
- case 'ntripv1': // NTRIP V1 POST
98
- data = `POST /${mountpoint} HTTP/1.0\r\nUser-Agent: NTRIP ${userAgent}\r\nAuthorization: Basic ${authorization}\r\nContent-Type: gnss/data\r\n\r\n`;
99
- break;
100
- case 'ntripv2': // NTRIP V2 POST
101
- 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`;
102
- break;
103
- default:
104
- throw new Error('Auth mode is not supported ' + authmode);
105
- }
106
-
107
- this.client.write(data);
108
- });
109
-
110
- // on data event
111
- this.client.on('data', (data) => {
112
- this._onData(data);
113
- });
114
-
115
- // on end event
116
- this.client.on('end', () => {
117
- this._onError('socket ended');
118
- });
119
-
120
- // on close event
121
- this.client.on('close', () => {
122
- this._onError('socket closed');
123
- });
124
-
125
- // on error event
126
- this.client.on('error', (err) => {
127
- // TODO: check for code = ECONNREFUSED
128
- this._onError(err);
129
- });
130
-
131
- this.client.setTimeout(this.timeout);
132
130
  }
133
131
  }
134
132
 
@@ -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