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,23 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: 'npm'
4
+ directory: '/'
5
+ schedule:
6
+ interval: 'weekly'
7
+ open-pull-requests-limit: 5
8
+ # The following packages are transitive devDependencies pulled in by
9
+ # `node-red` / `node-red-node-test-helper`. They have known advisories
10
+ # but no patched version is currently resolvable, so Dependabot's
11
+ # security-update PRs fail noisily on every retry. Since these packages
12
+ # are dev-only (not reachable from the published runtime tree of
13
+ # @gnss/nmea, @gnss/rtcm, ntrip-client), the advisories don't affect
14
+ # end users. Remove the ignore once upstream ships a fixed version.
15
+ ignore:
16
+ - dependency-name: 'picomatch'
17
+ - dependency-name: 'ip-address'
18
+ - dependency-name: 'serialize-javascript'
19
+
20
+ - package-ecosystem: 'github-actions'
21
+ directory: '/'
22
+ schedule:
23
+ interval: 'monthly'
@@ -4,28 +4,29 @@
4
4
  name: Node.js CI
5
5
 
6
6
  on:
7
- push:
8
- branches: [ "main" ]
9
- pull_request:
10
- branches: [ "main" ]
7
+ push:
8
+ branches: ['main']
9
+ pull_request:
10
+ branches: ['main']
11
11
 
12
12
  jobs:
13
- build:
13
+ build:
14
+ runs-on: ubuntu-latest
14
15
 
15
- runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ node-version: [18.x, 20.x, 22.x]
19
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
16
20
 
17
- strategy:
18
- matrix:
19
- node-version: [18.x, 20.x, 22.x]
20
- # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21
-
22
- steps:
23
- - uses: actions/checkout@v4
24
- - name: Use Node.js ${{ matrix.node-version }}
25
- uses: actions/setup-node@v4
26
- with:
27
- node-version: ${{ matrix.node-version }}
28
- cache: 'npm'
29
- - run: npm ci
30
- - run: npm run build --if-present
31
- #- run: npm test
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - name: Use Node.js ${{ matrix.node-version }}
24
+ uses: actions/setup-node@v4
25
+ with:
26
+ node-version: ${{ matrix.node-version }}
27
+ cache: 'npm'
28
+ - run: npm ci
29
+ - run: npm run build --if-present
30
+ - run: npm run lint
31
+ - run: npm run format:check
32
+ - run: npm test
@@ -0,0 +1,65 @@
1
+ # Release pipeline.
2
+ #
3
+ # Trigger: push of a version tag matching v* or V* (e.g. v0.2.7, V1.0.0).
4
+ # Stages:
5
+ # 1. test — runs the full lint/format/test matrix on the tagged commit.
6
+ # 2. publish-npm — publishes the package to the npm registry.
7
+ # 3. github-release — creates a GitHub Release with auto-generated notes.
8
+ #
9
+ # Requires the `NPM_TOKEN` secret to be configured in the repository.
10
+
11
+ name: Release
12
+
13
+ on:
14
+ push:
15
+ tags:
16
+ - 'v*'
17
+ - 'V*'
18
+
19
+ jobs:
20
+ test:
21
+ runs-on: ubuntu-latest
22
+ strategy:
23
+ matrix:
24
+ node-version: [18.x, 20.x, 22.x]
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - name: Use Node.js ${{ matrix.node-version }}
28
+ uses: actions/setup-node@v4
29
+ with:
30
+ node-version: ${{ matrix.node-version }}
31
+ cache: 'npm'
32
+ - run: npm ci
33
+ - run: npm run lint
34
+ - run: npm run format:check
35
+ - run: npm test
36
+
37
+ publish-npm:
38
+ needs: test
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+ - uses: actions/setup-node@v4
43
+ with:
44
+ node-version: 20
45
+ registry-url: 'https://registry.npmjs.org/'
46
+ - run: npm ci
47
+ - run: npm publish
48
+ env:
49
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
50
+
51
+ github-release:
52
+ needs: publish-npm
53
+ runs-on: ubuntu-latest
54
+ permissions:
55
+ contents: write
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ with:
59
+ fetch-depth: 0
60
+ - name: Create GitHub Release
61
+ uses: softprops/action-gh-release@v2
62
+ with:
63
+ tag_name: ${{ github.ref_name }}
64
+ name: ${{ github.ref_name }}
65
+ generate_release_notes: true
@@ -0,0 +1,20 @@
1
+ # Generated / vendored
2
+ node_modules/
3
+ package-lock.json
4
+
5
+ # Node-RED flow files — re-formatting would change diff noise without value
6
+ examples/
7
+
8
+ # Editor UI HTML has Node-RED-specific structural conventions
9
+ # (data-template-name script blocks, x-red script blocks) that Prettier
10
+ # can mangle. Touch only with care.
11
+ ntrip/99-ntrip.html
12
+
13
+ # Markdown is kept hand-formatted (line-wrapped prose, ASCII diagrams).
14
+ # Re-flowing it would produce a large noisy diff.
15
+ *.md
16
+ doc/
17
+
18
+ # Editor / agent local state — not checked in
19
+ .claude/
20
+ .vscode/
@@ -0,0 +1,9 @@
1
+ {
2
+ "trailingComma": "es5",
3
+ "tabWidth": 4,
4
+ "semi": true,
5
+ "singleQuote": true,
6
+ "bracketSpacing": true,
7
+ "endOfLine": "auto",
8
+ "printWidth": 160
9
+ }
package/CHANGELOG.md CHANGED
@@ -1,6 +1,70 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## [0.2.7]
5
+ ### Internal — tooling, tests, docs (no runtime changes)
6
+ - Added a mocha test suite (14 specs across `NmeaDecoder`, `NmeaEncoder`, `RtcmDecoder`) using `node-red-node-test-helper`. Regression specs are tagged so they can be re-run with `mocha -g regression`.
7
+ - Added ESLint (flat config, `@eslint/js` recommended + `eslint-config-prettier`) and Prettier (4-space indent, single quotes, `printWidth 160`).
8
+ - CI now runs `lint` + `format:check` + `test` across Node 18/20/22.
9
+ - New `.github/workflows/release.yml` — push a `v*` or `V*` tag to trigger npm publish + GitHub Release with auto-generated notes.
10
+ - New `.github/dependabot.yml` — weekly npm, monthly github-actions. Ignores `picomatch`, `ip-address`, `serialize-javascript` (transitive devDeps with no resolvable patched version yet) to stop noisy red security-update runs.
11
+ - New architecture documentation tree under `doc/architecture/` (8 chapters + 8 ADRs).
12
+
13
+ ## [0.2.6]
14
+ ### Fixed regressions introduced in 0.2.5
15
+ - RTCM and NMEA decoders no longer call `node.error()` on decode failures. Decode failures still go to the second (error) output, but they no longer flood the Node-RED `[error]` log when a binary stream is intentionally piped through the "wrong" decoder (e.g. RTCM bytes through the NMEA decoder — the demo flow ships with this wiring).
16
+ - NMEA decoder error output now preserves the original payload on `input` (a `Buffer` if a `Buffer` was supplied) and exposes the utf8 string form as `inputString`, matching pre-0.2.5 behavior.
17
+
18
+ ### Documentation
19
+ - Completed the per-node help text in the Node-RED info sidebar for all four nodes (`NtripClient`, `RtcmDecoder`, `NmeaDecoder`, `NmeaEncoder`) — configuration parameters, input/output shapes, status legend.
20
+ - README updated: Requirements section (Node.js >= 18), corrected NMEA decoder output schema (`nmeaMessage`, not `message`), new NMEA Encoder section with the full list of supported `messageType` values, a "Notes on usage" section covering the common gotchas (two-output design, coordinate gating, dynamic `setXYZ` injection, pass-through interleaving, plaintext transport), and a number of typo fixes.
21
+
22
+ ## [0.2.5]
23
+ ### Fixed NTRIP client node
24
+ - Unsupported `mode` no longer crashes the node with `client is undefined`; it now logs an error and stops cleanly.
25
+ - Handshake detection (`ICY 200 OK`, `ICY 406`, `SOURCETABLE 200 OK`) is now only checked while the connection is not yet established, and any RTCM bytes that arrive in the same TCP segment as the `ICY 200 OK` reply are now forwarded instead of dropped.
26
+ - Coordinate guard changed from `x && y && z` to `not all zero`, so coordinates on an axis (e.g. equator) are no longer silently dropped. Non-finite values are rejected.
27
+ - Separate Rx/Tx counters; handshake replies no longer inflate the inbound message count.
28
+ - `host` is validated as a non-empty trimmed string; `interval` is coerced to an integer.
29
+ - `AggregateError` reference is now guarded by a `typeof` check.
30
+ - `client.removeAllListeners()` is called on node close to prevent listener leaks across reconnects.
31
+ - Status badge no longer passes raw Error objects where a string is expected.
32
+ - Removed pointless `async` on the input handler.
33
+
34
+ ### Fixed RTCM decoder node
35
+ - Frames that straddle TCP packet boundaries are now buffered across input events instead of being dropped on a decode error.
36
+ - Added a zero-length / non-positive-length guard to prevent a potential infinite loop.
37
+ - Decode failures now propagate to `node.error` so Catch nodes can react.
38
+
39
+ ### Fixed NMEA decoder node
40
+ - A single chunk containing multiple `\r\n`-delimited NMEA sentences is now decoded sentence by sentence (previously only the first sentence was parsed).
41
+ - Non-string / non-Buffer payloads are routed to the error output instead of crashing.
42
+ - Decode failures now propagate to `node.error` so Catch nodes can react.
43
+
44
+ ### Fixed NTRIP client library wrapper
45
+ - Rejects CR/LF in `mountpoint`, `username`, and `password` to prevent handshake header injection.
46
+ - Fixed `'socket timeouted'` typo in timeout error message.
47
+
48
+ ### Fixed editor UI
49
+ - Password field is now rendered with `type="password"` instead of `type="text"`.
50
+ - `host` and `mountpoint` are validated as non-empty strings in the configuration dialog.
51
+
52
+ ### Other
53
+ - Bumped `engines.node` from `>=7.6.0` to `>=18.0.0` to match the CI matrix and the actual runtime requirements (`AggregateError`, optional chaining, etc.).
54
+
55
+ ## [0.2.4]
56
+ ### Fixed NMEA encoder node
57
+ - Restored `NmeaMessage` instance passthrough (the `instanceof` check was previously inspecting `input.prototype` and always evaluated to false).
58
+ - Replaced unguarded property access with a payload validation guard so missing `nmeaMessage`/`messageType` is routed to the error output instead of throwing.
59
+ - Fixed `ReferenceError` in the catch and invalid-input branches that referenced an out-of-scope `input` variable.
60
+ - Unknown `messageType` values are now routed to the error output instead of silently emitting `nmeaMessage: undefined` on the success output.
61
+ - `case 'Object'` renamed to `case 'OBJECT'` (the preceding `toUpperCase()` made the original case unreachable).
62
+ - Status badge now turns red when a message fails to encode.
63
+ - `messageType` is coerced to a string before `toUpperCase()` to tolerate non-string inputs.
64
+
65
+ ## [0.2.3]
66
+ ### Added NMEA encoder node - [#13](https://github.com/windkh/node-red-contrib-ntrip/issues/13)
67
+
4
68
  ## [0.2.2]
5
69
  ### Added documentation - [#3](https://github.com/windkh/node-red-contrib-ntrip/issues/3)
6
70
 
package/CLAUDE.md ADDED
@@ -0,0 +1,73 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project overview
6
+
7
+ `node-red-contrib-ntrip` is a Node-RED contribution package providing four GNSS-related nodes: an NTRIP client (download/upload), an RTCM decoder, an NMEA decoder, and an NMEA encoder. It is published to npm and consumed by users via Node-RED's palette manager — there is no application entry point of its own. Runtime target is Node.js >= 7.6 (CI matrix: 18.x, 20.x, 22.x).
8
+
9
+ ## Commands
10
+
11
+ This package has no build step. Linting (ESLint flat config) and formatting (Prettier) are wired in:
12
+
13
+ ```bash
14
+ npm test # run all specs (mocha + node-red-node-test-helper)
15
+ npm run lint # eslint, treats warnings as warnings
16
+ npm run lint:fix # eslint --fix
17
+ npm run format # prettier --write .
18
+ npm run format:check # prettier --check . (used in CI)
19
+
20
+ npx mocha test/nmea-decoder.spec.js # run one spec file
21
+ npx mocha test/**/*.spec.js -g 'regression' # run only regression specs
22
+ ```
23
+
24
+ CI runs `npm ci`, `npm run build --if-present`, `npm run lint`, `npm run format:check`, then `npm test` across Node 18/20/22 (see [.github/workflows/node.js.yml](.github/workflows/node.js.yml)).
25
+
26
+ Prettier settings live in [.prettierrc.json](.prettierrc.json) (4-space indent, single quotes, trailing-commas-es5, printWidth 160). [.prettierignore](.prettierignore) skips `examples/` (Node-RED flow files), `ntrip/99-ntrip.html` (Node-RED-specific structural conventions), markdown, and `doc/`. ESLint config is [eslint.config.js](eslint.config.js) (flat config, `@eslint/js` recommended + `eslint-config-prettier`).
27
+
28
+ To exercise changes manually, install this directory into a local Node-RED:
29
+
30
+ ```bash
31
+ cd <node-red userDir, e.g. ~/.node-red>
32
+ npm install <path-to-this-repo>
33
+ ```
34
+
35
+ Then start Node-RED and import a flow from [examples/](examples/) (e.g. `ntripclient.json`, `sapos.json`, `upload.json`, `tcp.json`, `nmea-decode.json`, `nmea-encode.json`) to drive the nodes against a real NTRIP caster.
36
+
37
+ ## Architecture
38
+
39
+ Node-RED contribution packages follow a strict structure that this repo conforms to:
40
+
41
+ - [package.json](package.json) `node-red.nodes` points at a single registration file [ntrip/99-ntrip.js](ntrip/99-ntrip.js).
42
+ - That registration file is the only entry Node-RED loads. It `require`s each node implementation from [ntrip/nodes/](ntrip/nodes/) and calls `RED.nodes.registerType(...)` for each.
43
+ - Each Node-RED node is a pair: a `.js` runtime file in [ntrip/nodes/](ntrip/nodes/), plus the editor UI (HTML + inline `<script>`/`<template>`) co-located in [ntrip/99-ntrip.html](ntrip/99-ntrip.html). All four nodes' editor definitions share this single HTML file.
44
+ - Credentials (NTRIP username/password) are declared in both the registration call ([ntrip/99-ntrip.js:13-16](ntrip/99-ntrip.js#L13-L16)) and the HTML `credentials` block — they must stay in sync, otherwise Node-RED will not persist them.
45
+
46
+ Node responsibilities:
47
+
48
+ - **NtripClient** ([ntrip/nodes/ntrip-client-node.js](ntrip/nodes/ntrip-client-node.js)) — wraps [ntrip/lib/ntrip-client.js](ntrip/lib/ntrip-client.js), which extends the upstream `ntrip-client` package to add an *uploader* variant. The uploader's `_connect()` switches the handshake string by `authmode` (`legacy`, `hybrid`, `ntripv1`, `ntripv2`) — see [ntrip/lib/ntrip-client.js:45-66](ntrip/lib/ntrip-client.js#L45-L66). Special inbound bytes are intercepted and turned into status/error rather than forwarded: `ICY 200 OK` (connected), `ICY 406` (rejected), `SOURCETABLE 200 OK` (mountpoint missing). When `msg.payload` is an `Array`, it is treated as an `[x, y, z]` coordinate triple and forwarded via `client.setXYZ(...)` (used by the Sapos flow to seed a GGA sentence); any other payload is written to the caster. `passthrough` controls whether written data is also re-emitted on the output.
49
+ - **RtcmDecoder** ([ntrip/nodes/rtcm-decoder-node.js](ntrip/nodes/rtcm-decoder-node.js)) — loops `RtcmTransport.decode(buffer)` (from `@gnss/rtcm`), slicing the buffer by the returned `length` until empty, emitting one message per decoded RTCM frame. Two outputs: `[ok, error]`.
50
+ - **NmeaDecoder** ([ntrip/nodes/nmea-decoder-node.js](ntrip/nodes/nmea-decoder-node.js)) — decodes via `NmeaTransport.decode` from `@gnss/nmea`. Accepts either a Buffer or a string, and either `msg.payload` directly or `msg.payload.nmeaMessage`. Two outputs: `[ok, error]`.
51
+ - **NmeaEncoder** ([ntrip/nodes/nmea-encoder-node.js](ntrip/nodes/nmea-encoder-node.js)) — symmetric inverse, with a large `switch (messageType)` that maps NMEA sentence types (`GGA`, `RMC`, `GSV`, ...) to the corresponding `NmeaMessage*.construct(...)` factory before calling `NmeaTransport.encode`. When adding support for a new NMEA sentence type, add both the import at the top and the matching `case` in the switch.
52
+
53
+ ## Tests
54
+
55
+ Specs live in [test/](test/) — one file per node. Each uses `node-red-node-test-helper` to load the registration file [ntrip/99-ntrip.js](ntrip/99-ntrip.js), wires the node under test to two `helper` sink nodes (one per output port), and asserts on `msg.payload` shape.
56
+
57
+ Two patterns to reuse when adding tests:
58
+
59
+ - **`regression:` tag in the test name** — for any spec that locks in a previously-shipped bug. Future maintainers can run `mocha -g regression` to verify the fix is still in place.
60
+ - **`node.error` spy** — replace `n1.error` with a wrapper that flips a boolean, then assert the boolean stayed false. Used to prevent regressing the "decoder floods the Node-RED error log when fed mismatched binary" bug.
61
+
62
+ Known-good test fixtures embedded in the specs:
63
+
64
+ - NMEA: `$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47`, `$GPGLL,4916.45,N,12311.12,W,225444,A,*1D`
65
+ - RTCM: a real 25-byte type 1005 frame as hex `D300133ED7D30202980EDEEF34B4BD62AC0941986F33360B98`
66
+
67
+ NTRIP client integration tests are not yet written; the recommended approach is a fake TCP server via `net.createServer` that returns `"ICY 200 OK\r\n\r\n" + rtcmBytes` to exercise the handshake-state machine.
68
+
69
+ ## Conventions worth knowing
70
+
71
+ - All four nodes follow the same pattern: increment a `messagesReceived`/`invalidMessagesReceived` counter and call `node.status({...})` after each input, set `node.status({})` on `close`. Keep this consistent when adding nodes.
72
+ - The package version printed at load time is read from `package.json` at [ntrip/99-ntrip.js:8-9](ntrip/99-ntrip.js#L8-L9) — bumping `version` in package.json is the only source of truth.
73
+ - Releases are tracked in [CHANGELOG.md](CHANGELOG.md) using "Keep a Changelog" headings; each entry typically references the GitHub issue number that motivated it.
package/README.md CHANGED
@@ -30,6 +30,10 @@ Or run the following command in the root directory of your Node-RED installation
30
30
 
31
31
  npm install node-red-contrib-ntrip --save
32
32
 
33
+ # Requirements
34
+ - Node.js **>= 18.0.0** (uses `AggregateError` and other recent runtime features).
35
+ - Node-RED **>= 0.1.0**.
36
+
33
37
  # Dependencies
34
38
  This package depends on the following libraries
35
39
  - [@gnss/nmea](https://github.com/node-ntrip/nmea)
@@ -93,58 +97,181 @@ You need to provide these additional properties:
93
97
 
94
98
  ### Authentication Modes
95
99
  #### Legacy
96
- This mode initiates the uploading to a mountpoint using plan text.
100
+ This mode initiates the upload to a mountpoint using plain text.
97
101
  ```javascript
98
102
  SOURCE ${mountpoint}\r\n\r\n
99
103
  ```
100
- or with password and unsername
104
+ or with password and username
101
105
  ```javascript
102
106
  SOURCE ${password} /${mountpoint}\r\nSource-Agent: NTRIP ${username}\r\n\r\n
103
107
  ```
104
108
 
105
109
  #### Legacy (Basic Auth)
106
- This hybrid mode initiates the uploading to a mountpoint using plan text and Basic Auth.
110
+ This hybrid mode initiates the upload to a mountpoint using plain text and Basic Auth.
107
111
  ```javascript
108
112
  SOURCE ${mountpoint}\r\nAuthorization: Basic ${authorization}\r\n\r\n
109
113
  ```
110
114
 
111
115
  #### NTRIP V1
112
- This mode initiates the uploading to a mountpoint using HTTP with Basic Auth.
116
+ This mode initiates the upload to a mountpoint using HTTP with Basic Auth.
113
117
  ```javascript
114
118
  POST /${mountpoint} HTTP/1.0\r\nUser-Agent: NTRIP ${userAgent}\r\nAuthorization: Basic ${authorization}\r\nContent-Type: gnss/data\r\n\r\n
115
119
  ```
116
120
 
117
121
  #### NTRIP V2
118
- THis mode is equal to NTRIP V1 with some more data.
122
+ This mode is equal to NTRIP V1 with some more data.
119
123
  ```javascript
120
124
  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
121
125
  ```
122
126
 
123
127
  # RTCM Decoder Node
124
- This node accepts binary RTCM data. The node accepts either a single or multiple RTCN messages.
125
- The converted messages are sent one by one to the output in the following format.
128
+ This node accepts binary RTCM data. A single input message may contain a single
129
+ RTCM frame or several concatenated frames; each decoded frame is emitted as its
130
+ own output message. Bytes left over from a partial frame at the end of an input
131
+ chunk are buffered internally and joined with the next chunk, so frames that
132
+ straddle TCP packet boundaries are reassembled correctly.
133
+
134
+ The node has **two outputs**: decoded frames go to the first output, decode
135
+ failures to the second.
136
+
137
+ Successful output:
138
+ ```javascript
139
+ msg.payload =
140
+ {
141
+ rtcm, // RTCM message number (e.g. 1005, 1077)
142
+ messageType, // a readable name (e.g. "Stationary", "Msm7Gps")
143
+ message, // the decoded message object with all fields
144
+ input, // the raw bytes of this frame
145
+ };
146
+ ```
147
+
148
+ Error output:
126
149
  ```javascript
127
- msg.payload =
150
+ msg.payload =
128
151
  {
129
- rtcm, // RTCm message number
130
- messageType, // a string indicating a readyble RTCM message name
131
- message, // a structure containing all decoded data
132
- input, // the raw input message
152
+ error, // the Error thrown by the decoder
153
+ input, // the buffer at the point of failure
154
+ inputString, // String(input) for logging
133
155
  };
134
156
  ```
135
157
 
136
158
  # NMEA Decoder Node
137
- This node accepts binary NMEA data. The node accepts either a single or multiple NMEA messages.
138
- The converted messages are sent one by one to the output in the following format.
159
+ This node accepts NMEA 0183 sentences as a string or `Buffer`. Multiple
160
+ sentences in a single input (delimited by `\r\n`) are split and decoded
161
+ individually; each decoded sentence is emitted as its own output message.
162
+
163
+ The node has **two outputs**: decoded sentences go to the first, failures to
164
+ the second.
165
+
166
+ Successful output:
167
+ ```javascript
168
+ msg.payload =
169
+ {
170
+ messageType, // sentence type in upper case (e.g. "GGA", "RMC", "GSV")
171
+ nmeaMessage, // the decoded sentence object with all fields
172
+ input, // the raw sentence as it was parsed
173
+ };
174
+ ```
175
+
176
+ Error output:
139
177
  ```javascript
140
- msg.payload =
178
+ msg.payload =
141
179
  {
142
- messageType, // a string indicating a readyble RTCM message name
143
- message, // a structure containing all decoded data
144
- input, // the raw input message
180
+ error, // the Error thrown by the decoder
181
+ input, // the original payload as supplied (Buffer if a Buffer was given)
182
+ inputString, // the utf8 string form of input
145
183
  };
146
184
  ```
147
185
 
186
+ Errors are routed to the second output only — the node does not call
187
+ `node.error()` for per-sentence decode failures, so wiring binary RTCM through
188
+ the NMEA decoder (as the demo flow does) will not flood the Node-RED log.
189
+
190
+ # NMEA Encoder Node
191
+ This node is the inverse of the NMEA Decoder. It encodes a NMEA sentence object
192
+ back to its textual form using `@gnss/nmea`. Useful for round-tripping or for
193
+ synthesising sentences (e.g. building a `GGA` fix to feed into an NTRIP
194
+ uploader).
195
+
196
+ The node has **two outputs**: encoded sentences go to the first, failures to
197
+ the second.
198
+
199
+ Input shape:
200
+ ```javascript
201
+ msg.payload =
202
+ {
203
+ messageType, // "GGA", "RMC", "VTG", ... (case-insensitive)
204
+ nmeaMessage, // either a plain field object matching the sentence schema,
205
+ // or an already-constructed NmeaMessage instance
206
+ };
207
+ ```
208
+
209
+ Supported `messageType` values: `DTM`, `GBS`, `GGA`, `GLL`, `GNS`, `GRS`,
210
+ `GSA`, `GST`, `GSV`, `RMC`, `THS`, `TXT`, `VHW`, `VLW`, `VPW`, `VTG`, `ZDA`,
211
+ plus `OBJECT` for the generic `NmeaMessageUnknown` form.
212
+
213
+ Successful output:
214
+ ```javascript
215
+ msg.payload =
216
+ {
217
+ nmeaMessage, // the encoded NMEA sentence string (with $ prefix and checksum)
218
+ messageType, // echoed back as supplied
219
+ input, // the original input object
220
+ };
221
+ ```
222
+
223
+ Unknown `messageType` values, missing input fields, and encoder exceptions all
224
+ surface on the error output.
225
+
226
+ See also example flow [**NMEA encode flow**](examples/nmea-encode.json).
227
+
228
+ # Notes on usage
229
+
230
+ A few things that are easy to get wrong:
231
+
232
+ - **Two outputs on the decoders and the encoder.** The first output carries
233
+ successful messages, the second carries decode/encode failures. If you only
234
+ wire the first output you will silently drop errors.
235
+ - **NTRIP Client: handshake handling.** On the first reply from a caster the
236
+ node intercepts the `ICY 200 OK`, `ICY 406`, or `SOURCETABLE 200 OK` line and
237
+ uses it to set its status badge (`connecting…` → connected / rejected /
238
+ missing mountpoint). RTCM bytes that happen to share the same TCP segment as
239
+ the `ICY 200 OK` reply are forwarded; later packets are forwarded verbatim.
240
+ - **NTRIP Client: coordinates.** The `X`/`Y`/`Z` form fields are an ECEF
241
+ coordinate triple used to synthesise a `GGA` sentence for the caster. The
242
+ node only sends a `GGA` if the triple is finite **and not all zero** — so
243
+ `(0, 0, 0)` is the sentinel for "no location, do not send GGA". Coordinates
244
+ on a single axis (e.g. `(x, 0, 0)`) are still sent.
245
+ - **NTRIP Client: dynamic location updates.** To stream a moving position into
246
+ the caster, inject `msg.payload = [x, y, z]` (an Array, not a Buffer) into
247
+ the NTRIP Client. Arrays are routed to `client.setXYZ()` and update the
248
+ position used by the periodic `GGA`; non-array payloads are written verbatim
249
+ to the caster.
250
+ - **NTRIP Client: upload pass-through.** With *Pass through data* enabled the
251
+ bytes written to the caster are *also* re-emitted on the node output,
252
+ interleaved with bytes received from the caster. Downstream nodes will see
253
+ both streams on the same wire — distinguish them with a tag in a function
254
+ node if you need to.
255
+ - **NTRIP Client: status badge.** `Rx N Tx M` counts inbound and outbound
256
+ messages separately. Handshake replies do not bump the inbound count.
257
+ - **NTRIP Client: CR/LF in credentials.** `mountpoint`, `username`, and
258
+ `password` are interpolated into the handshake string. CR/LF in these fields
259
+ is rejected at startup to prevent header injection.
260
+ - **NMEA Encoder input shape.** The encoder expects `msg.payload` to be an
261
+ object with `messageType` and `nmeaMessage` properties — not a NMEA string.
262
+ To re-encode the output of the NMEA Decoder you can pipe it straight in;
263
+ to build a sentence from scratch use a function node:
264
+ ```javascript
265
+ msg.payload = {
266
+ messageType: "GGA",
267
+ nmeaMessage: { /* GGA field object */ }
268
+ };
269
+ return msg;
270
+ ```
271
+ - **Plaintext transport.** NTRIP traffic is unencrypted TCP. Credentials in
272
+ upload mode use Basic Auth and are observable on the wire. NTRIP-over-TLS
273
+ is not supported by this package.
274
+
148
275
  # Examples
149
276
  Examples are stored in the examples folder an can be imported from within node-red's sidebar via import.
150
277
 
@@ -0,0 +1,19 @@
1
+ # Architecture documentation
2
+
3
+ Architectural analysis of `node-red-contrib-ntrip`. Start with
4
+ [overview.md](overview.md) for a high-level orientation.
5
+
6
+ | Chapter | Read when |
7
+ |---------|-----------|
8
+ | [Overview](overview.md) | First contact — what this package is and how the pieces fit. |
9
+ | [Structural Design](structural-design.md) | You need to find or change a specific file / module. |
10
+ | [Behavioural Design](behavioural-design.md) | You're changing runtime behaviour — state machines, error paths, lifecycle hooks. |
11
+ | [Architecture Decisions](architecture-decisions.md) | You're about to revisit a load-bearing decision. Individual ADRs live in [adr/](adr/). |
12
+ | [Errors and Weaknesses](errors-and-weaknesses.md) | You're triaging a bug report — check whether it's already known. |
13
+ | [Refactoring Recommendations](refactoring-recommendations.md) | You have spare cycles and want to invest them in code health. |
14
+ | [Future Improvements](future-improvements.md) | You're planning the next minor release. |
15
+ | [Statistics](statistics.md) | You want a quantitative read on size, test coverage, and quality. |
16
+
17
+ This documentation lives alongside the code and is versioned with it. When
18
+ you change behaviour that contradicts a statement here, update the doc in the
19
+ same commit.
@@ -0,0 +1,64 @@
1
+ # ADR-0001: Single registration file for all nodes
2
+
3
+ ## Status
4
+
5
+ Accepted (original design).
6
+
7
+ ## Context
8
+
9
+ Node-RED loads a contribution package via the `node-red.nodes` map in
10
+ `package.json`. That map can point at one or many entry files: each entry file
11
+ is run once at startup and is expected to call `RED.nodes.registerType(...)`
12
+ for the node types it owns.
13
+
14
+ Two viable patterns exist:
15
+
16
+ - **One entry per node.** `package.json` lists four entries; each file
17
+ registers exactly one type. Pro: a node's entire definition is in one place.
18
+ Con: four entries to keep in sync, four loader invocations.
19
+ - **One entry for the whole package.** `package.json` lists one entry; that
20
+ file `require`s each node implementation and registers them all. Pro:
21
+ centralised; package version logging happens once.
22
+
23
+ ## Decision
24
+
25
+ Use a single registration entry file: [ntrip/99-ntrip.js](../../../ntrip/99-ntrip.js).
26
+ The `99-` prefix is a convention seen across published `node-red-contrib-*`
27
+ packages — it sorts late alphabetically, making the file easy to find.
28
+
29
+ Each node implementation lives under [ntrip/nodes/](../../../ntrip/nodes/) and
30
+ exports a factory function that the registration file imports.
31
+
32
+ The four node types' editor UIs share one HTML file
33
+ ([ntrip/99-ntrip.html](../../../ntrip/99-ntrip.html)) — Node-RED's loader picks
34
+ up the `.html` next to the registered `.js`.
35
+
36
+ ## Consequences
37
+
38
+ **Positive**
39
+
40
+ - One place to read the full inventory of nodes.
41
+ - Package version logging happens exactly once at load time (read from
42
+ `package.json`).
43
+ - New nodes are added by a one-line `require` + one-line `registerType` in the
44
+ entry file, plus a new file under `nodes/` and a new `<script>` block in the
45
+ HTML.
46
+
47
+ **Negative**
48
+
49
+ - The HTML file is large (~470 lines) because it must hold form templates and
50
+ help text for all four nodes. Adding a fifth node means appending another
51
+ ~100 lines to the same file.
52
+ - A typo in `registerType` names (e.g. `NtripClinet` instead of `NtripClient`)
53
+ is silently fatal — the JS side and HTML side must agree exactly.
54
+
55
+ **Trade-offs**
56
+
57
+ - Choosing the single-entry pattern prioritises *package coherence* over
58
+ *per-node isolation*. If the package ever grows to a dozen+ nodes, splitting
59
+ would become attractive.
60
+
61
+ ## Related
62
+
63
+ - Code: [ntrip/99-ntrip.js](../../../ntrip/99-ntrip.js), [ntrip/99-ntrip.html](../../../ntrip/99-ntrip.html)
64
+ - Configured via: [package.json](../../../package.json) `node-red.nodes`