@bldgblocks/node-red-contrib-control 0.1.4
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/README.md +43 -0
- package/nodes/accumulate-block.html +71 -0
- package/nodes/accumulate-block.js +104 -0
- package/nodes/add-block.html +67 -0
- package/nodes/add-block.js +97 -0
- package/nodes/analog-switch-block.html +65 -0
- package/nodes/analog-switch-block.js +129 -0
- package/nodes/and-block.html +64 -0
- package/nodes/and-block.js +73 -0
- package/nodes/average-block.html +97 -0
- package/nodes/average-block.js +137 -0
- package/nodes/boolean-switch-block.html +59 -0
- package/nodes/boolean-switch-block.js +88 -0
- package/nodes/boolean-to-number-block.html +59 -0
- package/nodes/boolean-to-number-block.js +45 -0
- package/nodes/cache-block.html +69 -0
- package/nodes/cache-block.js +106 -0
- package/nodes/call-status-block.html +111 -0
- package/nodes/call-status-block.js +274 -0
- package/nodes/changeover-block.html +234 -0
- package/nodes/changeover-block.js +392 -0
- package/nodes/comment-block.html +83 -0
- package/nodes/comment-block.js +53 -0
- package/nodes/compare-block.html +64 -0
- package/nodes/compare-block.js +84 -0
- package/nodes/contextual-label-block.html +67 -0
- package/nodes/contextual-label-block.js +52 -0
- package/nodes/convert-block.html +179 -0
- package/nodes/convert-block.js +289 -0
- package/nodes/count-block.html +57 -0
- package/nodes/count-block.js +92 -0
- package/nodes/debounce-block.html +64 -0
- package/nodes/debounce-block.js +140 -0
- package/nodes/delay-block.html +104 -0
- package/nodes/delay-block.js +180 -0
- package/nodes/divide-block.html +65 -0
- package/nodes/divide-block.js +123 -0
- package/nodes/edge-block.html +71 -0
- package/nodes/edge-block.js +120 -0
- package/nodes/frequency-block.html +55 -0
- package/nodes/frequency-block.js +140 -0
- package/nodes/hysteresis-block.html +131 -0
- package/nodes/hysteresis-block.js +142 -0
- package/nodes/interpolate-block.html +74 -0
- package/nodes/interpolate-block.js +141 -0
- package/nodes/load-sequence-block.html +134 -0
- package/nodes/load-sequence-block.js +272 -0
- package/nodes/max-block.html +76 -0
- package/nodes/max-block.js +103 -0
- package/nodes/memory-block.html +90 -0
- package/nodes/memory-block.js +241 -0
- package/nodes/min-block.html +77 -0
- package/nodes/min-block.js +106 -0
- package/nodes/minmax-block.html +89 -0
- package/nodes/minmax-block.js +119 -0
- package/nodes/modulo-block.html +73 -0
- package/nodes/modulo-block.js +126 -0
- package/nodes/multiply-block.html +63 -0
- package/nodes/multiply-block.js +115 -0
- package/nodes/negate-block.html +55 -0
- package/nodes/negate-block.js +91 -0
- package/nodes/nullify-block.html +111 -0
- package/nodes/nullify-block.js +78 -0
- package/nodes/on-change-block.html +79 -0
- package/nodes/on-change-block.js +191 -0
- package/nodes/oneshot-block.html +96 -0
- package/nodes/oneshot-block.js +169 -0
- package/nodes/or-block.html +64 -0
- package/nodes/or-block.js +73 -0
- package/nodes/pid-block.html +205 -0
- package/nodes/pid-block.js +407 -0
- package/nodes/priority-block.html +66 -0
- package/nodes/priority-block.js +239 -0
- package/nodes/rate-limit-block.html +99 -0
- package/nodes/rate-limit-block.js +221 -0
- package/nodes/round-block.html +73 -0
- package/nodes/round-block.js +89 -0
- package/nodes/saw-tooth-wave-block.html +87 -0
- package/nodes/saw-tooth-wave-block.js +161 -0
- package/nodes/scale-range-block.html +90 -0
- package/nodes/scale-range-block.js +137 -0
- package/nodes/sine-wave-block.html +88 -0
- package/nodes/sine-wave-block.js +142 -0
- package/nodes/subtract-block.html +64 -0
- package/nodes/subtract-block.js +103 -0
- package/nodes/thermistor-block.html +81 -0
- package/nodes/thermistor-block.js +146 -0
- package/nodes/tick-tock-block.html +66 -0
- package/nodes/tick-tock-block.js +110 -0
- package/nodes/time-sequence-block.html +67 -0
- package/nodes/time-sequence-block.js +144 -0
- package/nodes/triangle-wave-block.html +86 -0
- package/nodes/triangle-wave-block.js +154 -0
- package/nodes/tstat-block.html +311 -0
- package/nodes/tstat-block.js +499 -0
- package/nodes/units-block.html +150 -0
- package/nodes/units-block.js +106 -0
- package/package.json +73 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function AndBlockNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
|
|
6
|
+
// Initialize state
|
|
7
|
+
node.inputs = Array(parseInt(config.slots) || 2).fill(false);
|
|
8
|
+
node.slots = parseInt(config.slots);
|
|
9
|
+
|
|
10
|
+
node.status({ fill: "green", shape: "dot", text: `slots: ${node.slots}` });
|
|
11
|
+
|
|
12
|
+
// Initialize fields
|
|
13
|
+
let lastResult = null;
|
|
14
|
+
let lastInputs = node.inputs.slice();
|
|
15
|
+
|
|
16
|
+
node.on("input", function(msg, send, done) {
|
|
17
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
18
|
+
|
|
19
|
+
// Guard against invalid msg
|
|
20
|
+
if (!msg) {
|
|
21
|
+
node.status({ fill: "red", shape: "ring", text: "invalid message" });
|
|
22
|
+
if (done) done();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check required properties
|
|
27
|
+
if (!msg.hasOwnProperty("context")) {
|
|
28
|
+
node.status({ fill: "red", shape: "ring", text: "missing context" });
|
|
29
|
+
if (done) done();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
34
|
+
node.status({ fill: "red", shape: "ring", text: "missing payload" });
|
|
35
|
+
if (done) done();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Process input slot
|
|
40
|
+
if (msg.context.startsWith("in")) {
|
|
41
|
+
let index = parseInt(msg.context.slice(2), 10);
|
|
42
|
+
if (!isNaN(index) && index >= 1 && index <= node.slots) {
|
|
43
|
+
node.inputs[index - 1] = Boolean(msg.payload);
|
|
44
|
+
const result = node.inputs.every(v => v === true);
|
|
45
|
+
const isUnchanged = result === lastResult && node.inputs.every((v, i) => v === lastInputs[i]);
|
|
46
|
+
node.status({
|
|
47
|
+
fill: "blue",
|
|
48
|
+
shape: isUnchanged ? "ring" : "dot",
|
|
49
|
+
text: `in: [${node.inputs.join(", ")}], out: ${result}`
|
|
50
|
+
});
|
|
51
|
+
lastResult = result;
|
|
52
|
+
lastInputs = node.inputs.slice();
|
|
53
|
+
send({ payload: result });
|
|
54
|
+
if (done) done();
|
|
55
|
+
return;
|
|
56
|
+
} else {
|
|
57
|
+
node.status({ fill: "red", shape: "ring", text: `invalid input index ${index || "NaN"}` });
|
|
58
|
+
if (done) done();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
|
|
64
|
+
if (done) done();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
node.on("close", function(done) {
|
|
68
|
+
done();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
RED.nodes.registerType("and-block", AndBlockNode);
|
|
73
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="average-block">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
5
|
+
</div>
|
|
6
|
+
<div class="form-row">
|
|
7
|
+
<label for="node-input-sampleSize" title="Rolling window size (positive integer, e.g., 10)"><i class="fa fa-list-ol"></i> Window Size</label>
|
|
8
|
+
<input type="number" id="node-input-sampleSize" placeholder="10" min="1" step="1">
|
|
9
|
+
</div>
|
|
10
|
+
<div class="form-row">
|
|
11
|
+
<label for="node-input-minValid"><i class="fa fa-arrow-down"></i> Minimum Valid</label>
|
|
12
|
+
<input type="text" id="node-input-minValid" placeholder="150">
|
|
13
|
+
<input type="hidden" id="node-input-minValidType">
|
|
14
|
+
</div>
|
|
15
|
+
<div class="form-row">
|
|
16
|
+
<label for="node-input-maxValid"><i class="fa fa-arrow-up"></i> Maximum Valid</label>
|
|
17
|
+
<input type="text" id="node-input-maxValid" placeholder="1">
|
|
18
|
+
<input type="hidden" id="node-input-maxValidType">
|
|
19
|
+
</div>
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<script type="text/javascript">
|
|
23
|
+
RED.nodes.registerType("average-block", {
|
|
24
|
+
category: "control",
|
|
25
|
+
color: "#301934",
|
|
26
|
+
defaults: {
|
|
27
|
+
name: { value: "" },
|
|
28
|
+
sampleSize: {
|
|
29
|
+
value: 10,
|
|
30
|
+
required: true,
|
|
31
|
+
validate: function(v) { return !isNaN(parseInt(v)) && parseInt(v) >= 1; }
|
|
32
|
+
},
|
|
33
|
+
minValid: { value: 1, required: true },
|
|
34
|
+
minValidType: { value: "num" },
|
|
35
|
+
maxValid: { value: 150, required: true },
|
|
36
|
+
maxValidType: { value: "num" }
|
|
37
|
+
},
|
|
38
|
+
inputs: 1,
|
|
39
|
+
outputs: 1,
|
|
40
|
+
inputLabels: ["input"],
|
|
41
|
+
outputLabels: ["average"],
|
|
42
|
+
icon: "join.svg",
|
|
43
|
+
paletteLabel: "average",
|
|
44
|
+
label: function() {
|
|
45
|
+
return this.name ? `${this.name} (${this.sampleSize})` : `average (${this.sampleSize})`;
|
|
46
|
+
},
|
|
47
|
+
oneditprepare: function() {
|
|
48
|
+
const node = this;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Initialize typed inputs
|
|
52
|
+
$("#node-input-minValid").typedInput({
|
|
53
|
+
default: "num",
|
|
54
|
+
types: ["num", "msg", "flow", "global"],
|
|
55
|
+
typeField: "#node-input-minValidType"
|
|
56
|
+
}).typedInput("type", node.minValidType || "num").typedInput("value", node.minValid || "1");
|
|
57
|
+
|
|
58
|
+
$("#node-input-maxValid").typedInput({
|
|
59
|
+
default: "num",
|
|
60
|
+
types: ["num", "msg", "flow", "global"],
|
|
61
|
+
typeField: "#node-input-maxValidType"
|
|
62
|
+
}).typedInput("type", node.maxValidType || "num").typedInput("value", node.maxValid || "150");
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error("Error in hysteresis-block oneditprepare:", err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<script type="text/markdown" data-help-name="average-block">
|
|
71
|
+
Computes a rolling average of numeric inputs within a valid range.
|
|
72
|
+
|
|
73
|
+
### Inputs
|
|
74
|
+
: context (string) : Configures reset (`"reset"`) or window size (`"sampleSize"`).
|
|
75
|
+
: payload (number | boolean) : Number for averaging, boolean for reset.
|
|
76
|
+
: minValid (number) : Minimum valid range for payload input.
|
|
77
|
+
: maxValid (number) : Maximum valid range for payload input.
|
|
78
|
+
|
|
79
|
+
### Outputs
|
|
80
|
+
: payload (number | null) : Rolling average, or `null` if no valid inputs.
|
|
81
|
+
|
|
82
|
+
### Details
|
|
83
|
+
Averages numeric `msg.payload` values over a rolling window (default: 10).
|
|
84
|
+
|
|
85
|
+
Resets state (`sum`, `count`) via `msg.context = "reset"` with `msg.payload = true`.
|
|
86
|
+
|
|
87
|
+
### Status
|
|
88
|
+
- Green (dot): Configuration update
|
|
89
|
+
- Blue (dot): State changed
|
|
90
|
+
- Blue (ring): State unchanged
|
|
91
|
+
- Red (ring): Error
|
|
92
|
+
- Yellow (ring): Warning
|
|
93
|
+
|
|
94
|
+
### References
|
|
95
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
96
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
97
|
+
</script>
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function AverageBlockNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
|
|
6
|
+
// Initialize runtime state
|
|
7
|
+
node.runtime = {
|
|
8
|
+
maxValues: parseInt(config.sampleSize),
|
|
9
|
+
values: [], // Queue for rolling window
|
|
10
|
+
lastAvg: null
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Validate initial config
|
|
14
|
+
if (isNaN(node.runtime.maxValues) || node.runtime.maxValues < 1) {
|
|
15
|
+
node.runtime.maxValues = 10;
|
|
16
|
+
node.status({ fill: "red", shape: "ring", text: "invalid window size, using 10" });
|
|
17
|
+
} else {
|
|
18
|
+
node.status({ shape: "dot", text: `name: ${config.name}, window: ${config.sampleSize}` });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
node.on("input", function(msg, send, done) {
|
|
22
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
23
|
+
|
|
24
|
+
// Guard against invalid msg
|
|
25
|
+
if (!msg) {
|
|
26
|
+
node.status({ fill: "red", shape: "ring", text: "invalid message" });
|
|
27
|
+
if (done) done();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Evaluate all properties
|
|
32
|
+
try {
|
|
33
|
+
node.runtime.minValid = RED.util.evaluateNodeProperty(
|
|
34
|
+
config.minValid, config.minValidType, node, msg
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
node.runtime.maxValid = RED.util.evaluateNodeProperty(
|
|
38
|
+
config.maxValid, config.maxValidType, node, msg
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Validate values
|
|
42
|
+
if (isNaN(node.runtime.maxValid) || isNaN(node.runtime.minValid) || node.runtime.maxValid <= node.runtime.minValid ) {
|
|
43
|
+
node.status({ fill: "red", shape: "ring", text: `invalid evaluated values ${node.runtime.minValid}, ${node.runtime.maxValid}` });
|
|
44
|
+
if (done) done();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
} catch(err) {
|
|
48
|
+
node.status({ fill: "red", shape: "ring", text: "error evaluating properties" });
|
|
49
|
+
if (done) done(err);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle configuration messages
|
|
54
|
+
if (msg.hasOwnProperty("context")) {
|
|
55
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
56
|
+
node.status({ fill: "red", shape: "ring", text: "missing payload" });
|
|
57
|
+
if (done) done();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
switch (msg.context) {
|
|
62
|
+
case "reset":
|
|
63
|
+
if (typeof msg.payload !== "boolean") {
|
|
64
|
+
node.status({ fill: "red", shape: "ring", text: "invalid reset" });
|
|
65
|
+
if (done) done();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (msg.payload === true) {
|
|
69
|
+
node.runtime.values = [];
|
|
70
|
+
node.runtime.lastAvg = null;
|
|
71
|
+
node.status({ fill: "green", shape: "dot", text: "state reset" });
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
|
|
75
|
+
case "sampleSize":
|
|
76
|
+
let newMaxValues = parseInt(msg.payload);
|
|
77
|
+
if (isNaN(newMaxValues) || newMaxValues < 1) {
|
|
78
|
+
node.status({ fill: "red", shape: "ring", text: "invalid window size" });
|
|
79
|
+
if (done) done();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
node.runtime.maxValues = newMaxValues;
|
|
83
|
+
// Trim values if new window is smaller
|
|
84
|
+
if (node.runtime.values.length > newMaxValues) {
|
|
85
|
+
node.runtime.values = node.runtime.values.slice(-newMaxValues);
|
|
86
|
+
}
|
|
87
|
+
node.status({ fill: "green", shape: "dot", text: `window: ${newMaxValues}` });
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (done) done();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Check for missing payload
|
|
99
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
100
|
+
node.status({ fill: "red", shape: "ring", text: "missing payload" });
|
|
101
|
+
if (done) done();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Process input
|
|
106
|
+
const inputValue = parseFloat(msg.payload);
|
|
107
|
+
if (isNaN(inputValue) || inputValue < node.runtime.minValid || inputValue > node.runtime.maxValid) {
|
|
108
|
+
node.status({ fill: "yellow", shape: "ring", text: "out of range" });
|
|
109
|
+
if (done) done();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Update rolling window
|
|
114
|
+
node.runtime.values.push(inputValue);
|
|
115
|
+
if (node.runtime.values.length > node.runtime.maxValues) {
|
|
116
|
+
node.runtime.values.shift();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Calculate average
|
|
120
|
+
const avg = node.runtime.values.length ? node.runtime.values.reduce((a, b) => a + b, 0) / node.runtime.values.length : null;
|
|
121
|
+
const isUnchanged = avg === node.runtime.lastAvg;
|
|
122
|
+
|
|
123
|
+
// Send new message
|
|
124
|
+
node.status({ fill: "blue", shape: isUnchanged ? "ring" : "dot", text: `out: ${avg !== null ? avg.toFixed(3) : "null"}` });
|
|
125
|
+
node.runtime.lastAvg = avg;
|
|
126
|
+
send({ payload: avg });
|
|
127
|
+
|
|
128
|
+
if (done) done();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
node.on("close", function(done) {
|
|
132
|
+
done();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
RED.nodes.registerType("average-block", AverageBlockNode);
|
|
137
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!-- UI Template Section -->
|
|
2
|
+
<script type="text/html" data-template-name="boolean-switch-block">
|
|
3
|
+
<div class="form-row">
|
|
4
|
+
<label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
|
|
5
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
6
|
+
</div>
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<!-- JavaScript Section -->
|
|
10
|
+
<script type="text/javascript">
|
|
11
|
+
RED.nodes.registerType("boolean-switch-block", {
|
|
12
|
+
category: "control",
|
|
13
|
+
color: "#301934",
|
|
14
|
+
defaults: {
|
|
15
|
+
name: { value: "" }
|
|
16
|
+
},
|
|
17
|
+
inputs: 1,
|
|
18
|
+
outputs: 3,
|
|
19
|
+
inputLabels: ["control"],
|
|
20
|
+
outputLabels: ["outTrue", "outFalse", "outControl"],
|
|
21
|
+
icon: "font-awesome/fa-toggle-on",
|
|
22
|
+
paletteLabel: "boolean switch",
|
|
23
|
+
label: function() {
|
|
24
|
+
return this.name || "boolean switch";
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<!-- Help Section -->
|
|
30
|
+
<script type="text/markdown" data-help-name="boolean-switch-block">
|
|
31
|
+
Routes input flows to one of three outputs based on a boolean switch state.
|
|
32
|
+
|
|
33
|
+
### Inputs
|
|
34
|
+
: context (string) : Configuration commands (`toggle`, `switch`, `inTrue`, `inFalse`).
|
|
35
|
+
: payload (any) : Flow data for `"inTrue"` or `"inFalse"`.
|
|
36
|
+
|
|
37
|
+
### Outputs
|
|
38
|
+
: outTrue (msg) : Receives `msg` with `context "inTrue"` when switch is `true`.
|
|
39
|
+
: outFalse (msg) : Receives `msg` with `context "inFalse"` when switch is `false`.
|
|
40
|
+
: outControl (msg) : `msg.payload` set to switch state on toggle.
|
|
41
|
+
|
|
42
|
+
### Details
|
|
43
|
+
Routes input messages to one of three outputs based on a boolean switch state (`true` or `false`).
|
|
44
|
+
|
|
45
|
+
Messages with `msg.context = "inTrue"` are sent to `outTrue` when `state = true`, and messages with `msg.context = "inFalse"` are sent to `outFalse` when `state = false`.
|
|
46
|
+
|
|
47
|
+
State can be controlled directly with the `"switch"` property given a `msg.payload` boolean value.
|
|
48
|
+
|
|
49
|
+
### Status
|
|
50
|
+
- Green (dot): Configuration update
|
|
51
|
+
- Blue (dot): State changed
|
|
52
|
+
- Blue (ring): State unchanged
|
|
53
|
+
- Red (ring): Error
|
|
54
|
+
- Yellow (ring): Warning
|
|
55
|
+
|
|
56
|
+
### References
|
|
57
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
58
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function BooleanSwitchBlockNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
|
|
6
|
+
// Initialize state from config
|
|
7
|
+
node.state = config.state !== undefined ? Boolean(config.state) : false;
|
|
8
|
+
|
|
9
|
+
// Set initial status
|
|
10
|
+
node.status({
|
|
11
|
+
fill: "green",
|
|
12
|
+
shape: "dot",
|
|
13
|
+
text: `state: ${node.state}`
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
node.on("input", function(msg, send, done) {
|
|
17
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
18
|
+
|
|
19
|
+
// Guard against invalid message
|
|
20
|
+
if (!msg) {
|
|
21
|
+
node.status({ fill: "red", shape: "ring", text: "invalid message" });
|
|
22
|
+
if (done) done();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Validate context
|
|
27
|
+
if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
|
|
28
|
+
node.status({ fill: "red", shape: "ring", text: "missing or invalid context" });
|
|
29
|
+
if (done) done();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Handle context commands
|
|
34
|
+
switch (msg.context) {
|
|
35
|
+
case "toggle":
|
|
36
|
+
node.state = !node.state;
|
|
37
|
+
node.status({
|
|
38
|
+
fill: "green",
|
|
39
|
+
shape: "dot",
|
|
40
|
+
text: `state: ${node.state}`
|
|
41
|
+
});
|
|
42
|
+
send([null, null, { payload: node.state }]);
|
|
43
|
+
break;
|
|
44
|
+
case "switch":
|
|
45
|
+
node.state = !!msg.payload;
|
|
46
|
+
node.status({
|
|
47
|
+
fill: "green",
|
|
48
|
+
shape: "dot",
|
|
49
|
+
text: `state: ${node.state}`
|
|
50
|
+
});
|
|
51
|
+
send([null, null, { payload: node.state }]);
|
|
52
|
+
break;
|
|
53
|
+
case "inTrue":
|
|
54
|
+
if (node.state) {
|
|
55
|
+
node.status({
|
|
56
|
+
fill: "blue",
|
|
57
|
+
shape: "dot",
|
|
58
|
+
text: `out: ${msg.payload}`
|
|
59
|
+
});
|
|
60
|
+
send([msg, null, { payload: node.state }]);
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
case "inFalse":
|
|
64
|
+
if (!node.state) {
|
|
65
|
+
node.status({
|
|
66
|
+
fill: "blue",
|
|
67
|
+
shape: "dot",
|
|
68
|
+
text: `out: ${msg.payload}`
|
|
69
|
+
});
|
|
70
|
+
send([null, msg, { payload: node.state }]);
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
|
|
75
|
+
if (done) done("Unknown context");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (done) done();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
node.on("close", function(done) {
|
|
82
|
+
node.status({});
|
|
83
|
+
done();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
RED.nodes.registerType("boolean-switch-block", BooleanSwitchBlockNode);
|
|
88
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!-- UI Template Section -->
|
|
2
|
+
<script type="text/html" data-template-name="boolean-to-number-block">
|
|
3
|
+
<div class="form-row">
|
|
4
|
+
<label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
|
|
5
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
6
|
+
</div>
|
|
7
|
+
<div class="form-row">
|
|
8
|
+
<label for="node-input-nullToZero"><i class="fa fa-cog"></i> Null Mapping</label>
|
|
9
|
+
<input type="checkbox" id="node-input-nullToZero" style="width: auto; margin-left: 10px;"> Map null to 0
|
|
10
|
+
</div>
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<!-- JavaScript Section -->
|
|
14
|
+
<script type="text/javascript">
|
|
15
|
+
RED.nodes.registerType("boolean-to-number-block", {
|
|
16
|
+
category: "control",
|
|
17
|
+
color: "#301934",
|
|
18
|
+
defaults: {
|
|
19
|
+
name: { value: "" },
|
|
20
|
+
nullToZero: { value: false }
|
|
21
|
+
},
|
|
22
|
+
inputs: 1,
|
|
23
|
+
outputs: 1,
|
|
24
|
+
inputLabels: ["input"],
|
|
25
|
+
outputLabels: ["output"],
|
|
26
|
+
icon: "font-awesome/fa-calculator",
|
|
27
|
+
paletteLabel: "boolean to number",
|
|
28
|
+
label: function() {
|
|
29
|
+
return this.name || "boolean to number";
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<!-- Help Section -->
|
|
35
|
+
<script type="text/markdown" data-help-name="boolean-to-number-block">
|
|
36
|
+
Converts a boolean or null input payload to a numeric output.
|
|
37
|
+
|
|
38
|
+
### Inputs
|
|
39
|
+
: payload (boolean | null) : Value to convert (`true`, `false`, `null`).
|
|
40
|
+
|
|
41
|
+
### Outputs
|
|
42
|
+
: payload (number) : Converted value `null` to `0` (if `nullToZero`) or `-1`, `false` to `0`, `true` to `1`.
|
|
43
|
+
|
|
44
|
+
### Details
|
|
45
|
+
Converts `msg.payload` (boolean or null) to a number `null` maps to `0` (if `nullToZero` is `true`) or `-1` (if `false`), `false` to `0`, `true` to `1`.
|
|
46
|
+
|
|
47
|
+
Passthrough all other input message properties
|
|
48
|
+
|
|
49
|
+
### Status
|
|
50
|
+
- Green (dot): Configuration update
|
|
51
|
+
- Blue (dot): State changed
|
|
52
|
+
- Blue (ring): State unchanged
|
|
53
|
+
- Red (ring): Error
|
|
54
|
+
- Yellow (ring): Warning
|
|
55
|
+
|
|
56
|
+
### References
|
|
57
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
58
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function BooleanToNumberBlockNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
|
|
6
|
+
// Initialize runtime for editor display
|
|
7
|
+
node.runtime = {
|
|
8
|
+
name: config.name,
|
|
9
|
+
nullToZero: Boolean(config.nullToZero)
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
node.on("input", function(msg, send, done) {
|
|
13
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
14
|
+
|
|
15
|
+
// Check for missing payload
|
|
16
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
17
|
+
node.status({ fill: "red", shape: "ring", text: "missing payload" });
|
|
18
|
+
if (done) done();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Validate and convert payload
|
|
23
|
+
const inputDisplay = msg.payload === null ? "null" : String(msg.payload);
|
|
24
|
+
if (msg.payload === null) {
|
|
25
|
+
msg.payload = node.runtime.nullToZero ? 0 : -1;
|
|
26
|
+
node.status({ fill: "blue", shape: "dot", text: `in: ${inputDisplay}, out: ${msg.payload}` });
|
|
27
|
+
send(msg);
|
|
28
|
+
} else if (typeof msg.payload === "boolean") {
|
|
29
|
+
msg.payload = msg.payload ? 1 : 0;
|
|
30
|
+
node.status({ fill: "blue", shape: "dot", text: `in: ${inputDisplay}, out: ${msg.payload}` });
|
|
31
|
+
send(msg);
|
|
32
|
+
} else {
|
|
33
|
+
node.status({ fill: "red", shape: "ring", text: "invalid payload" });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (done) done();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
node.on("close", function(done) {
|
|
40
|
+
done();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
RED.nodes.registerType("boolean-to-number-block", BooleanToNumberBlockNode);
|
|
45
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!-- UI Template Section: Defines the edit dialog -->
|
|
2
|
+
<script type="text/html" data-template-name="cache-block">
|
|
3
|
+
<div class="form-row">
|
|
4
|
+
<label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
|
|
5
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
6
|
+
</div>
|
|
7
|
+
<div class="form-row">
|
|
8
|
+
<label for="node-input-operationMode" title="Choose whether to output the full cloned message or only the payload"><i class="fa fa-cog"></i> Operation Mode</label>
|
|
9
|
+
<input type="text" id="node-input-operationMode" placeholder="payload">
|
|
10
|
+
</div>
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<!-- JavaScript Section: Registers the node and handles editor logic -->
|
|
14
|
+
<script type="text/javascript">
|
|
15
|
+
RED.nodes.registerType("cache-block", {
|
|
16
|
+
category: "control",
|
|
17
|
+
color: "#301934",
|
|
18
|
+
defaults: {
|
|
19
|
+
name: { value: "" },
|
|
20
|
+
operationMode: { value: "payload", required: true }
|
|
21
|
+
},
|
|
22
|
+
inputs: 1,
|
|
23
|
+
outputs: 1,
|
|
24
|
+
inputLabels: ["input"],
|
|
25
|
+
outputLabels: ["output"],
|
|
26
|
+
icon: "font-awesome/fa-database",
|
|
27
|
+
paletteLabel: "cache",
|
|
28
|
+
label: function() {
|
|
29
|
+
return this.name || "cache";
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<!-- Help Section -->
|
|
35
|
+
<script type="text/markdown" data-help-name="cache-block">
|
|
36
|
+
Caches an input message and outputs either a copy or just the payload based on configuration.
|
|
37
|
+
|
|
38
|
+
### Inputs
|
|
39
|
+
: context (string) : Sets action (`"update"` to store, `"execute"` to output, `"reset"` to clear).
|
|
40
|
+
Unmatched values trigger an error.
|
|
41
|
+
: payload (any | boolean) : Value to store (`update`), ignored for `execute`, boolean `true` for `reset`.
|
|
42
|
+
|
|
43
|
+
### Outputs
|
|
44
|
+
: payload (any) : Cached value when `execute` is triggered; `null` if cache is empty.
|
|
45
|
+
: other properties : Included only if "Clone Message" mode is selected.
|
|
46
|
+
|
|
47
|
+
### Properties
|
|
48
|
+
: name (string) : Display name in editor. Default; "".
|
|
49
|
+
: operationMode (string) : Selects output behavior; "Clone Message" (full message) or "Payload Only" (just payload).
|
|
50
|
+
|
|
51
|
+
### Details
|
|
52
|
+
Stores the entire input message when `msg.context = "update"`.
|
|
53
|
+
|
|
54
|
+
Outputs either a full copy of the cached message (`operationMode = "Clone Message"`)
|
|
55
|
+
or a new message with only the cached `msg.payload`
|
|
56
|
+
|
|
57
|
+
Clears the cache to `null` when `msg.context = "reset"` with `msg.payload = true`.
|
|
58
|
+
|
|
59
|
+
### Status
|
|
60
|
+
- Green (dot): Configuration update
|
|
61
|
+
- Blue (dot): State changed
|
|
62
|
+
- Blue (ring): State unchanged
|
|
63
|
+
- Red (ring): Error
|
|
64
|
+
- Yellow (ring): Warning
|
|
65
|
+
|
|
66
|
+
### References
|
|
67
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
68
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
69
|
+
</script>
|