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
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
|
|
359
|
-
|
|
360
|
-
|
|
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
|
package/examples/watchdog.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"id":"47bd8463328d83d3","type":"comment","z":"a604108643639054","name":"NTRIP stream stall
|
|
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
|
-
|
|
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
|
|
53
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let input = payload.nmeaMessage;
|
|
67
|
+
} else {
|
|
68
|
+
let input = payload.nmeaMessage;
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
try {
|
|
71
|
+
let messageType = String(payload.messageType).toUpperCase();
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
137
|
+
let message = NmeaTransport.encode(nmeaMessage);
|
|
139
138
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
let outMsg = {
|
|
140
|
+
payload: {
|
|
141
|
+
nmeaMessage: message,
|
|
142
|
+
messageType: payload.messageType,
|
|
143
|
+
input: input,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
147
146
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|