node-red-contrib-ntrip 0.2.2 → 0.2.7

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 (41) hide show
  1. package/.github/dependabot.yml +23 -0
  2. package/.github/workflows/node.js.yml +22 -21
  3. package/.github/workflows/release.yml +65 -0
  4. package/.prettierignore +20 -0
  5. package/.prettierrc.json +9 -0
  6. package/CHANGELOG.md +64 -0
  7. package/CLAUDE.md +73 -0
  8. package/README.md +145 -18
  9. package/doc/architecture/README.md +19 -0
  10. package/doc/architecture/adr/0001-single-registration-file.md +64 -0
  11. package/doc/architecture/adr/0002-ntrip-uploader-extension.md +71 -0
  12. package/doc/architecture/adr/0003-two-output-decoder-design.md +73 -0
  13. package/doc/architecture/adr/0004-stateful-handshake-interception.md +74 -0
  14. package/doc/architecture/adr/0005-rtcm-partial-frame-buffer.md +87 -0
  15. package/doc/architecture/adr/0006-nmea-multi-sentence-split.md +66 -0
  16. package/doc/architecture/adr/0007-mocha-test-helper-stack.md +67 -0
  17. package/doc/architecture/adr/0008-coordinate-gating-sentinel.md +76 -0
  18. package/doc/architecture/adr/README.md +18 -0
  19. package/doc/architecture/architecture-decisions.md +60 -0
  20. package/doc/architecture/behavioural-design.md +226 -0
  21. package/doc/architecture/errors-and-weaknesses.md +71 -0
  22. package/doc/architecture/future-improvements.md +130 -0
  23. package/doc/architecture/overview.md +77 -0
  24. package/doc/architecture/refactoring-recommendations.md +114 -0
  25. package/doc/architecture/statistics.md +118 -0
  26. package/doc/architecture/structural-design.md +141 -0
  27. package/eslint.config.js +36 -0
  28. package/examples/nmea-decode.json +1 -0
  29. package/examples/nmea-encode.json +1 -0
  30. package/ntrip/99-ntrip.html +248 -27
  31. package/ntrip/99-ntrip.js +14 -11
  32. package/ntrip/lib/ntrip-client.js +23 -18
  33. package/ntrip/nodes/nmea-decoder-node.js +80 -37
  34. package/ntrip/nodes/nmea-encoder-node.js +172 -0
  35. package/ntrip/nodes/ntrip-client-node.js +143 -123
  36. package/ntrip/nodes/rtcm-decoder-node.js +65 -47
  37. package/package.json +20 -2
  38. package/test/nmea-decoder.spec.js +146 -0
  39. package/test/nmea-encoder.spec.js +104 -0
  40. package/test/rtcm-decoder.spec.js +116 -0
  41. package/.github/workflows/npm-publish.yml +0 -33
@@ -0,0 +1,118 @@
1
+ # Statistics
2
+
3
+ Snapshot taken at **v0.2.6** (commit `ecf02ba`).
4
+
5
+ ## Lines of code
6
+
7
+ ### Source
8
+
9
+ | File | LOC |
10
+ |------|----:|
11
+ | [ntrip/99-ntrip.html](../../ntrip/99-ntrip.html) | 467 |
12
+ | [ntrip/nodes/ntrip-client-node.js](../../ntrip/nodes/ntrip-client-node.js) | 182 |
13
+ | [ntrip/nodes/nmea-encoder-node.js](../../ntrip/nodes/nmea-encoder-node.js) | 177 |
14
+ | [ntrip/lib/ntrip-client.js](../../ntrip/lib/ntrip-client.js) | 114 |
15
+ | [ntrip/nodes/nmea-decoder-node.js](../../ntrip/nodes/nmea-decoder-node.js) | 98 |
16
+ | [ntrip/nodes/rtcm-decoder-node.js](../../ntrip/nodes/rtcm-decoder-node.js) | 91 |
17
+ | [ntrip/99-ntrip.js](../../ntrip/99-ntrip.js) | 26 |
18
+ | **Total** | **1155** |
19
+
20
+ - JavaScript-only source: **688 LOC** across 6 files.
21
+ - HTML editor UI: **467 LOC** in one file.
22
+
23
+ ### Tests
24
+
25
+ | File | LOC | Tests |
26
+ |------|----:|------:|
27
+ | [test/nmea-decoder.spec.js](../../test/nmea-decoder.spec.js) | 134 | 6 |
28
+ | [test/rtcm-decoder.spec.js](../../test/rtcm-decoder.spec.js) | 108 | 4 |
29
+ | [test/nmea-encoder.spec.js](../../test/nmea-encoder.spec.js) | 96 | 4 |
30
+ | **Total** | **338** | **14** |
31
+
32
+ ### Ratios
33
+
34
+ | Metric | Value |
35
+ |--------|------:|
36
+ | Test LOC / JS source LOC | 49 % |
37
+ | Tests per JS source file (4 of 6 covered by specs) | 3.5 avg |
38
+ | Comments + blank lines (rough estimate) | ~20 % of source |
39
+
40
+ ## Test suite
41
+
42
+ Run with `npm test`. Latest local run: **14 passing, 0 failing, ~500 ms**.
43
+
44
+ Breakdown:
45
+
46
+ - **NmeaDecoder** (6 specs) — golden GGA decode, multi-sentence split, Buffer
47
+ input, `payload.nmeaMessage` shape, Buffer preservation in error output, no
48
+ `node.error` flood (regression).
49
+ - **NmeaEncoder** (4 specs) — `NmeaMessage` instance passthrough, unknown
50
+ `messageType` routed to error output, missing fields routed to error output,
51
+ empty payload handled.
52
+ - **RtcmDecoder** (4 specs) — golden RTCM 1005 decode, two concatenated frames
53
+ fan out, frame split across two input events reassembled, no `node.error`
54
+ flood under garbage (regression).
55
+
56
+ 3 of the 14 specs are explicitly tagged `(regression: …)` and lock in
57
+ previously-shipped bug fixes. Run them in isolation with
58
+ `npx mocha test/**/*.spec.js -g regression`.
59
+
60
+ NtripClient is *not* covered by the suite — it requires a fake TCP server.
61
+ See [Future Improvements](future-improvements.md).
62
+
63
+ ## Coverage
64
+
65
+ **Not measured.** No `c8` / `nyc` / Istanbul integration is configured. To
66
+ enable, add `c8` as a dev dep and a `coverage` script (`c8 --reporter=text
67
+ --reporter=html mocha test/**/*.spec.js`). Statement coverage of the three
68
+ spec'd nodes should be ≥ 85 % at the current spec count; the missing 15 % is
69
+ mostly edge-case branches inside `RtcmTransport.decode` failures.
70
+
71
+ ## Repository history
72
+
73
+ Snapshot from `git log`:
74
+
75
+ | Metric | Value |
76
+ |--------|------:|
77
+ | Total commits on `main` | 28 |
78
+ | Commits matching `/fix/i` | 8 |
79
+ | Releases tagged in [CHANGELOG.md](../../CHANGELOG.md) | 9 (0.1.0 → 0.2.6) |
80
+ | Bug-fix-only releases (0.2.4 / 0.2.5 / 0.2.6) | 3 (last 14 days) |
81
+
82
+ ## Dependencies
83
+
84
+ | Class | Count | Names |
85
+ |-------|------:|-------|
86
+ | Runtime | 3 | `@gnss/nmea`, `@gnss/rtcm`, `ntrip-client` |
87
+ | Dev | 4 | `mocha`, `chai`, `node-red`, `node-red-node-test-helper` |
88
+ | Transitive (dev tree) | ~600 | from `npm audit` |
89
+
90
+ After installing dev deps: `6 vulnerabilities (1 low, 2 moderate, 3 high)` —
91
+ all inside the `node-red` dev tree, none reachable from the published package
92
+ because the runtime dep set is just three packages.
93
+
94
+ ## Quality Index
95
+
96
+ A composite, intentionally rough — a way to track movement over time, not an
97
+ absolute scale. Each criterion is scored 0–10; the index is the unweighted
98
+ mean.
99
+
100
+ | Criterion | Score | Reasoning |
101
+ |-----------|------:|-----------|
102
+ | Test coverage (proxy: spec count vs node count) | 7 | 14 specs over 3 of 4 nodes. `NtripClient` untested. |
103
+ | Bug-fix cadence (lower is better, scored inversely) | 5 | 3 patch releases in 2 weeks indicates churn from recent rewrite — settling now. |
104
+ | Documentation completeness | 8 | Per-node help text, README usage notes, this architecture doc. |
105
+ | Type safety | 2 | No TypeScript or JSDoc-with-`@ts-check`. |
106
+ | Lint / style enforcement | 1 | No `eslint` or `prettier`. Style is consistent by convention only. |
107
+ | Dependency hygiene | 7 | 3 runtime deps, 0 known runtime vulns. Dev tree carries `node-red` itself. |
108
+ | CI rigour | 6 | Builds + tests on three Node versions. No coverage gate, no lint gate. |
109
+ | Bus factor | 4 | Single primary maintainer (`Karl-Heinz Wind`). 1 active contributor. |
110
+ | **Overall** | **5.0** | Median: 5.5. Headroom in lint, types, and `NtripClient` coverage. |
111
+
112
+ ### How to improve the index quickly
113
+
114
+ - **+1.0 each** — adopting `eslint` (covers Type safety bump via `@ts-check`
115
+ on JSDoc) and adding coverage reporting.
116
+ - **+0.5** — writing one round of `NtripClient` integration specs against a
117
+ fake caster.
118
+ - **+0.5** — pinning dev deps in `package.json` more strictly (`~` instead of `^`).
@@ -0,0 +1,141 @@
1
+ # Structural Design
2
+
3
+ ## Repository layout
4
+
5
+ ```
6
+ node-red-contrib-ntrip/
7
+ ├── ntrip/
8
+ │ ├── 99-ntrip.js # Node-RED registration entry point
9
+ │ ├── 99-ntrip.html # Editor UI for all four nodes (forms + help text)
10
+ │ ├── nodes/
11
+ │ │ ├── ntrip-client-node.js
12
+ │ │ ├── rtcm-decoder-node.js
13
+ │ │ ├── nmea-decoder-node.js
14
+ │ │ └── nmea-encoder-node.js
15
+ │ └── lib/
16
+ │ └── ntrip-client.js # Local extension of the upstream ntrip-client package
17
+ ├── examples/ # Sample flows (ntripclient.json, sapos.json, upload.json, …)
18
+ ├── test/ # Mocha specs (one per node, suffixed .spec.js)
19
+ ├── images/ # Screenshots referenced by README.md
20
+ ├── doc/architecture/ # This documentation tree
21
+ ├── package.json
22
+ ├── package-lock.json
23
+ ├── CHANGELOG.md
24
+ ├── CLAUDE.md
25
+ └── README.md
26
+ ```
27
+
28
+ ## Module dependency graph
29
+
30
+ ```
31
+ ┌────────────────────────┐
32
+ │ ntrip/99-ntrip.js │ (Node-RED entry, registerType x 4)
33
+ └─────────┬──────────────┘
34
+ │ require
35
+ ┌─────────┴────────────────────────────────────────────────────────┐
36
+ ▼ ▼ ▼ ▼ │
37
+ ntrip-client- rtcm-decoder- nmea-decoder- nmea-encoder- │
38
+ node.js node.js node.js node.js │
39
+ │ │ │ │ │
40
+ │ require │ require │ require │ require │
41
+ ▼ ▼ ▼ ▼ │
42
+ lib/ntrip- @gnss/rtcm @gnss/nmea @gnss/nmea │
43
+ client.js │
44
+ │ │
45
+ │ require │
46
+ ▼ │
47
+ ntrip-client (npm package, upstream) │
48
+
49
+
50
+ package.json reads
51
+ version into log line
52
+ ```
53
+
54
+ ## File responsibilities
55
+
56
+ ### Entry layer
57
+
58
+ | File | Responsibility |
59
+ |------|----------------|
60
+ | [ntrip/99-ntrip.js](../../ntrip/99-ntrip.js) | Imports each node implementation. Registers the four types with `RED.nodes.registerType`. Declares the `credentials` block for `NtripClient`. Reads the package version from `package.json` and logs it. |
61
+ | [ntrip/99-ntrip.html](../../ntrip/99-ntrip.html) | Per-node `registerType` call (client side), the configuration `<template>`, and the help text shown in Node-RED's info sidebar. The credentials block is duplicated here — Node-RED requires both. |
62
+
63
+ ### Node implementations
64
+
65
+ | File | Type | Inputs | Outputs |
66
+ |------|------|--------|---------|
67
+ | [ntrip/nodes/ntrip-client-node.js](../../ntrip/nodes/ntrip-client-node.js) | `NtripClient` | 1 | 1 (data from caster + optional pass-through of writes) |
68
+ | [ntrip/nodes/rtcm-decoder-node.js](../../ntrip/nodes/rtcm-decoder-node.js) | `RtcmDecoder` | 1 | 2 (decoded, error) |
69
+ | [ntrip/nodes/nmea-decoder-node.js](../../ntrip/nodes/nmea-decoder-node.js) | `NmeaDecoder` | 1 | 2 (decoded, error) |
70
+ | [ntrip/nodes/nmea-encoder-node.js](../../ntrip/nodes/nmea-encoder-node.js) | `NmeaEncoder` | 1 | 2 (encoded, error) |
71
+
72
+ All four follow the same skeleton:
73
+
74
+ ```javascript
75
+ module.exports = function (RED) {
76
+ function Node(config) {
77
+ RED.nodes.createNode(this, config);
78
+ const node = this;
79
+ node.<counter>Received = 0;
80
+ node.invalidMessagesReceived = 0;
81
+
82
+ // ... wire up the on('input') handler, possibly an external resource
83
+
84
+ this.on('close', function (done) {
85
+ // cleanup
86
+ node.status({});
87
+ done();
88
+ });
89
+ }
90
+ return Node;
91
+ };
92
+ ```
93
+
94
+ ### Library layer
95
+
96
+ [ntrip/lib/ntrip-client.js](../../ntrip/lib/ntrip-client.js) — a thin local
97
+ adapter around the upstream `ntrip-client` npm package. Exposes two factories:
98
+
99
+ - `createDownloader(options)` — returns the upstream `NtripClient` unchanged.
100
+ - `createUploader(options)` — returns a subclass `NtripClientUploader` that
101
+ overrides `_connect()` to switch the handshake string by `authmode`
102
+ (`legacy`, `hybrid`, `ntripv1`, `ntripv2`). The constructor also rejects
103
+ CR/LF in `mountpoint`/`username`/`password` to prevent header injection.
104
+
105
+ The override pattern is a full replacement of the parent's `_connect`, not a
106
+ delegation — see [ADR-0002](adr/0002-ntrip-uploader-extension.md) for the
107
+ rationale and trade-offs.
108
+
109
+ ## Class / type hierarchy
110
+
111
+ ```
112
+ ntrip-client (npm)
113
+ └── NtripClient # download
114
+ └── NtripClientUploader (lib/ntrip-client.js) # upload
115
+ ```
116
+
117
+ No other classes are defined in this package; every Node-RED node is a plain
118
+ function passed to `registerType`, and decoder/encoder state lives on the node
119
+ instance via simple property assignment (`node.pendingBuffer`, `node.messagesReceived`).
120
+
121
+ ## Coupling
122
+
123
+ - **High cohesion within each node file.** A node's input handler, status
124
+ updates, and close handler are co-located. There is no shared mutable state
125
+ across node instances.
126
+ - **No cross-node imports.** The decoders do not know about `NtripClient` and
127
+ vice versa; they communicate only through Node-RED wires at flow runtime.
128
+ - **Two seams to the outside world:**
129
+ - The `ntrip-client` upstream package (via [lib/ntrip-client.js](../../ntrip/lib/ntrip-client.js)).
130
+ - `@gnss/rtcm` and `@gnss/nmea` for codec work.
131
+ - **Implicit coupling between [99-ntrip.js](../../ntrip/99-ntrip.js) and [99-ntrip.html](../../ntrip/99-ntrip.html)** — both must declare the `credentials` block, and the registered node-type names must match exactly. The HTML form field IDs (`node-input-host`, `node-input-port`, …) must align with the property names read in the JS (`config.host`, `config.port`, …).
132
+
133
+ ## Distribution shape
134
+
135
+ - `node-red.nodes` in [package.json](../../package.json) points only at
136
+ `ntrip/99-ntrip.js` — Node-RED loads that file, which transitively pulls in
137
+ everything else.
138
+ - Runtime dependencies: `@gnss/nmea`, `@gnss/rtcm`, `ntrip-client`. No transient
139
+ runtime dependency on Node-RED itself.
140
+ - Dev-only dependencies: `mocha`, `chai` (CJS v4), `node-red`,
141
+ `node-red-node-test-helper`.
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const js = require('@eslint/js');
4
+ const globals = require('globals');
5
+ const prettierConfig = require('eslint-config-prettier');
6
+
7
+ module.exports = [
8
+ {
9
+ ignores: ['node_modules/**', 'examples/**', 'doc/**', 'ntrip/99-ntrip.html'],
10
+ },
11
+ js.configs.recommended,
12
+ prettierConfig,
13
+ {
14
+ languageOptions: {
15
+ ecmaVersion: 2022,
16
+ sourceType: 'commonjs',
17
+ globals: {
18
+ ...globals.node,
19
+ },
20
+ },
21
+ rules: {
22
+ 'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
23
+ 'no-console': 'off',
24
+ 'no-prototype-builtins': 'off',
25
+ },
26
+ },
27
+ {
28
+ files: ['test/**/*.js'],
29
+ languageOptions: {
30
+ globals: {
31
+ ...globals.node,
32
+ ...globals.mocha,
33
+ },
34
+ },
35
+ },
36
+ ];
@@ -0,0 +1 @@
1
+ [{"id":"ce248cbbb81fedc7","type":"debug","z":"95fe791a9c2e02fb","name":"debug 45","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1100,"y":360,"wires":[]},{"id":"1e5f02ac026876e5","type":"debug","z":"95fe791a9c2e02fb","name":"debug 46","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1100,"y":420,"wires":[]},{"id":"fa8ecd8584cd2497","type":"NmeaDecoder","z":"95fe791a9c2e02fb","description":"","x":780,"y":380,"wires":[["ce248cbbb81fedc7","395ad916396d2633"],["1e5f02ac026876e5"]]},{"id":"458040210a13ca79","type":"serial in","z":"95fe791a9c2e02fb","name":"COM8 USB","serial":"02508e00360cfd65","x":590,"y":380,"wires":[["028e8c8c34a7cb4f","fa8ecd8584cd2497"]]},{"id":"028e8c8c34a7cb4f","type":"debug","z":"95fe791a9c2e02fb","name":"debug 67","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":760,"y":420,"wires":[]},{"id":"02508e00360cfd65","type":"serial-port","name":"","serialport":"COM8","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","waitfor":"","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"\\n","bin":"false","out":"char","addchar":"","responsetimeout":"10000"}]
@@ -0,0 +1 @@
1
+ [{"id":"395ad916396d2633","type":"NmeaEncoder","z":"95fe791a9c2e02fb","description":"","x":1100,"y":660,"wires":[["b1d594a6a88edfca","0451732ab38bafa9"],["0aacddfbe23ce47d"]]},{"id":"b1d594a6a88edfca","type":"debug","z":"95fe791a9c2e02fb","name":"debug 65","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1280,"y":620,"wires":[]},{"id":"0aacddfbe23ce47d","type":"debug","z":"95fe791a9c2e02fb","name":"debug 66","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1280,"y":680,"wires":[]},{"id":"8288987f3959d44e","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":540,"wires":[["311378b9c7996b1f"]]},{"id":"311378b9c7996b1f","type":"function","z":"95fe791a9c2e02fb","name":"GPDTM","func":"// https://github.com/Node-NTRIP/nmea/blob/master/src/messages/dtm.ts\nmsg.payload = {\n messageType : 'DTM',\n nmeaMessage : {\n talker: 'GP', \n datum: 'W84',\n // subDatum: string;\n latitudeOffset : 0,\n longitudeOffset: 0,\n altitudeOffset: 0,\n // refDatum: 'W84'\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":540,"wires":[["395ad916396d2633"]]},{"id":"81c6cb1be666c10c","type":"function","z":"95fe791a9c2e02fb","name":"GNGBS","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/gbs.ts\nmsg.payload = {\n messageType : 'GBS',\n nmeaMessage : {\n talker: 'GN',\n time: new Date(0, 0, 0, 1, 2, 3, 0),\n latitudeError: 0,\n longitudeError: 0,\n altitudeError: 0,\n satelliteId: 1,\n probability: 1,\n bias: 0,\n standardDeviation: 0,\n systemId: 1,\n signalId: 0,\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":580,"wires":[["395ad916396d2633"]]},{"id":"0e982631e6b36163","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":580,"wires":[["81c6cb1be666c10c"]]},{"id":"0451732ab38bafa9","type":"NmeaDecoder","z":"95fe791a9c2e02fb","description":"","x":1320,"y":760,"wires":[["ccc599cb0e9addf6"],[]]},{"id":"ccc599cb0e9addf6","type":"debug","z":"95fe791a9c2e02fb","name":"debug 68","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1480,"y":740,"wires":[]},{"id":"7ea7ecdb2b40ae9c","type":"function","z":"95fe791a9c2e02fb","name":"GNGLL","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/gll.ts\nmsg.payload = {\n messageType : 'GLL',\n nmeaMessage : {\n talker: 'GN',\n time: new Date(0, 0, 0, 1, 2, 3, 0),\n latitude: 66.66,\n longitude: 33.33,\n status: 'A',\n modeIndicator: 'R',\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":660,"wires":[["395ad916396d2633"]]},{"id":"fa082ed5b7d0ed5a","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":660,"wires":[["7ea7ecdb2b40ae9c"]]},{"id":"9cb944ace7f6806e","type":"function","z":"95fe791a9c2e02fb","name":"GNGGA","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/gga.ts\nmsg.payload = {\n messageType : 'GGA',\n nmeaMessage : {\n talker: 'GN',\n time: new Date(0, 0, 0, 1, 2, 3, 0),\n latitude: 66.66,\n longitude: 33.33,\n quality: 1,\n numberSatellites: 10,\n hdop: 2.5,\n altitude: 180.00,\n geoidalSeparation: 90.00,\n differentialAge: 5,\n differentialStationId: 'differentialStationId',\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":620,"wires":[["395ad916396d2633"]]},{"id":"0e3c3f77a0bc9553","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":620,"wires":[["9cb944ace7f6806e"]]},{"id":"78e689b321ae3f8f","type":"comment","z":"95fe791a9c2e02fb","name":"","info":"// see also https://github.com/node-ntrip/nmea#readme\n// put your payload into msg.payload.message see output of decoder node","x":740,"y":500,"wires":[]},{"id":"9fb5ecd91eba0f94","type":"function","z":"95fe791a9c2e02fb","name":"GNGNS","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/gns.ts\nmsg.payload = {\n messageType : 'GNS',\n nmeaMessage : {\n talker: 'GN',\n time: new Date(0, 0, 0, 1, 2, 3, 0),\n latitude: 66.66,\n longitude: 33.33,\n modeIndicators: ['R'],\n numberSatellites: 1,\n hdop: 0,\n altitude: 123,\n geoidalSeparation: 0,\n differentialAge: 0,\n differentialStationId: 'differentialStationId',\n navigationStatus: 'S',\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":700,"wires":[["395ad916396d2633"]]},{"id":"b4c368a6ec3fd9b0","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":700,"wires":[["9fb5ecd91eba0f94"]]},{"id":"346c9bfb24eca4bf","type":"function","z":"95fe791a9c2e02fb","name":"GNGRS","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/grs.ts\nmsg.payload = {\n messageType : 'GRS',\n nmeaMessage : {\n talker: 'GN',\n time: new Date(0, 0, 0, 1, 2, 3, 0),\n residualsMode: 0,\n residuals: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],\n systemId: 1,\n signalId: 1,\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":740,"wires":[["395ad916396d2633"]]},{"id":"a42bcfe61f797be3","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":740,"wires":[["346c9bfb24eca4bf"]]},{"id":"24fc3108ef208f3b","type":"function","z":"95fe791a9c2e02fb","name":"GNGSA","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/gsa.ts\nmsg.payload = {\n messageType : 'GSA',\n nmeaMessage : {\n talker: 'GN',\n operationMode: 'A',\n navMode: 3,\n satellites: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],\n pdop: 0,\n hdop: 0,\n vdop: 0,\n systemId: 1,\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":780,"wires":[["395ad916396d2633"]]},{"id":"79f77e548a594e86","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":780,"wires":[["24fc3108ef208f3b"]]},{"id":"946e93e6b5d5bd99","type":"function","z":"95fe791a9c2e02fb","name":"GNGST","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/gst.ts\nmsg.payload = {\n messageType : 'GST',\n nmeaMessage : {\n talker: 'GN',\n time: new Date(0, 0, 0, 1, 2, 3, 0),\n rangeRms: 1,\n standardDeviationMajor: 0,\n standardDeviationMinor: 0,\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":820,"wires":[["395ad916396d2633"]]},{"id":"45b4a35d34900485","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":820,"wires":[["946e93e6b5d5bd99"]]},{"id":"cd0e6bb7b2ebe038","type":"function","z":"95fe791a9c2e02fb","name":"GNGSV","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/gsv.ts\nmsg.payload = {\n messageType : 'GSV',\n nmeaMessage : {\n talker: 'GN',\n sentenceCount: 1,\n sentenceNumber: 1,\n numberSatellites: 1,\n satellites: [{ \n satelliteId: 1,\n elevationDegrees: 1,\n azimuthDegrees: 0,\n signalNoiseRatio: 1,\n }],\n signalId: 1,\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":860,"wires":[["395ad916396d2633"]]},{"id":"95fbbdfc6e3339cf","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":860,"wires":[["cd0e6bb7b2ebe038"]]},{"id":"a8daeb2afa192350","type":"function","z":"95fe791a9c2e02fb","name":"GNRMC","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/rmc.ts\nmsg.payload = {\n messageType : 'RMC',\n nmeaMessage : {\n talker: 'GN',\n time: new Date(0, 0, 0, 1, 2, 3, 0),\n status: 'A',\n latitude: 10,\n longitude: 11,\n speedOverGroundKnots: 0,\n courseOverGroundTrue: 1,\n date: new Date(0, 0, 0),\n magneticVariation: 0,\n modeIndicator: 'R',\n navStatus: 'S',\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":900,"wires":[["395ad916396d2633"]]},{"id":"aabbb873df0609ca","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":900,"wires":[["a8daeb2afa192350"]]},{"id":"54fe198c52d51c3d","type":"function","z":"95fe791a9c2e02fb","name":"GNTHS","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/ths.ts\nmsg.payload = {\n messageType : 'THS',\n nmeaMessage : {\n talker: 'GN',\n headingTrue: 1,\n modeIndicator: 'R'\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":940,"wires":[["395ad916396d2633"]]},{"id":"2227f94cab8861f7","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":940,"wires":[["54fe198c52d51c3d"]]},{"id":"53b59280cfaa354c","type":"function","z":"95fe791a9c2e02fb","name":"GNTXT","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/txt.ts\nmsg.payload = {\n messageType : 'TXT',\n nmeaMessage : {\n talker: 'GN',\n messageCount: 1,\n messageNumber: 1,\n messageType: 1,\n text: 'Bigfoot'\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":980,"wires":[["395ad916396d2633"]]},{"id":"5db9e0dee97ef9e0","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":980,"wires":[["53b59280cfaa354c"]]},{"id":"4aeabe8dfd5e58de","type":"function","z":"95fe791a9c2e02fb","name":"GNVHW","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/vhw.ts\nmsg.payload = {\n messageType : 'VHW',\n nmeaMessage : {\n talker: 'GN',\n headingTrue: 1,\n headingMagnetic: 2,\n speedKnots: 3,\n speedKmh: 4\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":1020,"wires":[["395ad916396d2633"]]},{"id":"d835de357bdbb233","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":1020,"wires":[["4aeabe8dfd5e58de"]]},{"id":"95767237d022ba6f","type":"function","z":"95fe791a9c2e02fb","name":"GNVLW","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/vlw.ts\nmsg.payload = {\n messageType : 'VLW',\n nmeaMessage : {\n talker: 'GN',\n totalCumulativeWaterDistance: 1,\n resetWaterDistance: 2,\n totalCumulativeGroundDistance: 3,\n resetGroundDistance: 4\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":1060,"wires":[["395ad916396d2633"]]},{"id":"d6f529187402ec50","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":1060,"wires":[["95767237d022ba6f"]]},{"id":"f72f5b6462f257c4","type":"function","z":"95fe791a9c2e02fb","name":"GNVPW","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/vpw.ts\nmsg.payload = {\n messageType : 'VPW',\n nmeaMessage : {\n talker: 'GN',\n speedKnots: 1,\n speedMs: 2\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":1100,"wires":[["395ad916396d2633"]]},{"id":"238c581bcb5a2ecf","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":1100,"wires":[["f72f5b6462f257c4"]]},{"id":"80bfe72db5dee054","type":"function","z":"95fe791a9c2e02fb","name":"GNVTG","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/vtg.ts\nmsg.payload = {\n messageType : 'VTG',\n nmeaMessage : {\n talker: 'GN',\n courseOverGroundTrue: 1,\n courseOverGroundMagnetic: 2,\n speedOverGroundKnots: 3,\n speedOverGroundKmh: 4,\n modeIndicator: 'R',\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":1140,"wires":[["395ad916396d2633"]]},{"id":"8a811f6ae216f934","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":1140,"wires":[["80bfe72db5dee054"]]},{"id":"0cfc1b0b7af0efa1","type":"function","z":"95fe791a9c2e02fb","name":"GNZDA","func":"// see https://github.com/Node-NTRIP/nmea/blob/master/src/messages/zda.ts\nmsg.payload = {\n messageType : 'ZDA',\n nmeaMessage : {\n talker: 'GN',\n date: new Date(0, 0, 0, 1, 2, 3, 0),\n localTimeZoneHours: 1,\n localTimeZoneMinutes: 2\n }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":840,"y":1180,"wires":[["395ad916396d2633"]]},{"id":"5a48b0e6d7d8d4c0","type":"inject","z":"95fe791a9c2e02fb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":680,"y":1180,"wires":[["0cfc1b0b7af0efa1"]]}]