node-red-contrib-ntrip 0.2.3 → 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.
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/node.js.yml +22 -21
- package/.github/workflows/release.yml +65 -0
- package/.prettierignore +20 -0
- package/.prettierrc.json +9 -0
- package/CHANGELOG.md +61 -0
- package/CLAUDE.md +73 -0
- package/README.md +145 -18
- package/doc/architecture/README.md +19 -0
- package/doc/architecture/adr/0001-single-registration-file.md +64 -0
- package/doc/architecture/adr/0002-ntrip-uploader-extension.md +71 -0
- package/doc/architecture/adr/0003-two-output-decoder-design.md +73 -0
- package/doc/architecture/adr/0004-stateful-handshake-interception.md +74 -0
- package/doc/architecture/adr/0005-rtcm-partial-frame-buffer.md +87 -0
- package/doc/architecture/adr/0006-nmea-multi-sentence-split.md +66 -0
- package/doc/architecture/adr/0007-mocha-test-helper-stack.md +67 -0
- package/doc/architecture/adr/0008-coordinate-gating-sentinel.md +76 -0
- package/doc/architecture/adr/README.md +18 -0
- package/doc/architecture/architecture-decisions.md +60 -0
- package/doc/architecture/behavioural-design.md +226 -0
- package/doc/architecture/errors-and-weaknesses.md +71 -0
- package/doc/architecture/future-improvements.md +130 -0
- package/doc/architecture/overview.md +77 -0
- package/doc/architecture/refactoring-recommendations.md +114 -0
- package/doc/architecture/statistics.md +118 -0
- package/doc/architecture/structural-design.md +141 -0
- package/eslint.config.js +36 -0
- package/ntrip/99-ntrip.html +207 -31
- package/ntrip/99-ntrip.js +12 -12
- package/ntrip/lib/ntrip-client.js +23 -18
- package/ntrip/nodes/nmea-decoder-node.js +77 -34
- package/ntrip/nodes/nmea-encoder-node.js +68 -47
- package/ntrip/nodes/ntrip-client-node.js +143 -123
- package/ntrip/nodes/rtcm-decoder-node.js +65 -47
- package/package.json +20 -2
- package/test/nmea-decoder.spec.js +146 -0
- package/test/nmea-encoder.spec.js +104 -0
- package/test/rtcm-decoder.spec.js +116 -0
- 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
push:
|
|
8
|
+
branches: ['main']
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: ['main']
|
|
11
11
|
|
|
12
12
|
jobs:
|
|
13
|
-
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
14
15
|
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
package/.prettierignore
ADDED
|
@@ -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/
|
package/.prettierrc.json
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
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
|
+
|
|
4
65
|
## [0.2.3]
|
|
5
66
|
### Added NMEA encoder node - [#13](https://github.com/windkh/node-red-contrib-ntrip/issues/13)
|
|
6
67
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
125
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
138
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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`
|