node-red-contrib-3dm-space 1.1.0 → 2.1.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-join.html +80 -0
- package/3dm-cloud-join.js +155 -0
- package/node-red-contrib-3dm-space-2.0.0.tgz +0 -0
- package/package.json +3 -2
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('3dm-cloud-join', {
|
|
3
|
+
category: '3DM.space',
|
|
4
|
+
color: '#6D6D6D',
|
|
5
|
+
defaults: {
|
|
6
|
+
timeout: {
|
|
7
|
+
value: 5,
|
|
8
|
+
required: true,
|
|
9
|
+
validate: RED.validators.number()
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
inputs: 1,
|
|
13
|
+
outputs: 1,
|
|
14
|
+
icon: 'font-awesome/fa-compress',
|
|
15
|
+
paletteLabel: '3DM Join',
|
|
16
|
+
label: function () {
|
|
17
|
+
return '3DM Join';
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script type="text/html" data-template-name="3dm-cloud-join">
|
|
23
|
+
<div class="form-row">
|
|
24
|
+
<label for="node-input-timeout">
|
|
25
|
+
<i class="fa fa-clock-o"></i> Timeout
|
|
26
|
+
</label>
|
|
27
|
+
<input
|
|
28
|
+
type="number"
|
|
29
|
+
id="node-input-timeout"
|
|
30
|
+
min="1"
|
|
31
|
+
step="1"
|
|
32
|
+
placeholder="5"
|
|
33
|
+
style="width: 120px;"
|
|
34
|
+
>
|
|
35
|
+
<span style="margin-left: 6px;">seconds</span>
|
|
36
|
+
</div>
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<script type="text/html" data-help-name="3dm-cloud-join">
|
|
40
|
+
<p>
|
|
41
|
+
The <b>3DM Join</b> node collects JSON object payloads, merges all keys together,
|
|
42
|
+
then sends one combined payload after the configured timeout.
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<h3>Input</h3>
|
|
46
|
+
<p>
|
|
47
|
+
The node expects <code>msg.payload</code> to be a JSON object.
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<pre>{
|
|
51
|
+
"tank_level": 65
|
|
52
|
+
}</pre>
|
|
53
|
+
|
|
54
|
+
<h3>Output</h3>
|
|
55
|
+
<p>
|
|
56
|
+
When the timeout expires, the node outputs one message containing all collected keys.
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<pre>{
|
|
60
|
+
"tank_level": 65,
|
|
61
|
+
"pump_run": true,
|
|
62
|
+
"dc_voltage": 24.4
|
|
63
|
+
}</pre>
|
|
64
|
+
|
|
65
|
+
<h3>Timeout</h3>
|
|
66
|
+
<p>
|
|
67
|
+
The timeout starts from the first valid message received. The timer does not restart
|
|
68
|
+
when more messages arrive.
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
<h3>Duplicate Keys</h3>
|
|
72
|
+
<p>
|
|
73
|
+
If the same key arrives more than once during the collection window, the latest value is used.
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<h3>Invalid Payloads</h3>
|
|
77
|
+
<p>
|
|
78
|
+
Payloads that are not JSON objects are ignored.
|
|
79
|
+
</p>
|
|
80
|
+
</script>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
return value &&
|
|
6
|
+
typeof value === "object" &&
|
|
7
|
+
!Array.isArray(value) &&
|
|
8
|
+
!Buffer.isBuffer(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function cleanTimeoutSeconds(value) {
|
|
12
|
+
var seconds = Number(value);
|
|
13
|
+
|
|
14
|
+
if (!isFinite(seconds) || seconds < 1) {
|
|
15
|
+
seconds = 5;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
seconds = Math.floor(seconds);
|
|
19
|
+
|
|
20
|
+
if (seconds > 86400) {
|
|
21
|
+
seconds = 86400;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return seconds;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function clonePayloadObject(obj) {
|
|
28
|
+
var out = {};
|
|
29
|
+
|
|
30
|
+
Object.keys(obj).forEach(function (key) {
|
|
31
|
+
out[key] = obj[key];
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ThreeDMCloudJoinNode(config) {
|
|
38
|
+
RED.nodes.createNode(this, config);
|
|
39
|
+
|
|
40
|
+
var node = this;
|
|
41
|
+
|
|
42
|
+
node.timeoutSeconds = cleanTimeoutSeconds(config.timeout);
|
|
43
|
+
node.timeoutMs = node.timeoutSeconds * 1000;
|
|
44
|
+
|
|
45
|
+
node.timer = null;
|
|
46
|
+
node.buffer = {};
|
|
47
|
+
node.messageCount = 0;
|
|
48
|
+
node.firstMsg = null;
|
|
49
|
+
|
|
50
|
+
function setStatus(fill, shape, text) {
|
|
51
|
+
node.status({
|
|
52
|
+
fill: fill,
|
|
53
|
+
shape: shape,
|
|
54
|
+
text: text
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function clearTimer() {
|
|
59
|
+
if (node.timer) {
|
|
60
|
+
clearTimeout(node.timer);
|
|
61
|
+
node.timer = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resetBatch() {
|
|
66
|
+
clearTimer();
|
|
67
|
+
node.buffer = {};
|
|
68
|
+
node.messageCount = 0;
|
|
69
|
+
node.firstMsg = null;
|
|
70
|
+
setStatus("grey", "ring", "waiting");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function flushBatch() {
|
|
74
|
+
clearTimer();
|
|
75
|
+
|
|
76
|
+
if (node.messageCount < 1) {
|
|
77
|
+
resetBatch();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var outMsg = node.firstMsg || {};
|
|
82
|
+
outMsg.payload = clonePayloadObject(node.buffer);
|
|
83
|
+
|
|
84
|
+
node.send(outMsg);
|
|
85
|
+
|
|
86
|
+
setStatus(
|
|
87
|
+
"green",
|
|
88
|
+
"dot",
|
|
89
|
+
"sent " + Object.keys(node.buffer).length + " keys"
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
node.buffer = {};
|
|
93
|
+
node.messageCount = 0;
|
|
94
|
+
node.firstMsg = null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function startBatch(msg) {
|
|
98
|
+
node.buffer = {};
|
|
99
|
+
node.messageCount = 0;
|
|
100
|
+
node.firstMsg = RED.util.cloneMessage(msg);
|
|
101
|
+
|
|
102
|
+
node.timer = setTimeout(function () {
|
|
103
|
+
flushBatch();
|
|
104
|
+
}, node.timeoutMs);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
node.on("input", function (msg, send, done) {
|
|
108
|
+
var payload = msg.payload;
|
|
109
|
+
|
|
110
|
+
if (!isPlainObject(payload)) {
|
|
111
|
+
setStatus("red", "ring", "bad payload");
|
|
112
|
+
if (done) {
|
|
113
|
+
done();
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!node.timer) {
|
|
119
|
+
startBatch(msg);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
Object.keys(payload).forEach(function (key) {
|
|
123
|
+
node.buffer[key] = payload[key];
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
node.messageCount += 1;
|
|
127
|
+
|
|
128
|
+
setStatus(
|
|
129
|
+
"blue",
|
|
130
|
+
"dot",
|
|
131
|
+
"collecting " + Object.keys(node.buffer).length + " keys"
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (done) {
|
|
135
|
+
done();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
node.on("close", function (removed, done) {
|
|
140
|
+
if (typeof removed === "function") {
|
|
141
|
+
done = removed;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
resetBatch();
|
|
145
|
+
|
|
146
|
+
if (done) {
|
|
147
|
+
done();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
resetBatch();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
RED.nodes.registerType("3dm-cloud-join", ThreeDMCloudJoinNode);
|
|
155
|
+
};
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-3dm-space",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Node-RED nodes for 3DM.space SCADA Cloud telemetry, attributes, and local config storage",
|
|
5
5
|
"main": "3dm-cloud-config.js",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"3dm-cloud-config": "3dm-cloud-config.js",
|
|
22
22
|
"3dm-cloud-out": "3dm-cloud-out.js",
|
|
23
23
|
"3dm-cloud-in": "3dm-cloud-in.js",
|
|
24
|
-
"3dm-config-store": "3dm-config-store.js"
|
|
24
|
+
"3dm-config-store": "3dm-config-store.js",
|
|
25
|
+
"3dm-cloud-join": "3dm-cloud-join.js"
|
|
25
26
|
}
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|