@bldgblocks/node-red-contrib-control 0.1.27 → 0.1.29
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 +1 -1
- package/nodes/average-block.js +1 -1
- package/nodes/boolean-switch-block.html +2 -1
- package/nodes/boolean-switch-block.js +1 -2
- package/nodes/call-status-block.html +30 -45
- package/nodes/call-status-block.js +81 -195
- package/nodes/changeover-block.html +76 -12
- package/nodes/changeover-block.js +24 -8
- package/nodes/delay-block.html +1 -1
- package/nodes/frequency-block.html +3 -1
- package/nodes/frequency-block.js +64 -7
- package/nodes/global-getter.html +96 -0
- package/nodes/global-getter.js +42 -0
- package/nodes/global-setter.html +72 -0
- package/nodes/global-setter.js +43 -0
- package/nodes/hysteresis-block.js +1 -1
- package/nodes/latch-block.html +55 -0
- package/nodes/latch-block.js +77 -0
- package/nodes/on-change-block.html +0 -1
- package/nodes/on-change-block.js +2 -12
- package/nodes/pid-block.html +102 -80
- package/nodes/pid-block.js +120 -110
- package/nodes/rate-of-change-block.html +110 -0
- package/nodes/rate-of-change-block.js +233 -0
- package/nodes/string-builder-block.html +112 -0
- package/nodes/string-builder-block.js +89 -0
- package/nodes/tstat-block.html +85 -39
- package/nodes/tstat-block.js +66 -30
- package/package.json +6 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<!-- UI Template Section -->
|
|
2
|
+
<script type="text/html" data-template-name="latch-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("latch-block", {
|
|
12
|
+
category: "control",
|
|
13
|
+
color: "#301934",
|
|
14
|
+
defaults: {
|
|
15
|
+
name: { value: "" },
|
|
16
|
+
state: { value: false }
|
|
17
|
+
},
|
|
18
|
+
inputs: 1,
|
|
19
|
+
outputs: 1,
|
|
20
|
+
inputLabels: ["input"],
|
|
21
|
+
outputLabels: ["output"],
|
|
22
|
+
icon: "font-awesome/fa-toggle-on",
|
|
23
|
+
paletteLabel: "latch",
|
|
24
|
+
label: function() {
|
|
25
|
+
return this.name || "latch";
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<!-- Help Section -->
|
|
31
|
+
<script type="text/markdown" data-help-name="latch-block">
|
|
32
|
+
Latch output based on control messages.
|
|
33
|
+
|
|
34
|
+
### Inputs
|
|
35
|
+
: context (string) : Configuration commands (`set`, `reset`).
|
|
36
|
+
: payload (boolean) : `true` to set latch, `false` to reset latch when paired with appropriate `context`.
|
|
37
|
+
|
|
38
|
+
### Outputs
|
|
39
|
+
: output (msg) : `msg.payload` `true` or `false` based on latch state.
|
|
40
|
+
|
|
41
|
+
### Details
|
|
42
|
+
Set or reset the latch state based on input messages. `msg.context` = `"set"` with `msg.payload` = `true`
|
|
43
|
+
sets the latch `true`, while `msg.context` = `"reset"` with `msg.payload` = `true` resets it to `false`.
|
|
44
|
+
|
|
45
|
+
### Status
|
|
46
|
+
- Green (dot): Configuration update
|
|
47
|
+
- Blue (dot): State changed
|
|
48
|
+
- Blue (ring): State unchanged
|
|
49
|
+
- Red (ring): Error
|
|
50
|
+
- Yellow (ring): Warning
|
|
51
|
+
|
|
52
|
+
### References
|
|
53
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
54
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
55
|
+
</script>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function LatchBlockNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
|
|
5
|
+
const node = this;
|
|
6
|
+
|
|
7
|
+
// Initialize state from config
|
|
8
|
+
node.state = config.state;
|
|
9
|
+
|
|
10
|
+
// Set initial status
|
|
11
|
+
node.status({
|
|
12
|
+
fill: "green",
|
|
13
|
+
shape: "dot",
|
|
14
|
+
text: `state: ${node.state}`
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
node.on("input", function(msg, send, done) {
|
|
18
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
19
|
+
|
|
20
|
+
// Guard against invalid message
|
|
21
|
+
if (!msg) {
|
|
22
|
+
node.status({ fill: "red", shape: "ring", text: "invalid message" });
|
|
23
|
+
if (done) done();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Validate context
|
|
28
|
+
if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
|
|
29
|
+
node.status({ fill: "red", shape: "ring", text: "missing or invalid context" });
|
|
30
|
+
if (done) done();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle context commands
|
|
35
|
+
switch (msg.context) {
|
|
36
|
+
case "set":
|
|
37
|
+
if (node.state) {
|
|
38
|
+
node.status({ fill: "blue", shape: "ring", text: `state: ${node.state}` });
|
|
39
|
+
} else {
|
|
40
|
+
if (msg.payload) {
|
|
41
|
+
node.state = true;
|
|
42
|
+
node.status({ fill: "blue", shape: "dot", text: `state: ${node.state}` });
|
|
43
|
+
} else {
|
|
44
|
+
node.status({ fill: "blue", shape: "ring", text: `state: ${node.state}` });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Output latch value regardless
|
|
48
|
+
send({ payload: node.state });
|
|
49
|
+
break;
|
|
50
|
+
case "reset":
|
|
51
|
+
if (node.state === false) {
|
|
52
|
+
node.status({ fill: "blue", shape: "ring", text: `state: ${node.state}` });
|
|
53
|
+
} else {
|
|
54
|
+
if (msg.payload) {
|
|
55
|
+
node.state = false;
|
|
56
|
+
node.status({ fill: "blue", shape: "dot", text: `state: ${node.state}` });
|
|
57
|
+
} else {
|
|
58
|
+
node.status({ fill: "blue", shape: "ring", text: `state: ${node.state}` });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
send({ payload: node.state });
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
|
|
65
|
+
if (done) done("Unknown context");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (done) done();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
node.on("close", function(done) {
|
|
72
|
+
done();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
RED.nodes.registerType("latch-block", LatchBlockNode);
|
|
77
|
+
};
|
|
@@ -64,7 +64,6 @@ differs from the last output value. When `period > 0`, outputs the first message
|
|
|
64
64
|
Supports complex payloads (objects, arrays) via deep comparison.
|
|
65
65
|
Configuration
|
|
66
66
|
- `msg.context = "period"` Sets period (ms), no output.
|
|
67
|
-
- `msg.context = "status"` Outputs `{ period, periodType }`.
|
|
68
67
|
|
|
69
68
|
### Status
|
|
70
69
|
- Green (dot): Configuration update
|
package/nodes/on-change-block.js
CHANGED
|
@@ -70,16 +70,6 @@ module.exports = function(RED) {
|
|
|
70
70
|
if (done) done();
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
|
-
if (msg.context === "status") {
|
|
74
|
-
send({
|
|
75
|
-
payload: {
|
|
76
|
-
period: node.runtime.period,
|
|
77
|
-
periodType: node.runtime.periodType
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
if (done) done();
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
73
|
// Ignore unknown context
|
|
84
74
|
}
|
|
85
75
|
|
|
@@ -115,7 +105,7 @@ module.exports = function(RED) {
|
|
|
115
105
|
node.status({
|
|
116
106
|
fill: "blue",
|
|
117
107
|
shape: "ring",
|
|
118
|
-
text: `filtered: ${JSON.stringify(currentValue).slice(0, 20)}
|
|
108
|
+
text: `filtered: ${JSON.stringify(currentValue).slice(0, 20)} |`
|
|
119
109
|
});
|
|
120
110
|
if (done) done();
|
|
121
111
|
return;
|
|
@@ -139,7 +129,7 @@ module.exports = function(RED) {
|
|
|
139
129
|
node.status({
|
|
140
130
|
fill: "blue",
|
|
141
131
|
shape: "ring",
|
|
142
|
-
text: `
|
|
132
|
+
text: `filtered: ${JSON.stringify(currentValue).slice(0, 20)}` // remove ' |' to indicate end of filter period
|
|
143
133
|
});
|
|
144
134
|
}, node.runtime.period);
|
|
145
135
|
}
|
package/nodes/pid-block.html
CHANGED
|
@@ -6,23 +6,28 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
<div class="form-row">
|
|
8
8
|
<label for="node-input-kp" title="Proportional gain (number)"><i class="fa fa-sliders"></i> Kp</label>
|
|
9
|
-
<input type="
|
|
9
|
+
<input type="text" id="node-input-kp" placeholder="0" step="any">
|
|
10
|
+
<input type="hidden" id="node-input-kpType">
|
|
10
11
|
</div>
|
|
11
12
|
<div class="form-row">
|
|
12
13
|
<label for="node-input-ki" title="Integral gain (number)"><i class="fa fa-sliders"></i> Ki</label>
|
|
13
|
-
<input type="
|
|
14
|
+
<input type="text" id="node-input-ki" placeholder="0" step="any">
|
|
15
|
+
<input type="hidden" id="node-input-kiType">
|
|
14
16
|
</div>
|
|
15
17
|
<div class="form-row">
|
|
16
18
|
<label for="node-input-kd" title="Derivative gain (number)"><i class="fa fa-sliders"></i> Kd</label>
|
|
17
|
-
<input type="
|
|
19
|
+
<input type="text" id="node-input-kd" placeholder="0" step="any">
|
|
20
|
+
<input type="hidden" id="node-input-kdType">
|
|
18
21
|
</div>
|
|
19
22
|
<div class="form-row">
|
|
20
23
|
<label for="node-input-setpoint" title="Target setpoint (number)"><i class="fa fa-crosshairs"></i> Setpoint</label>
|
|
21
|
-
<input type="
|
|
24
|
+
<input type="text" id="node-input-setpoint" placeholder="0" step="any">
|
|
25
|
+
<input type="hidden" id="node-input-setpointType">
|
|
22
26
|
</div>
|
|
23
27
|
<div class="form-row">
|
|
24
28
|
<label for="node-input-deadband" title="Deadband range around setpoint (non-negative number)"><i class="fa fa-arrows-h"></i> Deadband</label>
|
|
25
|
-
<input type="
|
|
29
|
+
<input type="text" id="node-input-deadband" placeholder="0" step="any" min="0">
|
|
30
|
+
<input type="hidden" id="node-input-deadbandType">
|
|
26
31
|
</div>
|
|
27
32
|
<div class="form-row">
|
|
28
33
|
<label for="node-input-dbBehavior" title="Deadband behavior: ReturnToZero or HoldLastResult"><i class="fa fa-cog"></i> Deadband Behavior</label>
|
|
@@ -33,15 +38,18 @@
|
|
|
33
38
|
</div>
|
|
34
39
|
<div class="form-row">
|
|
35
40
|
<label for="node-input-outMin" title="Minimum output limit (number, less than outMax, leave empty for no limit)"><i class="fa fa-arrow-down"></i> Out Min</label>
|
|
36
|
-
<input type="
|
|
41
|
+
<input type="text" id="node-input-outMin" placeholder="No min" step="any">
|
|
42
|
+
<input type="hidden" id="node-input-outMinType">
|
|
37
43
|
</div>
|
|
38
44
|
<div class="form-row">
|
|
39
45
|
<label for="node-input-outMax" title="Maximum output limit (number, greater than outMin, leave empty for no limit)"><i class="fa fa-arrow-up"></i> Out Max</label>
|
|
40
|
-
<input type="
|
|
46
|
+
<input type="text" id="node-input-outMax" placeholder="No max" step="any">
|
|
47
|
+
<input type="hidden" id="node-input-outMaxType">
|
|
41
48
|
</div>
|
|
42
49
|
<div class="form-row">
|
|
43
50
|
<label for="node-input-maxChange" title="Maximum output change per cycle (non-negative number)"><i class="fa fa-exchange"></i> Max Change</label>
|
|
44
|
-
<input type="
|
|
51
|
+
<input type="text" id="node-input-maxChange" placeholder="0" step="any" min="0">
|
|
52
|
+
<input type="hidden" id="node-input-maxChangeType">
|
|
45
53
|
</div>
|
|
46
54
|
<div class="form-row">
|
|
47
55
|
<label for="node-input-directAction" title="Direct (true) or reverse (false) action"><i class="fa fa-exchange"></i> Direct Action</label>
|
|
@@ -49,11 +57,8 @@
|
|
|
49
57
|
</div>
|
|
50
58
|
<div class="form-row">
|
|
51
59
|
<label for="node-input-run" title="Enable (true) or disable (false) PID calculation"><i class="fa fa-play"></i> Run</label>
|
|
52
|
-
<input type="
|
|
53
|
-
|
|
54
|
-
<div class="form-row">
|
|
55
|
-
<label><i class="fa fa-info-circle"></i> Changed Runtime Values</label>
|
|
56
|
-
<pre id="node-runtime-changes" style="color: #555; white-space: pre-wrap;">Changed Values: None</pre>
|
|
60
|
+
<input type="text" id="node-input-run" style="width: auto; vertical-align: middle;" checked>
|
|
61
|
+
<input type="hidden" id="node-input-runType">
|
|
57
62
|
</div>
|
|
58
63
|
</script>
|
|
59
64
|
|
|
@@ -64,17 +69,26 @@
|
|
|
64
69
|
color: "#301934",
|
|
65
70
|
defaults: {
|
|
66
71
|
name: { value: "" },
|
|
67
|
-
kp: { value: 0, required: true
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
kp: { value: 0, required: true },
|
|
73
|
+
kpType: { value: "num" },
|
|
74
|
+
ki: { value: 0, required: true },
|
|
75
|
+
kiType: { value: "num" },
|
|
76
|
+
kd: { value: 0, required: true },
|
|
77
|
+
kdType: { value: "num" },
|
|
78
|
+
setpoint: { value: 0, required: true },
|
|
79
|
+
setpointType: { value: "num" },
|
|
80
|
+
deadband: { value: 0, required: true },
|
|
81
|
+
deadbandType: { value: "num" },
|
|
72
82
|
dbBehavior: { value: "ReturnToZero" },
|
|
73
|
-
outMin: { value: null
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
outMin: { value: null },
|
|
84
|
+
outMinType: { value: "num" },
|
|
85
|
+
outMax: { value: null },
|
|
86
|
+
outMaxType: { value: "num" },
|
|
87
|
+
maxChange: { value: 0, required: true },
|
|
88
|
+
maxChangeType: { value: "num" },
|
|
76
89
|
directAction: { value: false },
|
|
77
|
-
run: { value: true }
|
|
90
|
+
run: { value: true },
|
|
91
|
+
runType: { value: "bool" }
|
|
78
92
|
},
|
|
79
93
|
inputs: 1,
|
|
80
94
|
outputs: 1,
|
|
@@ -87,59 +101,67 @@
|
|
|
87
101
|
},
|
|
88
102
|
oneditprepare: function() {
|
|
89
103
|
const node = this;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
$("#node-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// Initialize typed inputs
|
|
107
|
+
$("#node-input-kp").typedInput({
|
|
108
|
+
default: "num",
|
|
109
|
+
types: ["num", "msg", "flow", "global"],
|
|
110
|
+
typeField: "#node-input-kpType"
|
|
111
|
+
}).typedInput("type", node.kpType || "num").typedInput("value", node.kp);
|
|
112
|
+
|
|
113
|
+
$("#node-input-ki").typedInput({
|
|
114
|
+
default: "num",
|
|
115
|
+
types: ["num", "msg", "flow", "global"],
|
|
116
|
+
typeField: "#node-input-kiType"
|
|
117
|
+
}).typedInput("type", node.kiType || "num").typedInput("value", node.ki);
|
|
118
|
+
|
|
119
|
+
$("#node-input-kd").typedInput({
|
|
120
|
+
default: "num",
|
|
121
|
+
types: ["num", "msg", "flow", "global"],
|
|
122
|
+
typeField: "#node-input-kdType"
|
|
123
|
+
}).typedInput("type", node.kdType || "num").typedInput("value", node.kd);
|
|
124
|
+
|
|
125
|
+
$("#node-input-setpoint").typedInput({
|
|
126
|
+
default: "num",
|
|
127
|
+
types: ["num", "msg", "flow", "global"],
|
|
128
|
+
typeField: "#node-input-setpointType"
|
|
129
|
+
}).typedInput("type", node.setpointType || "num").typedInput("value", node.setpoint);
|
|
130
|
+
|
|
131
|
+
$("#node-input-deadband").typedInput({
|
|
132
|
+
default: "num",
|
|
133
|
+
types: ["num", "msg", "flow", "global"],
|
|
134
|
+
typeField: "#node-input-deadbandType"
|
|
135
|
+
}).typedInput("type", node.deadbandType || "num").typedInput("value", node.deadband);
|
|
136
|
+
|
|
137
|
+
$("#node-input-outMin").typedInput({
|
|
138
|
+
default: "num",
|
|
139
|
+
types: ["num", "msg", "flow", "global"],
|
|
140
|
+
typeField: "#node-input-outMinType"
|
|
141
|
+
}).typedInput("type", node.outMinType || "num").typedInput("value", node.outMin);
|
|
142
|
+
|
|
143
|
+
$("#node-input-outMax").typedInput({
|
|
144
|
+
default: "num",
|
|
145
|
+
types: ["num", "msg", "flow", "global"],
|
|
146
|
+
typeField: "#node-input-outMaxType"
|
|
147
|
+
}).typedInput("type", node.outMaxType || "num").typedInput("value", node.outMax);
|
|
148
|
+
|
|
149
|
+
$("#node-input-maxChange").typedInput({
|
|
150
|
+
default: "num",
|
|
151
|
+
types: ["num", "msg", "flow", "global"],
|
|
152
|
+
typeField: "#node-input-maxChangeType"
|
|
153
|
+
}).typedInput("type", node.maxChangeType || "num").typedInput("value", node.maxChange);
|
|
154
|
+
|
|
155
|
+
$("#node-input-run").typedInput({
|
|
156
|
+
default: "bool",
|
|
157
|
+
types: ["bool", "msg", "flow", "global"],
|
|
158
|
+
typeField: "#node-input-runType"
|
|
159
|
+
}).typedInput("type", node.runType || "bool").typedInput("value", node.run);
|
|
160
|
+
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error("Error in oneditprepare:", err);
|
|
163
|
+
}
|
|
164
|
+
|
|
143
165
|
}
|
|
144
166
|
});
|
|
145
167
|
</script>
|
|
@@ -193,11 +215,11 @@ Ziegler-Nichols tuning sets `kp = 0.6*Ku`, `ki = 2*kp/Tu`, `kd = kp*Tu/8` after
|
|
|
193
215
|
- Invalid config at startup: Red status (`invalid config` or specific), resets to defaults.
|
|
194
216
|
|
|
195
217
|
### Status
|
|
196
|
-
- Green (dot): Configuration, reset, or tuning
|
|
197
|
-
- Blue (dot): Output change
|
|
198
|
-
- Blue (ring): Output unchanged
|
|
199
|
-
- Red (ring): Errors
|
|
200
|
-
- Yellow (ring): Unknown context
|
|
218
|
+
- Green (dot): Configuration, reset, or tuning
|
|
219
|
+
- Blue (dot): Output change
|
|
220
|
+
- Blue (ring): Output unchanged
|
|
221
|
+
- Red (ring): Errors
|
|
222
|
+
- Yellow (ring): Unknown context
|
|
201
223
|
|
|
202
224
|
### References
|
|
203
225
|
- [Node-RED Documentation](https://nodered.org/docs/)
|