@bldgblocks/node-red-contrib-control 0.1.34 → 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 +43 -13
- package/nodes/global-setter.html +1 -1
- package/nodes/global-setter.js +40 -12
- 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-register.js → network-point-register.js} +18 -4
- 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-read.js → network-service-read.js} +4 -3
- 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-write.js → network-service-write.js} +3 -3
- 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 +206 -3
- package/package.json +14 -6
- package/nodes/network-read.html +0 -56
- package/nodes/network-write.html +0 -65
package/nodes/or-block.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function OrBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
const node = this;
|
|
@@ -7,60 +9,87 @@ module.exports = function(RED) {
|
|
|
7
9
|
node.inputs = Array(parseInt(config.slots) || 2).fill(false)
|
|
8
10
|
node.slots = parseInt(config.slots);
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
utils.setStatusOK(node, `slots: ${node.slots}`);
|
|
11
13
|
|
|
12
14
|
// Initialize logic fields
|
|
13
15
|
let lastResult = null;
|
|
14
16
|
let lastInputs = node.inputs.slice();
|
|
17
|
+
let lastOutputTime = 0; // Track last output timestamp for debounce
|
|
18
|
+
let lastOutputValue = undefined; // Track last output value for duplicate suppression
|
|
19
|
+
const DEBOUNCE_MS = 500; // Debounce period in milliseconds
|
|
15
20
|
|
|
16
21
|
node.on("input", function(msg, send, done) {
|
|
17
22
|
send = send || function() { node.send.apply(node, arguments); };
|
|
18
23
|
|
|
19
24
|
// Guard against invalid msg
|
|
20
25
|
if (!msg) {
|
|
21
|
-
|
|
26
|
+
utils.setStatusError(node, "invalid message");
|
|
22
27
|
if (done) done();
|
|
23
28
|
return;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
// Check required properties
|
|
27
32
|
if (!msg.hasOwnProperty("context")) {
|
|
28
|
-
|
|
33
|
+
utils.setStatusError(node, "missing context");
|
|
29
34
|
if (done) done();
|
|
30
35
|
return;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
if (!msg.hasOwnProperty("payload")) {
|
|
34
|
-
|
|
39
|
+
utils.setStatusError(node, "missing payload");
|
|
35
40
|
if (done) done();
|
|
36
41
|
return;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
// Process input slot
|
|
40
45
|
if (msg.context.startsWith("in")) {
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
node.inputs[index - 1] = Boolean(msg.payload);
|
|
46
|
+
const slotVal = utils.validateSlotIndex(msg.context, node.slots);
|
|
47
|
+
if (slotVal.valid) {
|
|
48
|
+
node.inputs[slotVal.index - 1] = Boolean(msg.payload);
|
|
44
49
|
const result = node.inputs.some(v => v === true);
|
|
45
50
|
const isUnchanged = result === lastResult && node.inputs.every((v, i) => v === lastInputs[i]);
|
|
46
|
-
node.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
const statusText = `in: [${node.inputs.join(", ")}], out: ${result}`;
|
|
52
|
+
|
|
53
|
+
// ================================================================
|
|
54
|
+
// Debounce: Suppress consecutive same outputs within 500ms
|
|
55
|
+
// But always output if value is different or debounce time expired
|
|
56
|
+
// ================================================================
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const timeSinceLastOutput = now - lastOutputTime;
|
|
59
|
+
const isSameOutput = result === lastOutputValue;
|
|
60
|
+
const shouldSuppress = isSameOutput && timeSinceLastOutput < DEBOUNCE_MS;
|
|
61
|
+
|
|
62
|
+
if (shouldSuppress) {
|
|
63
|
+
// Same output within debounce window - don't send, just update status
|
|
64
|
+
utils.setStatusUnchanged(node, statusText);
|
|
65
|
+
} else {
|
|
66
|
+
// Different output or debounce period expired - send it
|
|
67
|
+
if (isUnchanged) {
|
|
68
|
+
utils.setStatusUnchanged(node, statusText);
|
|
69
|
+
} else {
|
|
70
|
+
utils.setStatusChanged(node, statusText);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Record output for next debounce comparison
|
|
74
|
+
lastOutputTime = now;
|
|
75
|
+
lastOutputValue = result;
|
|
76
|
+
|
|
77
|
+
// Send output to allow all downstream branches to update
|
|
78
|
+
send({ payload: result });
|
|
79
|
+
}
|
|
80
|
+
|
|
51
81
|
lastResult = result;
|
|
52
82
|
lastInputs = node.inputs.slice();
|
|
53
|
-
send({ payload: result });
|
|
54
83
|
if (done) done();
|
|
55
84
|
return;
|
|
56
85
|
} else {
|
|
57
|
-
|
|
86
|
+
utils.setStatusError(node, slotVal.error);
|
|
58
87
|
if (done) done();
|
|
59
88
|
return;
|
|
60
89
|
}
|
|
61
90
|
}
|
|
62
91
|
|
|
63
|
-
|
|
92
|
+
utils.setStatusWarn(node, "unknown context");
|
|
64
93
|
if (done) done();
|
|
65
94
|
});
|
|
66
95
|
|
package/nodes/pid-block.html
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
<label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
|
|
5
5
|
<input type="text" id="node-input-name" placeholder="Name">
|
|
6
6
|
</div>
|
|
7
|
+
<div class="form-row">
|
|
8
|
+
<label for="node-input-inputProperty" title="Message property to read input from"><i class="fa fa-folder-open"></i> Input Property</label>
|
|
9
|
+
<input type="text" id="node-input-inputProperty" placeholder="payload">
|
|
10
|
+
</div>
|
|
7
11
|
<div class="form-row">
|
|
8
12
|
<label for="node-input-kp" title="Proportional gain (number)"><i class="fa fa-sliders"></i> Kp</label>
|
|
9
13
|
<input type="text" id="node-input-kp" placeholder="0" step="any">
|
|
@@ -29,6 +33,11 @@
|
|
|
29
33
|
<input type="text" id="node-input-deadband" placeholder="0" step="any" min="0">
|
|
30
34
|
<input type="hidden" id="node-input-deadbandType">
|
|
31
35
|
</div>
|
|
36
|
+
<div class="form-row">
|
|
37
|
+
<label for="node-input-setpointRateLimit" title="Maximum setpoint change per second (non-negative number, 0 = no limit)"><i class="fa fa-arrow-right"></i> Setpoint Rate Limit</label>
|
|
38
|
+
<input type="text" id="node-input-setpointRateLimit" placeholder="0" step="any" min="0">
|
|
39
|
+
<input type="hidden" id="node-input-setpointRateLimitType">
|
|
40
|
+
</div>
|
|
32
41
|
<div class="form-row">
|
|
33
42
|
<label for="node-input-dbBehavior" title="Deadband behavior: ReturnToZero or HoldLastResult"><i class="fa fa-cog"></i> Deadband Behavior</label>
|
|
34
43
|
<select id="node-input-dbBehavior">
|
|
@@ -69,6 +78,7 @@
|
|
|
69
78
|
color: "#301934",
|
|
70
79
|
defaults: {
|
|
71
80
|
name: { value: "" },
|
|
81
|
+
inputProperty: { value: "payload" },
|
|
72
82
|
kp: { value: 0, required: true },
|
|
73
83
|
kpType: { value: "num" },
|
|
74
84
|
ki: { value: 0, required: true },
|
|
@@ -79,6 +89,8 @@
|
|
|
79
89
|
setpointType: { value: "num" },
|
|
80
90
|
deadband: { value: 0, required: true },
|
|
81
91
|
deadbandType: { value: "num" },
|
|
92
|
+
setpointRateLimit: { value: 0, required: true },
|
|
93
|
+
setpointRateLimitType: { value: "num" },
|
|
82
94
|
dbBehavior: { value: "ReturnToZero" },
|
|
83
95
|
outMin: { value: null },
|
|
84
96
|
outMinType: { value: "num" },
|
|
@@ -134,6 +146,12 @@
|
|
|
134
146
|
typeField: "#node-input-deadbandType"
|
|
135
147
|
}).typedInput("type", node.deadbandType || "num").typedInput("value", node.deadband);
|
|
136
148
|
|
|
149
|
+
$("#node-input-setpointRateLimit").typedInput({
|
|
150
|
+
default: "num",
|
|
151
|
+
types: ["num", "msg", "flow", "global"],
|
|
152
|
+
typeField: "#node-input-setpointRateLimitType"
|
|
153
|
+
}).typedInput("type", node.setpointRateLimitType || "num").typedInput("value", node.setpointRateLimit);
|
|
154
|
+
|
|
137
155
|
$("#node-input-outMin").typedInput({
|
|
138
156
|
default: "num",
|
|
139
157
|
types: ["num", "msg", "flow", "global"],
|
|
@@ -185,6 +203,7 @@ Implements a PID controller with deadband, output limits, and tuning.
|
|
|
185
203
|
: kd (number) : Derivative gain. Default: 0.
|
|
186
204
|
: setpoint (number) : Target setpoint. Default: 0.
|
|
187
205
|
: deadband (number) : Deadband range around setpoint (non-negative). Default: 0.
|
|
206
|
+
: setpointRateLimit (number) : Maximum setpoint change per second (non-negative, 0 = no limit). Default: 0.
|
|
188
207
|
: dbBehavior (string) : Deadband behavior (`"ReturnToZero"`, `"HoldLastResult"`). Default: `"ReturnToZero"`.
|
|
189
208
|
: outMin (number | null) : Minimum output limit (less than outMax). Default: null.
|
|
190
209
|
: outMax (number | null) : Maximum output limit (greater than outMin). Default: null.
|
|
@@ -193,13 +212,44 @@ Implements a PID controller with deadband, output limits, and tuning.
|
|
|
193
212
|
: run (boolean) : Enable (true) or disable (false) PID calculation. Default: true.
|
|
194
213
|
|
|
195
214
|
### Details
|
|
196
|
-
Calculates PID control output based on numeric `msg.payload`, setpoint, and gains (`kp`, `ki`, `kd`).
|
|
197
215
|
|
|
198
|
-
|
|
216
|
+
**Core Algorithm**
|
|
217
|
+
Calculates PID control output every cycle based on `msg.payload` (input), setpoint, and configurable gains (`kp`, `ki`, `kd`). Supports both **Direct Action** (cooling: error↑ → output↑) and **Reverse Action** (heating: error↑ → output↓) modes.
|
|
218
|
+
|
|
219
|
+
**Error Calculation**
|
|
220
|
+
- Reverse Action (heating): `error = setpoint - input`
|
|
221
|
+
- Direct Action (cooling): `error = input - setpoint`
|
|
222
|
+
|
|
223
|
+
**PID Terms**
|
|
224
|
+
- **P (Proportional)**: Immediate response to error. `pGain = Kp × error`
|
|
225
|
+
- **I (Integral)**: Removes steady-state offset by accumulating error over time with anti-windup clamping to prevent excessive accumulation when output limits are active.
|
|
226
|
+
- **D (Derivative)**: Low-pass filtered to prevent noise amplification. Uses exponential smoothing (0.1 new + 0.9 old).
|
|
227
|
+
|
|
228
|
+
**Setpoint Rate Limiting**
|
|
229
|
+
Smoothly ramps setpoint changes at configured rate (units per second) to prevent integrator wind-up and thermal shock. With `setpointRateLimit = 0`, setpoint changes immediately.
|
|
230
|
+
|
|
231
|
+
**Deadband**
|
|
232
|
+
No output generated when input is within ±deadband of setpoint. Behavior controlled by `dbBehavior`:
|
|
233
|
+
- `ReturnToZero`: Output becomes 0
|
|
234
|
+
- `HoldLastResult`: Output holds previous value
|
|
235
|
+
|
|
236
|
+
**Output Limiting**
|
|
237
|
+
Hard limits applied before output transmission. `maxChange` further limits rate of change to prevent abrupt jumps (units per second).
|
|
238
|
+
|
|
239
|
+
**Relay Auto-Tuning (Ziegler-Nichols)**
|
|
240
|
+
Send `{context: "tune", payload: true}` to start automatic tuning. Uses bang-bang relay control to oscillate the system and measure:
|
|
241
|
+
- **Tu** (Ultimate Period): Time for one complete oscillation cycle
|
|
242
|
+
- **Ku** (Ultimate Gain): Peak oscillation amplitude
|
|
243
|
+
|
|
244
|
+
Calculates conservative "no overshoot" gains:
|
|
245
|
+
- `Kp = 0.2 × Ku`
|
|
246
|
+
- `Ki = 0.4 × Kp / Tu`
|
|
247
|
+
- `Kd = 0.066 × Kp × Tu`
|
|
199
248
|
|
|
200
|
-
|
|
249
|
+
Tuning data persists across input messages but resets on flow restart. Progress shown in status bar during measurement.
|
|
201
250
|
|
|
202
|
-
|
|
251
|
+
**Output**
|
|
252
|
+
Sends every calculation cycle: `{ payload: number, diagnostics: {...} }` showing P/I/D breakdown and current state. On tuning completion: `{ tuneResult: {...} }` with calculated gains and oscillation count.
|
|
203
253
|
|
|
204
254
|
### Error Handling
|
|
205
255
|
- Missing `msg`: No output, red status (`invalid message`).
|