@bldgblocks/node-red-contrib-control 0.1.33 → 0.1.36
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/nodes/accumulate-block.html +18 -8
- package/nodes/accumulate-block.js +39 -44
- package/nodes/add-block.html +1 -1
- package/nodes/add-block.js +18 -11
- package/nodes/alarm-collector.html +260 -0
- package/nodes/alarm-collector.js +292 -0
- package/nodes/alarm-config.html +129 -0
- package/nodes/alarm-config.js +126 -0
- package/nodes/alarm-service.html +96 -0
- package/nodes/alarm-service.js +142 -0
- package/nodes/analog-switch-block.js +25 -36
- package/nodes/and-block.js +44 -15
- package/nodes/average-block.js +46 -41
- package/nodes/boolean-switch-block.js +10 -28
- package/nodes/boolean-to-number-block.html +18 -5
- package/nodes/boolean-to-number-block.js +24 -16
- package/nodes/cache-block.js +24 -37
- package/nodes/call-status-block.html +91 -32
- package/nodes/call-status-block.js +398 -115
- package/nodes/changeover-block.html +5 -0
- package/nodes/changeover-block.js +167 -162
- package/nodes/comment-block.html +1 -1
- package/nodes/comment-block.js +14 -9
- package/nodes/compare-block.html +14 -4
- package/nodes/compare-block.js +23 -18
- package/nodes/contextual-label-block.html +5 -0
- package/nodes/contextual-label-block.js +6 -16
- package/nodes/convert-block.html +25 -39
- package/nodes/convert-block.js +31 -16
- package/nodes/count-block.html +11 -5
- package/nodes/count-block.js +34 -32
- package/nodes/delay-block.js +58 -53
- package/nodes/divide-block.js +43 -45
- package/nodes/edge-block.html +17 -10
- package/nodes/edge-block.js +43 -41
- package/nodes/enum-switch-block.js +6 -6
- package/nodes/frequency-block.html +6 -1
- package/nodes/frequency-block.js +64 -74
- package/nodes/global-getter.html +51 -15
- package/nodes/global-getter.js +74 -67
- package/nodes/global-setter.html +1 -1
- package/nodes/global-setter.js +168 -188
- package/nodes/history-buffer.html +96 -0
- package/nodes/history-buffer.js +461 -0
- package/nodes/history-collector.html +29 -1
- package/nodes/history-collector.js +37 -16
- package/nodes/history-config.html +13 -1
- package/nodes/history-service.html +84 -0
- package/nodes/history-service.js +52 -0
- package/nodes/hysteresis-block.html +5 -0
- package/nodes/hysteresis-block.js +13 -16
- package/nodes/interpolate-block.html +20 -2
- package/nodes/interpolate-block.js +39 -50
- package/nodes/join.html +78 -0
- package/nodes/join.js +78 -0
- package/nodes/latch-block.js +12 -14
- package/nodes/load-sequence-block.js +102 -110
- package/nodes/max-block.js +26 -26
- package/nodes/memory-block.js +57 -58
- package/nodes/min-block.js +26 -25
- package/nodes/minmax-block.js +35 -34
- package/nodes/modulo-block.js +45 -43
- package/nodes/multiply-block.js +43 -41
- package/nodes/negate-block.html +17 -7
- package/nodes/negate-block.js +25 -19
- package/nodes/network-point-read.html +128 -0
- package/nodes/network-point-read.js +230 -0
- package/nodes/{network-register.html → network-point-register.html} +94 -7
- package/nodes/network-point-register.js +126 -0
- package/nodes/network-point-write.html +149 -0
- package/nodes/network-point-write.js +222 -0
- package/nodes/network-service-bridge.html +131 -0
- package/nodes/network-service-bridge.js +376 -0
- package/nodes/network-service-read.html +81 -0
- package/nodes/network-service-read.js +58 -0
- package/nodes/{network-point-registry.html → network-service-registry.html} +19 -4
- package/nodes/{network-point-registry.js → network-service-registry.js} +7 -2
- package/nodes/network-service-write.html +89 -0
- package/nodes/network-service-write.js +83 -0
- package/nodes/nullify-block.js +13 -15
- package/nodes/on-change-block.html +17 -9
- package/nodes/on-change-block.js +49 -46
- package/nodes/oneshot-block.html +13 -10
- package/nodes/oneshot-block.js +57 -75
- package/nodes/or-block.js +44 -15
- package/nodes/pid-block.html +54 -4
- package/nodes/pid-block.js +459 -248
- package/nodes/priority-block.js +24 -35
- package/nodes/rate-limit-block.js +70 -72
- package/nodes/rate-of-change-block.html +33 -14
- package/nodes/rate-of-change-block.js +74 -62
- package/nodes/round-block.html +14 -9
- package/nodes/round-block.js +32 -25
- package/nodes/saw-tooth-wave-block.js +49 -76
- package/nodes/scale-range-block.html +12 -6
- package/nodes/scale-range-block.js +46 -39
- package/nodes/sine-wave-block.js +49 -57
- package/nodes/string-builder-block.js +6 -6
- package/nodes/subtract-block.js +38 -34
- package/nodes/thermistor-block.js +44 -44
- package/nodes/tick-tock-block.js +32 -32
- package/nodes/time-sequence-block.js +30 -42
- package/nodes/triangle-wave-block.js +49 -69
- package/nodes/tstat-block.js +34 -44
- package/nodes/units-block.html +90 -69
- package/nodes/units-block.js +22 -30
- package/nodes/utils.js +275 -3
- package/package.json +14 -6
- package/nodes/network-read.html +0 -56
- package/nodes/network-read.js +0 -59
- package/nodes/network-register.js +0 -161
- package/nodes/network-write.html +0 -64
- package/nodes/network-write.js +0 -126
package/nodes/modulo-block.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function ModuloBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
const node = this;
|
|
5
7
|
|
|
6
8
|
// Initialize runtime state
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
};
|
|
9
|
+
// Initialize state
|
|
10
|
+
node.name = config.name;
|
|
11
|
+
node.slots = parseInt(config.slots);
|
|
12
|
+
node.inputs = Array(parseInt(config.slots) || 2).fill(1);
|
|
13
|
+
node.lastResult = null;
|
|
13
14
|
|
|
14
15
|
// Validate initial config
|
|
15
|
-
if (isNaN(node.
|
|
16
|
-
node.
|
|
17
|
-
node.
|
|
18
|
-
|
|
16
|
+
if (isNaN(node.slots) || node.slots < 1) {
|
|
17
|
+
node.slots = 2;
|
|
18
|
+
node.inputs = Array(2).fill(1);
|
|
19
|
+
utils.setStatusError(node, "invalid slots, using 2");
|
|
19
20
|
} else {
|
|
20
|
-
|
|
21
|
+
utils.setStatusOK(node, `name: ${node.name || "modulo"}, slots: ${node.slots}`);
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
node.on("input", function(msg, send, done) {
|
|
@@ -25,35 +26,36 @@ module.exports = function(RED) {
|
|
|
25
26
|
|
|
26
27
|
// Guard against invalid message
|
|
27
28
|
if (!msg) {
|
|
28
|
-
|
|
29
|
+
utils.setStatusError(node, "invalid message");
|
|
29
30
|
if (done) done();
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
// Check for missing context or payload
|
|
34
35
|
if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
|
|
35
|
-
|
|
36
|
+
utils.setStatusError(node, "missing context");
|
|
36
37
|
if (done) done();
|
|
37
38
|
return;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
if (!msg.hasOwnProperty("payload")) {
|
|
41
|
-
|
|
42
|
+
utils.setStatusError(node, "missing payload");
|
|
42
43
|
if (done) done();
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// Handle configuration messages
|
|
47
48
|
if (msg.context === "reset") {
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
const boolVal = utils.validateBoolean(msg.payload);
|
|
50
|
+
if (!boolVal.valid) {
|
|
51
|
+
utils.setStatusError(node, boolVal.error);
|
|
50
52
|
if (done) done();
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
53
|
-
if (
|
|
54
|
-
node.
|
|
55
|
-
node.
|
|
56
|
-
|
|
55
|
+
if (boolVal.value === true) {
|
|
56
|
+
node.inputs = Array(node.slots).fill(1);
|
|
57
|
+
node.lastResult = null;
|
|
58
|
+
utils.setStatusOK(node, "state reset");
|
|
57
59
|
if (done) done();
|
|
58
60
|
return;
|
|
59
61
|
}
|
|
@@ -64,56 +66,56 @@ module.exports = function(RED) {
|
|
|
64
66
|
if (msg.context === "slots") {
|
|
65
67
|
const newSlots = parseInt(msg.payload);
|
|
66
68
|
if (isNaN(newSlots) || newSlots < 1) {
|
|
67
|
-
|
|
69
|
+
utils.setStatusError(node, "invalid slots");
|
|
68
70
|
if (done) done();
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
|
-
node.
|
|
72
|
-
node.
|
|
73
|
-
node.
|
|
74
|
-
|
|
73
|
+
node.slots = newSlots;
|
|
74
|
+
node.inputs = Array(newSlots).fill(1);
|
|
75
|
+
node.lastResult = null;
|
|
76
|
+
utils.setStatusOK(node, `slots: ${newSlots}`);
|
|
75
77
|
if (done) done();
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
if (msg.context.startsWith("in")) {
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
82
|
+
const slotVal = utils.validateSlotIndex(msg.context, node.slots);
|
|
83
|
+
if (!slotVal.valid) {
|
|
84
|
+
utils.setStatusError(node, slotVal.error);
|
|
83
85
|
if (done) done();
|
|
84
86
|
return;
|
|
85
87
|
}
|
|
88
|
+
const slotIndex = slotVal.index - 1;
|
|
86
89
|
const newValue = parseFloat(msg.payload);
|
|
87
90
|
if (isNaN(newValue) || !isFinite(newValue)) {
|
|
88
|
-
|
|
91
|
+
utils.setStatusError(node, "invalid input");
|
|
89
92
|
if (done) done();
|
|
90
93
|
return;
|
|
91
94
|
}
|
|
92
95
|
if (slotIndex > 0 && newValue === 0) {
|
|
93
|
-
|
|
96
|
+
utils.setStatusError(node, "modulo by zero");
|
|
94
97
|
if (done) done();
|
|
95
98
|
return;
|
|
96
99
|
}
|
|
97
|
-
node.
|
|
100
|
+
node.inputs[slotIndex] = newValue;
|
|
98
101
|
|
|
99
102
|
// Calculate modulo
|
|
100
|
-
const result = node.
|
|
101
|
-
const isUnchanged = result === node.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (!isUnchanged) {
|
|
109
|
-
node.runtime.lastResult = result;
|
|
110
|
-
send({ payload: result });
|
|
103
|
+
const result = node.inputs.reduce((acc, val, idx) => idx === 0 ? val : acc % val, node.inputs[0]);
|
|
104
|
+
const isUnchanged = result === node.lastResult;
|
|
105
|
+
const statusText = `in: ${msg.context}=${newValue.toFixed(2)}, out: ${result.toFixed(2)}`;
|
|
106
|
+
if (isUnchanged) {
|
|
107
|
+
utils.setStatusUnchanged(node, statusText);
|
|
108
|
+
} else {
|
|
109
|
+
utils.setStatusChanged(node, statusText);
|
|
111
110
|
}
|
|
111
|
+
|
|
112
|
+
node.lastResult = result;
|
|
113
|
+
send({ payload: result });
|
|
112
114
|
if (done) done();
|
|
113
115
|
return;
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
|
|
118
|
+
utils.setStatusWarn(node, "unknown context");
|
|
117
119
|
if (done) done("Unknown context");
|
|
118
120
|
});
|
|
119
121
|
|
package/nodes/multiply-block.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function MultiplyBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
|
|
5
7
|
const node = this;
|
|
6
8
|
|
|
7
9
|
// Initialize runtime state
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
10
|
+
// Initialize state
|
|
11
|
+
node.name = config.name;
|
|
12
|
+
node.slots = parseInt(config.slots);
|
|
13
|
+
node.inputs = Array(parseInt(config.slots) || 2).fill(1);
|
|
14
|
+
node.lastResult = null;
|
|
14
15
|
|
|
15
16
|
// Validate initial config
|
|
16
|
-
if (isNaN(node.
|
|
17
|
-
node.
|
|
18
|
-
node.
|
|
19
|
-
|
|
17
|
+
if (isNaN(node.slots) || node.slots < 1) {
|
|
18
|
+
node.slots = 2;
|
|
19
|
+
node.inputs = Array(2).fill(1);
|
|
20
|
+
utils.setStatusError(node, "invalid slots, using 2");
|
|
20
21
|
} else {
|
|
21
|
-
|
|
22
|
+
utils.setStatusOK(node, `name: ${node.name}, slots: ${node.slots}`);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
node.on("input", function(msg, send, done) {
|
|
@@ -26,81 +27,82 @@ module.exports = function(RED) {
|
|
|
26
27
|
|
|
27
28
|
// Guard against invalid msg
|
|
28
29
|
if (!msg) {
|
|
29
|
-
|
|
30
|
+
utils.setStatusError(node, "invalid message");
|
|
30
31
|
if (done) done();
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
// Check for missing context or payload
|
|
35
36
|
if (!msg.hasOwnProperty("context")) {
|
|
36
|
-
|
|
37
|
+
utils.setStatusError(node, "missing context");
|
|
37
38
|
if (done) done();
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
if (!msg.hasOwnProperty("payload")) {
|
|
42
|
-
|
|
43
|
+
utils.setStatusError(node, "missing payload");
|
|
43
44
|
if (done) done();
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
// Handle configuration messages
|
|
48
49
|
if (msg.context === "reset") {
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const boolVal = utils.validateBoolean(msg.payload);
|
|
51
|
+
if (!boolVal.valid) {
|
|
52
|
+
utils.setStatusError(node, boolVal.error);
|
|
51
53
|
if (done) done();
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
|
-
if (
|
|
55
|
-
node.
|
|
56
|
-
node.
|
|
57
|
-
|
|
56
|
+
if (boolVal.value === true) {
|
|
57
|
+
node.inputs = Array(node.slots).fill(1);
|
|
58
|
+
node.lastResult = null;
|
|
59
|
+
utils.setStatusOK(node, "state reset");
|
|
58
60
|
if (done) done();
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
63
|
} else if (msg.context === "slots") {
|
|
62
64
|
let newSlots = parseInt(msg.payload);
|
|
63
65
|
if (isNaN(newSlots) || newSlots < 1) {
|
|
64
|
-
|
|
66
|
+
utils.setStatusError(node, "invalid slots");
|
|
65
67
|
if (done) done();
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
68
|
-
node.
|
|
69
|
-
node.
|
|
70
|
-
node.
|
|
71
|
-
|
|
70
|
+
node.slots = newSlots;
|
|
71
|
+
node.inputs = Array(newSlots).fill(1);
|
|
72
|
+
node.lastResult = null;
|
|
73
|
+
utils.setStatusOK(node, `slots: ${node.slots}`);
|
|
72
74
|
if (done) done();
|
|
73
75
|
return;
|
|
74
76
|
} else if (msg.context.startsWith("in")) {
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
77
|
+
const slotVal = utils.validateSlotIndex(msg.context, node.slots);
|
|
78
|
+
if (!slotVal.valid) {
|
|
79
|
+
utils.setStatusError(node, slotVal.error);
|
|
78
80
|
if (done) done();
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
83
|
+
const slotIndex = slotVal.index - 1;
|
|
81
84
|
let newValue = parseFloat(msg.payload);
|
|
82
85
|
if (isNaN(newValue)) {
|
|
83
|
-
|
|
86
|
+
utils.setStatusError(node, "invalid input");
|
|
84
87
|
if (done) done();
|
|
85
88
|
return;
|
|
86
89
|
}
|
|
87
|
-
node.
|
|
90
|
+
node.inputs[slotIndex] = newValue;
|
|
88
91
|
// Calculate product
|
|
89
|
-
const product = node.
|
|
90
|
-
const isUnchanged = product === node.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!isUnchanged) {
|
|
97
|
-
node.runtime.lastResult = product;
|
|
98
|
-
send({ payload: product });
|
|
92
|
+
const product = node.inputs.reduce((acc, val) => acc * val, 1);
|
|
93
|
+
const isUnchanged = product === node.lastResult;
|
|
94
|
+
const statusText = `in: ${msg.context}=${newValue.toFixed(2)}, out: ${product.toFixed(2)}`;
|
|
95
|
+
if (isUnchanged) {
|
|
96
|
+
utils.setStatusUnchanged(node, statusText);
|
|
97
|
+
} else {
|
|
98
|
+
utils.setStatusChanged(node, statusText);
|
|
99
99
|
}
|
|
100
|
+
node.lastResult = product;
|
|
101
|
+
send({ payload: product });
|
|
100
102
|
if (done) done();
|
|
101
103
|
return;
|
|
102
104
|
} else {
|
|
103
|
-
|
|
105
|
+
utils.setStatusWarn(node, "unknown context");
|
|
104
106
|
if (done) done();
|
|
105
107
|
return;
|
|
106
108
|
}
|
package/nodes/negate-block.html
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
<label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
|
|
4
4
|
<input type="text" id="node-input-name" placeholder="Name">
|
|
5
5
|
</div>
|
|
6
|
+
<div class="form-row">
|
|
7
|
+
<label for="node-input-inputProperty" title="Message property to read input from"><i class="fa fa-folder-open"></i> Input Property</label>
|
|
8
|
+
<input type="text" id="node-input-inputProperty" placeholder="payload">
|
|
9
|
+
</div>
|
|
6
10
|
</script>
|
|
7
11
|
|
|
8
12
|
<script type="text/javascript">
|
|
@@ -10,7 +14,8 @@
|
|
|
10
14
|
category: "bldgblocks control",
|
|
11
15
|
color: "#301934",
|
|
12
16
|
defaults: {
|
|
13
|
-
name: { value: "" }
|
|
17
|
+
name: { value: "" },
|
|
18
|
+
inputProperty: { value: "payload" }
|
|
14
19
|
},
|
|
15
20
|
inputs: 1,
|
|
16
21
|
outputs: 1,
|
|
@@ -28,17 +33,22 @@
|
|
|
28
33
|
Negates a number or boolean input value.
|
|
29
34
|
|
|
30
35
|
### Inputs
|
|
31
|
-
:
|
|
36
|
+
: input-property (number | boolean) : Value to negate (number or boolean), read from the configured Input Property.
|
|
32
37
|
|
|
33
38
|
### Outputs
|
|
34
39
|
: payload (number | boolean) : Negated value (-number or !boolean).
|
|
35
40
|
|
|
36
|
-
###
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- For booleans, outputs the inverse (e.g., true becomes false).
|
|
41
|
+
### Properties
|
|
42
|
+
: name (string) : Display name in editor.
|
|
43
|
+
: inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
### Details
|
|
46
|
+
Negates a number or boolean value in a passthrough manner, updating the original message.
|
|
47
|
+
- Reads input from the message property specified in the **Input Property** field (default: `msg.payload`)
|
|
48
|
+
- For numbers, outputs the negative (e.g., 5 becomes -5)
|
|
49
|
+
- For booleans, outputs the inverse (e.g., true becomes false)
|
|
50
|
+
- Output is always written to `msg.payload`
|
|
51
|
+
- Preserves other message properties (e.g., `msg.topic`, `msg.context`)
|
|
42
52
|
|
|
43
53
|
### Status
|
|
44
54
|
- Green (dot): Configuration update
|
package/nodes/negate-block.js
CHANGED
|
@@ -1,39 +1,45 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function NegateBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
|
|
5
7
|
const node = this;
|
|
6
8
|
|
|
7
|
-
// Initialize
|
|
8
|
-
node.
|
|
9
|
-
|
|
10
|
-
lastOutput: null
|
|
11
|
-
};
|
|
9
|
+
// Initialize state
|
|
10
|
+
node.inputProperty = config.inputProperty || "payload";
|
|
11
|
+
node.lastOutput = null;
|
|
12
12
|
|
|
13
13
|
node.on("input", function(msg, send, done) {
|
|
14
14
|
send = send || function() { node.send.apply(node, arguments); };
|
|
15
15
|
|
|
16
16
|
// Guard against invalid msg
|
|
17
17
|
if (!msg) {
|
|
18
|
-
|
|
18
|
+
utils.setStatusError(node, "missing message");
|
|
19
19
|
if (done) done();
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Get input value from the specified property
|
|
24
|
+
let inputValue;
|
|
25
|
+
try {
|
|
26
|
+
inputValue = RED.util.getMessageProperty(msg, node.inputProperty);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
inputValue = undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (inputValue === undefined) {
|
|
32
|
+
utils.setStatusError(node, "missing or invalid input property");
|
|
26
33
|
if (done) done();
|
|
27
34
|
return;
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
const inputValue = msg.payload;
|
|
31
37
|
let outputValue;
|
|
32
38
|
let statusText;
|
|
33
39
|
|
|
34
40
|
if (typeof inputValue === "number") {
|
|
35
41
|
if (isNaN(inputValue)) {
|
|
36
|
-
|
|
42
|
+
utils.setStatusError(node, "invalid input: NaN");
|
|
37
43
|
if (done) done();
|
|
38
44
|
return;
|
|
39
45
|
}
|
|
@@ -43,20 +49,20 @@ module.exports = function(RED) {
|
|
|
43
49
|
outputValue = !inputValue;
|
|
44
50
|
statusText = `in: ${inputValue}, out: ${outputValue}`;
|
|
45
51
|
} else {
|
|
46
|
-
|
|
52
|
+
utils.setStatusError(node, "Unsupported type");
|
|
47
53
|
if (done) done();
|
|
48
54
|
return;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
// Check for unchanged output
|
|
52
|
-
const isUnchanged = outputValue === node.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
+
const isUnchanged = outputValue === node.lastOutput;
|
|
59
|
+
if (isUnchanged) {
|
|
60
|
+
utils.setStatusUnchanged(node, statusText);
|
|
61
|
+
} else {
|
|
62
|
+
utils.setStatusChanged(node, statusText);
|
|
63
|
+
}
|
|
58
64
|
|
|
59
|
-
node.
|
|
65
|
+
node.lastOutput = outputValue;
|
|
60
66
|
msg.payload = outputValue;
|
|
61
67
|
send(msg);
|
|
62
68
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="network-point-read">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-input-name" title="Display name shown on canvas"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<input type="text" id="node-input-name" placeholder="Point #101">
|
|
5
|
+
</div>
|
|
6
|
+
<div class="form-row">
|
|
7
|
+
<label for="node-input-pointId" title="Point ID to cache (e.g., 301)"><i class="fa fa-hashtag"></i> Point ID</label>
|
|
8
|
+
<input type="number" id="node-input-pointId" placeholder="301" min="0" required>
|
|
9
|
+
</div>
|
|
10
|
+
<hr style="margin: 10px 0;">
|
|
11
|
+
<div class="form-row">
|
|
12
|
+
<label for="node-input-outputProperty" title="Set output message property"><i class="fa fa-arrow-right"></i> Target</label>
|
|
13
|
+
<input type="text" id="node-input-outputProperty" placeholder="payload">
|
|
14
|
+
</div>
|
|
15
|
+
<div class="form-row">
|
|
16
|
+
<label for="node-input-bridgeNodeId" title="Select which network bridge to query"><i class="fa fa-link"></i> Network Bridge</label>
|
|
17
|
+
<input type="text" id="node-input-bridgeNodeId" style="width: calc(70% - 45px);">
|
|
18
|
+
<button id="node-config-find-bridge" class="editor-button" style="margin-left: 5px; width: 40px;" title="Find Bridge Node">
|
|
19
|
+
<i class="fa fa-search"></i>
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<script type="text/javascript">
|
|
25
|
+
RED.nodes.registerType("network-point-read", {
|
|
26
|
+
category: "bldgblocks network",
|
|
27
|
+
color: "#3090C7",
|
|
28
|
+
defaults: {
|
|
29
|
+
name: { value: "" },
|
|
30
|
+
outputProperty: { value: "payload" },
|
|
31
|
+
pointId: {
|
|
32
|
+
value: 0,
|
|
33
|
+
required: true,
|
|
34
|
+
validate: function(v) {
|
|
35
|
+
return !isNaN(parseInt(v)) && parseInt(v) >= 0;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
bridgeNodeId: {
|
|
39
|
+
value: "",
|
|
40
|
+
required: true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
inputs: 1,
|
|
44
|
+
outputs: 1,
|
|
45
|
+
inputLabels: ["trigger (from inject/timer)"],
|
|
46
|
+
outputLabels: ["value update / response"],
|
|
47
|
+
icon: "font-awesome/fa-refresh",
|
|
48
|
+
paletteLabel: "network point read",
|
|
49
|
+
label: function() {
|
|
50
|
+
const id = this.pointId ? ` #${this.pointId}` : " (unconfigured)";
|
|
51
|
+
const bridgeName = this.bridgeNodeId ? ` → ${RED.nodes.node(this.bridgeNodeId)?.name || "bridge"}` : "";
|
|
52
|
+
return this.name ? `${this.name}${id}${bridgeName}` : `network point read${id}${bridgeName}`;
|
|
53
|
+
},
|
|
54
|
+
oneditprepare: function() {
|
|
55
|
+
const node = this;
|
|
56
|
+
|
|
57
|
+
let candidateNodes = [];
|
|
58
|
+
RED.nodes.eachNode(function(n) {
|
|
59
|
+
if (n.type === 'network-service-bridge') {
|
|
60
|
+
candidateNodes.push({
|
|
61
|
+
value: n.id,
|
|
62
|
+
label: n.name || "(unnamed bridge)"
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
candidateNodes.sort((a, b) => a.label.localeCompare(b.label));
|
|
68
|
+
|
|
69
|
+
$("#node-input-bridgeNodeId").typedInput({
|
|
70
|
+
types: [{ value: "bridge", options: candidateNodes }]
|
|
71
|
+
});
|
|
72
|
+
$("#node-input-outputProperty").typedInput({
|
|
73
|
+
types: ['msg']
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
// Find button
|
|
78
|
+
$("#node-config-find-bridge").on("click", function() {
|
|
79
|
+
if (node.bridgeNodeId) {
|
|
80
|
+
RED.viewport.reveal(node.bridgeNodeId);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
oneditsave: function() {
|
|
85
|
+
// Optional: log configuration
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<script type="text/markdown" data-help-name="network-point-read">
|
|
91
|
+
Caches a remote point value and serves requests immediately (non-blocking).
|
|
92
|
+
|
|
93
|
+
Ideal for flows that need current point values without waiting for network latency.
|
|
94
|
+
|
|
95
|
+
### Inputs
|
|
96
|
+
: action (string) : Command - `getPoint` to request cached value, `resetCache` to clear cache.
|
|
97
|
+
: pointId (number) : The remote point ID to query (e.g., 301).
|
|
98
|
+
: payload (any) : Trigger message from inject or timer node.
|
|
99
|
+
|
|
100
|
+
### Outputs
|
|
101
|
+
: payload (number | string | boolean) : Cached point value on update.
|
|
102
|
+
: pointId (number) : Echo of queried point ID.
|
|
103
|
+
: cached (boolean) : true if value served from cache, false if fresh from network.
|
|
104
|
+
: age (number) : Cache age in milliseconds.
|
|
105
|
+
: action (string) : getPointResponse or pointUpdate.
|
|
106
|
+
|
|
107
|
+
### Details
|
|
108
|
+
Caching Architecture:
|
|
109
|
+
1. External trigger - Inject node controls polling timing
|
|
110
|
+
2. Event-based communication - Sends request to selected bridge
|
|
111
|
+
3. Immediate serving - Returns cached value on getPoint requests
|
|
112
|
+
4. Non-blocking - Logic flows never wait for network roundtrip
|
|
113
|
+
|
|
114
|
+
Use With:
|
|
115
|
+
- network-service-bridge - Routes requests via WebSocket
|
|
116
|
+
- History nodes - Feed cached poi
|
|
117
|
+
|
|
118
|
+
### Status
|
|
119
|
+
- Green (dot): Configuration update
|
|
120
|
+
- Blue (dot): State changed
|
|
121
|
+
- Blue (ring): State unchanged
|
|
122
|
+
- Red (ring): Error
|
|
123
|
+
- Yellow (ring): Warning
|
|
124
|
+
|
|
125
|
+
### References
|
|
126
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
127
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
128
|
+
</script>
|