node-red-contrib-3dm-space 2.2.0 → 2.3.0

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/3dm-cloud-in.js CHANGED
@@ -1,5 +1,7 @@
1
1
  module.exports = function (RED) {
2
- "use strict";
2
+ "use strict";
3
+
4
+ var roundPayload = require("./payload-precision");
3
5
 
4
6
  /*
5
7
  * Hidden cloud subscribe settings.
@@ -53,7 +55,7 @@ module.exports = function (RED) {
53
55
  return;
54
56
  }
55
57
 
56
- var data = parsePayload(payload);
58
+ var data = roundPayload(parsePayload(payload));
57
59
 
58
60
  if (node.keyFilter) {
59
61
  if (typeof data !== "object" || data === null || Array.isArray(data)) {
package/3dm-cloud-join.js CHANGED
@@ -1,6 +1,8 @@
1
1
  module.exports = function (RED) {
2
2
  "use strict";
3
3
 
4
+ var roundPayload = require("./payload-precision");
5
+
4
6
  function isPlainObject(value) {
5
7
  return value &&
6
8
  typeof value === "object" &&
@@ -79,7 +81,7 @@ module.exports = function (RED) {
79
81
  }
80
82
 
81
83
  var outMsg = node.firstMsg || {};
82
- outMsg.payload = clonePayloadObject(node.buffer);
84
+ outMsg.payload = roundPayload(clonePayloadObject(node.buffer));
83
85
 
84
86
  node.send(outMsg);
85
87
 
@@ -7,7 +7,8 @@
7
7
  server: { value: "", type: "3dm-cloud-config", required: true }
8
8
  },
9
9
  inputs: 1,
10
- outputs: 1,
10
+ outputs: 2,
11
+ outputLabels: ["telemetry result", "cloud online"],
11
12
  icon: "font-awesome/fa-cloud-upload",
12
13
  label: function () {
13
14
  return this.name || "3DM telemetry out";
@@ -36,6 +37,8 @@
36
37
  <p>Sends telemetry to 3DM.space SCADA Cloud.</p>
37
38
 
38
39
  <p>Input must be a JSON object.</p>
40
+ <p>Output 1: the existing telemetry result message.</p>
41
+ <p>Output 2: connection status with <code>msg.topic = "3dm.connection"</code> and a boolean <code>msg.payload</code>: <code>true</code> for online, <code>false</code> for offline. Emitted once after deployment and on each connection state change, without requiring telemetry input. A missing login configuration reports false.</p>
39
42
 
40
43
  <p>Example:</p>
41
44
 
package/3dm-cloud-out.js CHANGED
@@ -1,5 +1,7 @@
1
1
  module.exports = function (RED) {
2
- "use strict";
2
+ "use strict";
3
+
4
+ var roundPayload = require("./payload-precision");
3
5
 
4
6
  var fs = require("fs");
5
7
  var path = require("path");
@@ -85,6 +87,32 @@ module.exports = function (RED) {
85
87
  node.name = config.name;
86
88
  node.server = RED.nodes.getNode(config.server);
87
89
 
90
+ var lastConnectionState;
91
+ var connectionOutputClosed = false;
92
+
93
+ function sendConnectionState(connected) {
94
+ connected = Boolean(connected);
95
+ if (connectionOutputClosed || connected === lastConnectionState) {
96
+ return;
97
+ }
98
+ lastConnectionState = connected;
99
+ node.send([null, { topic: "3dm.connection", payload: connected }]);
100
+ }
101
+
102
+ // Defer the initial message until Node-RED has connected the flow wires.
103
+ var connectionStartupTimer = setTimeout(function () {
104
+ connectionStartupTimer = null;
105
+ sendConnectionState(node.server && node.server.connected);
106
+ }, 0);
107
+
108
+ node.on("close", function () {
109
+ connectionOutputClosed = true;
110
+ if (connectionStartupTimer) {
111
+ clearTimeout(connectionStartupTimer);
112
+ connectionStartupTimer = null;
113
+ }
114
+ });
115
+
88
116
  var queueFile = path.join(
89
117
  QUEUE_DIR,
90
118
  QUEUE_FILE_PREFIX + "-" + node.id + ".sqlite"
@@ -234,7 +262,7 @@ module.exports = function (RED) {
234
262
 
235
263
  return {
236
264
  ts: ts,
237
- values: payload
265
+ values: roundPayload(payload)
238
266
  };
239
267
  }
240
268
 
@@ -253,6 +281,8 @@ module.exports = function (RED) {
253
281
  return;
254
282
  }
255
283
 
284
+ packet = Object.assign({}, packet, { values: roundPayload(packet.values) });
285
+
256
286
  node.server.publish(
257
287
  TELEMETRY_TOPIC,
258
288
  packet,
@@ -283,7 +313,16 @@ module.exports = function (RED) {
283
313
  }
284
314
 
285
315
  function sendResult(msg, send, done) {
286
- send(msg);
316
+ if (typeof msg.payload === "string") {
317
+ try {
318
+ msg.payload = JSON.stringify(roundPayload(JSON.parse(msg.payload)));
319
+ } catch (err) {
320
+ // Preserve non-JSON text on rejected messages.
321
+ }
322
+ } else {
323
+ msg.payload = roundPayload(msg.payload);
324
+ }
325
+ send([msg, null]);
287
326
 
288
327
  if (done) {
289
328
  done();
@@ -592,6 +631,7 @@ module.exports = function (RED) {
592
631
  }
593
632
 
594
633
  var stateHandler = function (connected) {
634
+ sendConnectionState(connected);
595
635
  if (connected) {
596
636
  updateStatus("connected");
597
637
  maybeStartBackhaul();
@@ -1,5 +1,7 @@
1
1
  module.exports = function (RED) {
2
- "use strict";
2
+ "use strict";
3
+
4
+ var roundPayload = require("./payload-precision");
3
5
 
4
6
  var fs = require("fs");
5
7
  var path = require("path");
@@ -694,9 +696,9 @@ module.exports = function (RED) {
694
696
  topic: "configStore",
695
697
  event: eventName || "configStore",
696
698
  payload: {
697
- configStore: latestStore
699
+ configStore: roundPayload(latestStore)
698
700
  },
699
- configStore: latestStore
701
+ configStore: roundPayload(latestStore)
700
702
  };
701
703
 
702
704
  node.send(outputs);
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## 2.3.0 — 2026-10-18
4
+
5
+ - Add a second 3DM Out output for boolean cloud connection status: true online, false offline, on deployment and state changes.
6
+ - Preserve existing telemetry results on output 1, queue behavior, rate limiting, and five-decimal payload precision.
7
+ - Replace GPL declarations with Smithtek proprietary licence terms and include LICENSE in the release.
8
+ - Preserve all 2.2.1 documentation and assets; extend the npm file list to include all Markdown documents.
9
+
10
+
11
+ ## 2.2.1 — 2026-10-01
12
+
13
+ - Round numeric output payload values to a maximum of five decimal places, including nested objects and arrays.
14
+ - Apply consistent precision to cloud telemetry, queue replay, local telemetry results, incoming attributes, joined messages, and config snapshots.
15
+ - Preserve numeric types, integers, text, stored configuration precision, and internal control comparisons.
16
+ - Include the shared precision helper in the npm package and exclude old release archives.
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Smithtek Proprietary Software Licence
2
+
3
+ Copyright (c) 2026 Smithtek. All rights reserved.
4
+
5
+ This software, including its source code, documentation, and accompanying
6
+ Smithtek materials, is proprietary to Smithtek. It is not licensed under the
7
+ GPL or any other open-source licence.
8
+
9
+ Possession, download, installation, or public availability of this package does
10
+ not grant a licence. Use is permitted only under a separate written agreement
11
+ or express written permission from Smithtek, and only within its scope.
12
+
13
+ Without that permission, no person may use, copy, modify, distribute,
14
+ sublicense, sell, or create derivative works of this software, except to the
15
+ extent applicable law requires otherwise. Copyright and proprietary notices
16
+ must be retained in any authorised copies.
17
+
18
+ Third-party dependencies and materials remain subject to their respective
19
+ licences. These terms do not replace or restrict rights granted by those
20
+ third-party licences.
21
+
22
+ To the extent permitted by law, this software is provided AS IS, without
23
+ warranties of any kind. Smithtek shall not be liable for loss or damage arising
24
+ from use of the software. A separate written agreement with Smithtek takes
25
+ precedence over these terms where they conflict.
package/README.md CHANGED
@@ -23,6 +23,13 @@ The package is intended for Smithtek PassPort gateway projects using 3DM.space,
23
23
 
24
24
  ---
25
25
 
26
+
27
+ ## Numeric output precision
28
+
29
+ From version 2.2.1, numeric output payload values are rounded to a maximum of five decimal places. This applies recursively to nested objects and arrays in telemetry, incoming attributes, joined payloads, and config snapshots. For example, `12.12345678` becomes `12.12346`.
30
+
31
+ Values remain numbers; trailing zeros are not added. Integers, strings (including numeric strings), booleans, and null are unchanged. JSON telemetry strings are parsed and their numeric values are rounded before publishing; the local result payload remains a JSON string. Live and queued telemetry use the same precision, including older packets replayed from the queue. Stored configuration and internal control comparisons retain their original precision.
32
+
26
33
  ## Requirements
27
34
 
28
35
  To use these nodes, you need:
@@ -53,7 +60,7 @@ The credentials are copied from 3DM.space into Node-RED in this order:
53
60
 
54
61
  ## Nodes Included
55
62
 
56
- This package includes four nodes:
63
+ This package includes five nodes:
57
64
 
58
65
  - **3DM Cloud Login**
59
66
  - **3DM Out**
@@ -178,6 +185,15 @@ If the cloud connection is unavailable, valid data is stored locally and sent la
178
185
 
179
186
  ---
180
187
 
188
+ ## 3DM Out Outputs
189
+
190
+ From version 2.3.0, 3DM Out has two output pins:
191
+
192
+ 1. **Telemetry result**: the existing result message, including sent, queued, rate-limit, and error fields.
193
+ 2. **Cloud online**: a message with boolean `msg.payload`: `true` when the shared MQTT connection to 3DM.space is online, or `false` when offline. The topic is `3dm.connection`.
194
+
195
+ The second output emits once after deployment and whenever the connection state changes. It works without incoming telemetry. Repeated notifications of the same state do not produce duplicate messages. A missing cloud login configuration reports `false`. This reports MQTT connection status; it does not confirm that an individual telemetry packet was delivered or stored. Existing wires on output 1 keep their telemetry behavior. Connect output 2 to a Switch node or other flow logic to react to online/offline changes.
196
+
181
197
  ## 3DM Out Input Data Type
182
198
 
183
199
  The 3DM Out node expects `msg.payload` to be a JSON object.
@@ -2606,4 +2622,6 @@ Smithtek
2606
2622
 
2607
2623
  # License
2608
2624
 
2609
- GPL-3.0-or-later
2625
+ Smithtek proprietary software. Copyright (c) 2026 Smithtek. All rights reserved.
2626
+
2627
+ This package is not offered under an open-source licence. Use requires permission from Smithtek. See [LICENSE](LICENSE) for the proprietary terms. Third-party dependencies retain their own licences.
package/package.json CHANGED
@@ -1,34 +1,44 @@
1
- {
2
- "name": "node-red-contrib-3dm-space",
3
- "version": "2.2.0",
4
- "description": "Node-RED nodes for 3DM.space SCADA Cloud telemetry, attributes, and local config storage",
5
- "main": "3dm-cloud-config.js",
6
- "license": "GPL-3.0-or-later",
7
- "keywords": [
8
- "node-red",
9
- "node-red-contrib",
10
- "3dm",
11
- "3dm.space",
12
- "smithtek",
13
- "scada",
14
- "telemetry",
15
- "store-and-forward",
16
- "config-store",
17
- "scheduler"
18
- ],
19
- "node-red": {
20
- "nodes": {
21
- "3dm-cloud-config": "3dm-cloud-config.js",
22
- "3dm-cloud-out": "3dm-cloud-out.js",
23
- "3dm-cloud-in": "3dm-cloud-in.js",
24
- "3dm-config-store": "3dm-config-store.js",
25
- "3dm-cloud-join": "3dm-cloud-join.js"
26
- }
27
- },
28
- "dependencies": {
29
- "mqtt": "~4.3.7"
30
- },
31
- "optionalDependencies": {
32
- "node-persistent-queue": "^0.3.1"
33
- }
34
- }
1
+ {
2
+ "name": "node-red-contrib-3dm-space",
3
+ "version": "2.3.0",
4
+ "description": "Node-RED nodes for 3DM.space SCADA Cloud telemetry, attributes, and local config storage",
5
+ "main": "3dm-cloud-config.js",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "keywords": [
8
+ "node-red",
9
+ "node-red-contrib",
10
+ "3dm",
11
+ "3dm.space",
12
+ "smithtek",
13
+ "scada",
14
+ "telemetry",
15
+ "store-and-forward",
16
+ "config-store",
17
+ "scheduler"
18
+ ],
19
+ "node-red": {
20
+ "nodes": {
21
+ "3dm-cloud-config": "3dm-cloud-config.js",
22
+ "3dm-cloud-out": "3dm-cloud-out.js",
23
+ "3dm-cloud-in": "3dm-cloud-in.js",
24
+ "3dm-config-store": "3dm-config-store.js",
25
+ "3dm-cloud-join": "3dm-cloud-join.js"
26
+ }
27
+ },
28
+ "dependencies": {
29
+ "mqtt": "~4.3.7"
30
+ },
31
+ "optionalDependencies": {
32
+ "node-persistent-queue": "^0.3.1"
33
+ },
34
+ "files": [
35
+ "3dm-*.js",
36
+ "3dm-*.html",
37
+ "payload-precision.js",
38
+ "icons/",
39
+ "icon.png",
40
+ "*.md",
41
+ "LICENSE"
42
+ ],
43
+ "author": "Smithtek"
44
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ // Round numeric payload values without changing strings or the source objects.
4
+ function roundPayload(value, seen) {
5
+ if (typeof value === "number") {
6
+ return Number.isFinite(value) && !Number.isInteger(value)
7
+ ? Number(value.toFixed(5))
8
+ : value;
9
+ }
10
+
11
+ if (!value || typeof value !== "object") {
12
+ return value;
13
+ }
14
+
15
+ var prototype = Object.getPrototypeOf(value);
16
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
17
+ return value;
18
+ }
19
+
20
+ seen = seen || new WeakMap();
21
+ if (seen.has(value)) {
22
+ return seen.get(value);
23
+ }
24
+
25
+ var output = Array.isArray(value) ? new Array(value.length) : Object.create(prototype);
26
+ seen.set(value, output);
27
+ Object.keys(value).forEach(function (key) {
28
+ Object.defineProperty(output, key, {
29
+ value: roundPayload(value[key], seen),
30
+ enumerable: true,
31
+ writable: true,
32
+ configurable: true
33
+ });
34
+ });
35
+ return output;
36
+ }
37
+
38
+ module.exports = roundPayload;