node-red-contrib-ntrip 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +5 -3
  3. package/examples/watchdog.json +1 -1
  4. package/ntrip/lib/ntrip-client.js +70 -72
  5. package/ntrip/nodes/nmea-decoder-node.js +36 -38
  6. package/ntrip/nodes/nmea-encoder-node.js +97 -97
  7. package/ntrip/nodes/ntrip-client-node.js +153 -148
  8. package/ntrip/nodes/rtcm-decoder-node.js +50 -47
  9. package/ntrip/nodes/rtcm-encoder-node.js +33 -32
  10. package/package.json +16 -6
  11. package/.github/dependabot.yml +0 -23
  12. package/.github/workflows/node.js.yml +0 -32
  13. package/.github/workflows/release.yml +0 -65
  14. package/.prettierignore +0 -20
  15. package/.prettierrc.json +0 -9
  16. package/CLAUDE.md +0 -73
  17. package/doc/architecture/README.md +0 -19
  18. package/doc/architecture/adr/0001-single-registration-file.md +0 -64
  19. package/doc/architecture/adr/0002-ntrip-uploader-extension.md +0 -71
  20. package/doc/architecture/adr/0003-two-output-decoder-design.md +0 -73
  21. package/doc/architecture/adr/0004-stateful-handshake-interception.md +0 -74
  22. package/doc/architecture/adr/0005-rtcm-partial-frame-buffer.md +0 -87
  23. package/doc/architecture/adr/0006-nmea-multi-sentence-split.md +0 -66
  24. package/doc/architecture/adr/0007-mocha-test-helper-stack.md +0 -67
  25. package/doc/architecture/adr/0008-coordinate-gating-sentinel.md +0 -76
  26. package/doc/architecture/adr/README.md +0 -18
  27. package/doc/architecture/architecture-decisions.md +0 -60
  28. package/doc/architecture/behavioural-design.md +0 -226
  29. package/doc/architecture/errors-and-weaknesses.md +0 -71
  30. package/doc/architecture/future-improvements.md +0 -130
  31. package/doc/architecture/overview.md +0 -77
  32. package/doc/architecture/refactoring-recommendations.md +0 -114
  33. package/doc/architecture/statistics.md +0 -118
  34. package/doc/architecture/structural-design.md +0 -141
  35. package/eslint.config.js +0 -36
  36. package/test/nmea-decoder.spec.js +0 -146
  37. package/test/nmea-encoder.spec.js +0 -104
  38. package/test/rtcm-decoder.spec.js +0 -116
  39. package/test/rtcm-encoder.spec.js +0 -144
@@ -1,104 +0,0 @@
1
- 'use strict';
2
-
3
- const helper = require('node-red-node-test-helper');
4
- const { expect } = require('chai');
5
- const { NmeaTransport } = require('@gnss/nmea');
6
- const ntripModule = require('../ntrip/99-ntrip.js');
7
-
8
- helper.init(require.resolve('node-red'));
9
-
10
- const GGA = '$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47';
11
-
12
- function flow() {
13
- return [
14
- { id: 'n1', type: 'NmeaEncoder', wires: [['ok'], ['err']] },
15
- { id: 'ok', type: 'helper' },
16
- { id: 'err', type: 'helper' },
17
- ];
18
- }
19
-
20
- describe('NmeaEncoder', function () {
21
- before(() => helper.startServer());
22
- after(() => helper.stopServer());
23
- afterEach(() => helper.unload());
24
-
25
- it('encodes a pre-constructed NmeaMessage instance (instanceof passthrough)', function () {
26
- return new Promise((resolve, reject) => {
27
- helper.load(ntripModule, flow(), () => {
28
- const n1 = helper.getNode('n1');
29
- const ok = helper.getNode('ok');
30
- const instance = NmeaTransport.decode(GGA);
31
- ok.on('input', (msg) => {
32
- try {
33
- expect(msg.payload.nmeaMessage).to.be.a('string');
34
- expect(msg.payload.nmeaMessage).to.match(/^\$.+\*[0-9A-Fa-f]{2}\r?\n?$/);
35
- expect(msg.payload.nmeaMessage).to.include('GGA');
36
- resolve();
37
- } catch (e) {
38
- reject(e);
39
- }
40
- });
41
- // Even with a bogus messageType, an instance must take precedence and
42
- // flow straight through to NmeaTransport.encode without re-construction.
43
- n1.receive({ payload: { messageType: 'NONSENSE', nmeaMessage: instance } });
44
- });
45
- });
46
- });
47
-
48
- it('routes unknown messageType to the error output (no silent success)', function () {
49
- return new Promise((resolve, reject) => {
50
- helper.load(ntripModule, flow(), () => {
51
- const n1 = helper.getNode('n1');
52
- const ok = helper.getNode('ok');
53
- const err = helper.getNode('err');
54
- ok.on('input', () => reject(new Error('unexpected success output')));
55
- err.on('input', (msg) => {
56
- try {
57
- expect(msg.payload.error).to.exist;
58
- expect(String(msg.payload.error).toLowerCase()).to.include('unsupported');
59
- resolve();
60
- } catch (e) {
61
- reject(e);
62
- }
63
- });
64
- n1.receive({ payload: { messageType: 'XYZ', nmeaMessage: { foo: 1 } } });
65
- });
66
- });
67
- });
68
-
69
- it('routes missing nmeaMessage to the error output without crashing', function () {
70
- return new Promise((resolve, reject) => {
71
- helper.load(ntripModule, flow(), () => {
72
- const n1 = helper.getNode('n1');
73
- const err = helper.getNode('err');
74
- err.on('input', (msg) => {
75
- try {
76
- expect(msg.payload.error).to.exist;
77
- resolve();
78
- } catch (e) {
79
- reject(e);
80
- }
81
- });
82
- n1.receive({ payload: { messageType: 'GGA' } });
83
- });
84
- });
85
- });
86
-
87
- it('routes empty payload to the error output without crashing', function () {
88
- return new Promise((resolve, reject) => {
89
- helper.load(ntripModule, flow(), () => {
90
- const n1 = helper.getNode('n1');
91
- const err = helper.getNode('err');
92
- err.on('input', (msg) => {
93
- try {
94
- expect(msg.payload.error).to.exist;
95
- resolve();
96
- } catch (e) {
97
- reject(e);
98
- }
99
- });
100
- n1.receive({ payload: {} });
101
- });
102
- });
103
- });
104
- });
@@ -1,116 +0,0 @@
1
- 'use strict';
2
-
3
- const helper = require('node-red-node-test-helper');
4
- const { expect } = require('chai');
5
- const ntripModule = require('../ntrip/99-ntrip.js');
6
-
7
- helper.init(require.resolve('node-red'));
8
-
9
- // A real RTCM 3 message type 1005 (Stationary RTK Reference Station ARP).
10
- // 25 bytes total: 3-byte header + 19-byte payload + 3-byte CRC.
11
- const RTCM_1005 = Buffer.from('D300133ED7D30202980EDEEF34B4BD62AC0941986F33360B98', 'hex');
12
-
13
- function flow() {
14
- return [
15
- { id: 'n1', type: 'RtcmDecoder', wires: [['ok'], ['err']] },
16
- { id: 'ok', type: 'helper' },
17
- { id: 'err', type: 'helper' },
18
- ];
19
- }
20
-
21
- describe('RtcmDecoder', function () {
22
- before(() => helper.startServer());
23
- after(() => helper.stopServer());
24
- afterEach(() => helper.unload());
25
-
26
- it('decodes a valid RTCM 1005 frame on the success output', function () {
27
- return new Promise((resolve, reject) => {
28
- helper.load(ntripModule, flow(), () => {
29
- const n1 = helper.getNode('n1');
30
- const ok = helper.getNode('ok');
31
- ok.on('input', (msg) => {
32
- try {
33
- expect(msg.payload.rtcm).to.equal(1005);
34
- expect(msg.payload.messageType).to.be.a('string');
35
- expect(msg.payload.message).to.be.an('object');
36
- expect(Buffer.isBuffer(msg.payload.input)).to.equal(true);
37
- resolve();
38
- } catch (e) {
39
- reject(e);
40
- }
41
- });
42
- n1.receive({ payload: RTCM_1005 });
43
- });
44
- });
45
- });
46
-
47
- it('decodes two concatenated frames as two separate output messages', function () {
48
- return new Promise((resolve) => {
49
- helper.load(ntripModule, flow(), () => {
50
- const n1 = helper.getNode('n1');
51
- const ok = helper.getNode('ok');
52
- let count = 0;
53
- ok.on('input', () => {
54
- count++;
55
- if (count === 2) resolve();
56
- });
57
- n1.receive({ payload: Buffer.concat([RTCM_1005, RTCM_1005]) });
58
- });
59
- });
60
- });
61
-
62
- it('reassembles a frame split across two input events (carry-over buffer)', function () {
63
- return new Promise((resolve, reject) => {
64
- helper.load(ntripModule, flow(), () => {
65
- const n1 = helper.getNode('n1');
66
- const ok = helper.getNode('ok');
67
- let received = false;
68
- ok.on('input', (msg) => {
69
- if (received) return;
70
- received = true;
71
- try {
72
- expect(msg.payload.rtcm).to.equal(1005);
73
- resolve();
74
- } catch (e) {
75
- reject(e);
76
- }
77
- });
78
- const half = Math.floor(RTCM_1005.length / 2);
79
- n1.receive({ payload: RTCM_1005.slice(0, half) });
80
- setImmediate(() => n1.receive({ payload: RTCM_1005.slice(half) }));
81
- });
82
- });
83
- });
84
-
85
- it('does not call node.error on decode failure (regression: log flood)', function () {
86
- return new Promise((resolve, reject) => {
87
- helper.load(ntripModule, flow(), () => {
88
- const n1 = helper.getNode('n1');
89
- const err = helper.getNode('err');
90
- let errorCalled = false;
91
- const originalError = n1.error.bind(n1);
92
- n1.error = function (...args) {
93
- errorCalled = true;
94
- return originalError(...args);
95
- };
96
- // A buffer of garbage larger than the pending-buffer cap triggers the
97
- // error-output path. node.error must NOT be called for that.
98
- const big = Buffer.alloc(70000, 0xff);
99
- let errEmitted = false;
100
- err.on('input', () => {
101
- errEmitted = true;
102
- });
103
- n1.receive({ payload: big });
104
- setTimeout(() => {
105
- try {
106
- expect(errEmitted).to.equal(true);
107
- expect(errorCalled).to.equal(false);
108
- resolve();
109
- } catch (e) {
110
- reject(e);
111
- }
112
- }, 200);
113
- });
114
- });
115
- });
116
- });
@@ -1,144 +0,0 @@
1
- 'use strict';
2
-
3
- const helper = require('node-red-node-test-helper');
4
- const { expect } = require('chai');
5
- const { RtcmTransport, RtcmMessage } = require('@gnss/rtcm');
6
- const ntripModule = require('../ntrip/99-ntrip.js');
7
-
8
- helper.init(require.resolve('node-red'));
9
-
10
- // A real RTCM 3 message type 1005 (Stationary RTK Reference Station ARP).
11
- const RTCM_1005 = Buffer.from('D300133ED7D30202980EDEEF34B4BD62AC0941986F33360B98', 'hex');
12
-
13
- function flow() {
14
- return [
15
- { id: 'n1', type: 'RtcmEncoder', wires: [['ok'], ['err']] },
16
- { id: 'ok', type: 'helper' },
17
- { id: 'err', type: 'helper' },
18
- ];
19
- }
20
-
21
- describe('RtcmEncoder', function () {
22
- before(() => helper.startServer());
23
- after(() => helper.stopServer());
24
- afterEach(() => helper.unload());
25
-
26
- it('encodes an RtcmMessage instance round-tripped from the decoder', function () {
27
- return new Promise((resolve, reject) => {
28
- helper.load(ntripModule, flow(), () => {
29
- const n1 = helper.getNode('n1');
30
- const ok = helper.getNode('ok');
31
- const [instance] = RtcmTransport.decode(RTCM_1005);
32
- ok.on('input', (msg) => {
33
- try {
34
- expect(msg.payload.rtcm).to.equal(1005);
35
- expect(msg.payload.messageType).to.be.a('string');
36
- expect(Buffer.isBuffer(msg.payload.rtcmMessage)).to.equal(true);
37
- expect(msg.payload.rtcmMessage).to.deep.equal(RTCM_1005);
38
- resolve();
39
- } catch (e) {
40
- reject(e);
41
- }
42
- });
43
- n1.receive({ payload: instance });
44
- });
45
- });
46
- });
47
-
48
- it('accepts the decoder output shape (msg.payload.message)', function () {
49
- return new Promise((resolve, reject) => {
50
- helper.load(ntripModule, flow(), () => {
51
- const n1 = helper.getNode('n1');
52
- const ok = helper.getNode('ok');
53
- const [instance, length] = RtcmTransport.decode(RTCM_1005);
54
- ok.on('input', (msg) => {
55
- try {
56
- expect(msg.payload.rtcm).to.equal(1005);
57
- expect(Buffer.isBuffer(msg.payload.rtcmMessage)).to.equal(true);
58
- expect(msg.payload.rtcmMessage.length).to.equal(length);
59
- resolve();
60
- } catch (e) {
61
- reject(e);
62
- }
63
- });
64
- // Mimic the RtcmDecoder's success payload shape.
65
- n1.receive({
66
- payload: {
67
- rtcm: instance.messageType,
68
- messageType: 'StationArp',
69
- message: instance,
70
- input: RTCM_1005,
71
- },
72
- });
73
- });
74
- });
75
- });
76
-
77
- it('routes a non-RtcmMessage payload to the error output', function () {
78
- return new Promise((resolve, reject) => {
79
- helper.load(ntripModule, flow(), () => {
80
- const n1 = helper.getNode('n1');
81
- const ok = helper.getNode('ok');
82
- const err = helper.getNode('err');
83
- ok.on('input', () => reject(new Error('unexpected success output')));
84
- err.on('input', (msg) => {
85
- try {
86
- expect(msg.payload.error).to.be.a('string');
87
- expect(msg.payload.error.toLowerCase()).to.include('invalid');
88
- resolve();
89
- } catch (e) {
90
- reject(e);
91
- }
92
- });
93
- n1.receive({ payload: { foo: 'bar' } });
94
- });
95
- });
96
- });
97
-
98
- it('routes empty payload to the error output', function () {
99
- return new Promise((resolve, reject) => {
100
- helper.load(ntripModule, flow(), () => {
101
- const n1 = helper.getNode('n1');
102
- const err = helper.getNode('err');
103
- err.on('input', (msg) => {
104
- try {
105
- expect(msg.payload.error).to.exist;
106
- resolve();
107
- } catch (e) {
108
- reject(e);
109
- }
110
- });
111
- n1.receive({ payload: null });
112
- });
113
- });
114
- });
115
-
116
- it('does not call node.error on decode-shape failure (regression: log flood)', function () {
117
- return new Promise((resolve, reject) => {
118
- helper.load(ntripModule, flow(), () => {
119
- const n1 = helper.getNode('n1');
120
- let errorCalled = false;
121
- const originalError = n1.error.bind(n1);
122
- n1.error = function (...args) {
123
- errorCalled = true;
124
- return originalError(...args);
125
- };
126
- n1.receive({ payload: 'not-an-rtcm-message' });
127
- setTimeout(() => {
128
- try {
129
- expect(errorCalled).to.equal(false);
130
- resolve();
131
- } catch (e) {
132
- reject(e);
133
- }
134
- }, 50);
135
- });
136
- });
137
- });
138
-
139
- // Sanity check: RtcmMessage is exported and the test fixture decodes to an instance.
140
- it('test fixture sanity: decoded 1005 frame is an RtcmMessage instance', function () {
141
- const [instance] = RtcmTransport.decode(RTCM_1005);
142
- expect(instance).to.be.instanceOf(RtcmMessage);
143
- });
144
- });