@bldgblocks/node-red-contrib-control 0.1.34 → 0.1.37
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 +464 -0
- package/nodes/history-collector.html +29 -1
- package/nodes/history-collector.js +46 -16
- package/nodes/history-config.html +13 -1
- package/nodes/history-service.html +84 -0
- package/nodes/history-service.js +66 -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/priority-block.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function PriorityBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
const node = this;
|
|
5
7
|
const context = this.context();
|
|
6
8
|
|
|
7
9
|
// Initialize runtime state
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
10
|
+
// Initialize state
|
|
11
|
+
node.name = config.name;
|
|
11
12
|
|
|
12
13
|
// Initialize state from context or defaults
|
|
13
14
|
let priorities = context.get("priorities") || {
|
|
@@ -37,14 +38,14 @@ module.exports = function(RED) {
|
|
|
37
38
|
|
|
38
39
|
// Guard against invalid message
|
|
39
40
|
if (!msg) {
|
|
40
|
-
|
|
41
|
+
utils.setStatusError(node, "invalid message");
|
|
41
42
|
if (done) done();
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// Validate payload
|
|
46
47
|
if (!msg.hasOwnProperty("payload")) {
|
|
47
|
-
|
|
48
|
+
utils.setStatusError(node, "missing payload");
|
|
48
49
|
if (done) done();
|
|
49
50
|
return;
|
|
50
51
|
}
|
|
@@ -72,7 +73,7 @@ module.exports = function(RED) {
|
|
|
72
73
|
context.set("defaultValue", defaultValue);
|
|
73
74
|
context.set("fallbackValue", fallbackValue);
|
|
74
75
|
context.set("messages", messages);
|
|
75
|
-
|
|
76
|
+
utils.setStatusOK(node, "all slots cleared");
|
|
76
77
|
} else if (typeof clear === "string" && isValidSlot(clear)) {
|
|
77
78
|
if (clear.startsWith("priority")) priorities[clear] = null;
|
|
78
79
|
else if (clear === "default") defaultValue = null;
|
|
@@ -82,7 +83,7 @@ module.exports = function(RED) {
|
|
|
82
83
|
context.set("defaultValue", defaultValue);
|
|
83
84
|
context.set("fallbackValue", fallbackValue);
|
|
84
85
|
context.set("messages", messages);
|
|
85
|
-
|
|
86
|
+
utils.setStatusOK(node, `${clear} cleared`);
|
|
86
87
|
} else if (Array.isArray(clear) && clear.every(isValidSlot)) {
|
|
87
88
|
clear.forEach(slot => {
|
|
88
89
|
if (slot.startsWith("priority")) priorities[slot] = null;
|
|
@@ -94,16 +95,16 @@ module.exports = function(RED) {
|
|
|
94
95
|
context.set("defaultValue", defaultValue);
|
|
95
96
|
context.set("fallbackValue", fallbackValue);
|
|
96
97
|
context.set("messages", messages);
|
|
97
|
-
|
|
98
|
+
utils.setStatusOK(node, `${clear.join(", ")} cleared`);
|
|
98
99
|
} else {
|
|
99
|
-
|
|
100
|
+
utils.setStatusError(node, "invalid clear");
|
|
100
101
|
if (done) done();
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
103
104
|
} else if (msg.payload === "clear") {
|
|
104
105
|
// Handle string "clear" with msg.context
|
|
105
106
|
if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
|
|
106
|
-
|
|
107
|
+
utils.setStatusError(node, "missing or invalid context for clear");
|
|
107
108
|
if (done) done();
|
|
108
109
|
return;
|
|
109
110
|
}
|
|
@@ -117,16 +118,16 @@ module.exports = function(RED) {
|
|
|
117
118
|
context.set("defaultValue", defaultValue);
|
|
118
119
|
context.set("fallbackValue", fallbackValue);
|
|
119
120
|
context.set("messages", messages);
|
|
120
|
-
|
|
121
|
+
utils.setStatusOK(node, `${contextMsg} cleared`);
|
|
121
122
|
} else {
|
|
122
|
-
|
|
123
|
+
utils.setStatusError(node, "invalid clear context");
|
|
123
124
|
if (done) done();
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
127
|
} else {
|
|
127
128
|
// Handle non-object, non-"clear" payloads
|
|
128
129
|
if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
|
|
129
|
-
|
|
130
|
+
utils.setStatusError(node, "missing or invalid context");
|
|
130
131
|
if (done) done();
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
@@ -135,7 +136,7 @@ module.exports = function(RED) {
|
|
|
135
136
|
const value = msg.payload === null ? null : typeof msg.payload === "number" ? parseFloat(msg.payload) : typeof msg.payload === "boolean" ? msg.payload : null;
|
|
136
137
|
|
|
137
138
|
if (value === null && msg.payload !== null) {
|
|
138
|
-
|
|
139
|
+
utils.setStatusError(node, `invalid ${contextMsg}`);
|
|
139
140
|
if (done) done();
|
|
140
141
|
return;
|
|
141
142
|
}
|
|
@@ -145,33 +146,24 @@ module.exports = function(RED) {
|
|
|
145
146
|
messages[contextMsg] = RED.util.cloneMessage(msg);
|
|
146
147
|
context.set("priorities", priorities);
|
|
147
148
|
context.set("messages", messages);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
shape: "dot",
|
|
151
|
-
text: value === null ? `${contextMsg} relinquished` : `${contextMsg}: ${typeof value === "number" ? value.toFixed(2) : value}`
|
|
152
|
-
});
|
|
149
|
+
const priorityText = value === null ? `${contextMsg} relinquished` : `${contextMsg}: ${typeof value === "number" ? value.toFixed(2) : value}`;
|
|
150
|
+
utils.setStatusOK(node, priorityText);
|
|
153
151
|
} else if (contextMsg === "default") {
|
|
154
152
|
defaultValue = value;
|
|
155
153
|
messages[contextMsg] = RED.util.cloneMessage(msg);
|
|
156
154
|
context.set("defaultValue", defaultValue);
|
|
157
155
|
context.set("messages", messages);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
shape: "dot",
|
|
161
|
-
text: value === null ? "default relinquished" : `default: ${typeof value === "number" ? value.toFixed(2) : value}`
|
|
162
|
-
});
|
|
156
|
+
const defaultText = value === null ? "default relinquished" : `default: ${typeof value === "number" ? value.toFixed(2) : value}`;
|
|
157
|
+
utils.setStatusOK(node, defaultText);
|
|
163
158
|
} else if (contextMsg === "fallback") {
|
|
164
159
|
fallbackValue = value;
|
|
165
160
|
messages[contextMsg] = RED.util.cloneMessage(msg);
|
|
166
161
|
context.set("fallbackValue", fallbackValue);
|
|
167
162
|
context.set("messages", messages);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
shape: "dot",
|
|
171
|
-
text: value === null ? "fallback relinquished" : `fallback: ${typeof value === "number" ? value.toFixed(2) : value}`
|
|
172
|
-
});
|
|
163
|
+
const fallbackText = value === null ? "fallback relinquished" : `fallback: ${typeof value === "number" ? value.toFixed(2) : value}`;
|
|
164
|
+
utils.setStatusOK(node, fallbackText);
|
|
173
165
|
} else {
|
|
174
|
-
|
|
166
|
+
utils.setStatusWarn(node, "unknown context");
|
|
175
167
|
if (done) done("Unknown context");
|
|
176
168
|
return;
|
|
177
169
|
}
|
|
@@ -182,11 +174,8 @@ module.exports = function(RED) {
|
|
|
182
174
|
send(currentOutput);
|
|
183
175
|
const inDisplay = typeof msg.payload === "number" ? msg.payload.toFixed(2) : typeof msg.payload === "object" ? JSON.stringify(msg.payload).slice(0, 20) : msg.payload;
|
|
184
176
|
const outDisplay = currentOutput.payload === null ? "null" : typeof currentOutput.payload === "number" ? currentOutput.payload.toFixed(2) : currentOutput.payload;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
shape: "dot",
|
|
188
|
-
text: `in: ${inDisplay}, out: ${outDisplay}, slot: ${currentOutput.diagnostics.activePriority || "none"}`
|
|
189
|
-
});
|
|
177
|
+
const statusText = `in: ${inDisplay}, out: ${outDisplay}, slot: ${currentOutput.diagnostics.activePriority || "none"}`;
|
|
178
|
+
utils.setStatusChanged(node, statusText);
|
|
190
179
|
|
|
191
180
|
if (done) done();
|
|
192
181
|
|
|
@@ -1,64 +1,64 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
2
3
|
function RateLimitBlockNode(config) {
|
|
3
4
|
RED.nodes.createNode(this, config);
|
|
4
5
|
const node = this;
|
|
5
6
|
|
|
6
7
|
// Initialize runtime state
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
8
|
+
// Initialize state
|
|
9
|
+
node.name = config.name;
|
|
10
|
+
node.mode = config.mode;
|
|
11
|
+
node.rate = parseFloat(config.rate);
|
|
12
|
+
node.interval = parseInt(config.interval);
|
|
13
|
+
node.threshold = parseFloat(config.threshold);
|
|
14
|
+
node.currentValue = 0;
|
|
15
|
+
node.targetValue = 0;
|
|
16
|
+
node.lastUpdate = Date.now();
|
|
17
|
+
node.lastInputMsg = null;
|
|
18
18
|
|
|
19
19
|
// Validate initial config
|
|
20
|
-
if (isNaN(node.
|
|
21
|
-
node.
|
|
22
|
-
|
|
20
|
+
if (isNaN(node.rate) || node.rate <= 0 || !isFinite(node.rate)) {
|
|
21
|
+
node.rate = 1.0;
|
|
22
|
+
utils.setStatusError(node, "invalid rate");
|
|
23
23
|
}
|
|
24
|
-
if (isNaN(node.
|
|
25
|
-
node.
|
|
26
|
-
|
|
24
|
+
if (isNaN(node.interval) || node.interval < 10 || !Number.isInteger(node.interval)) {
|
|
25
|
+
node.interval = 100;
|
|
26
|
+
utils.setStatusError(node, "invalid interval");
|
|
27
27
|
}
|
|
28
|
-
if (isNaN(node.
|
|
29
|
-
node.
|
|
30
|
-
|
|
28
|
+
if (isNaN(node.threshold) || node.threshold < 0 || !isFinite(node.threshold)) {
|
|
29
|
+
node.threshold = 5.0;
|
|
30
|
+
utils.setStatusError(node, "invalid threshold");
|
|
31
31
|
}
|
|
32
|
-
if (!["rate-limit", "threshold", "full-value"].includes(node.
|
|
33
|
-
node.
|
|
34
|
-
|
|
32
|
+
if (!["rate-limit", "threshold", "full-value"].includes(node.mode)) {
|
|
33
|
+
node.mode = "rate-limit";
|
|
34
|
+
utils.setStatusError(node, "invalid mode");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Set initial status
|
|
38
|
-
|
|
38
|
+
utils.setStatusOK(node, `mode: ${node.mode}, out: ${node.currentValue.toFixed(2)}`);
|
|
39
39
|
|
|
40
40
|
let updateTimer = null;
|
|
41
41
|
|
|
42
42
|
// Function to update output for rate-limit mode
|
|
43
43
|
function updateRateLimitOutput() {
|
|
44
|
-
if (!node.
|
|
44
|
+
if (!node.lastInputMsg) return;
|
|
45
45
|
const now = Date.now();
|
|
46
|
-
const elapsed = (now - node.
|
|
47
|
-
const maxChange = node.
|
|
48
|
-
let newValue = node.
|
|
49
|
-
|
|
50
|
-
if (node.
|
|
51
|
-
newValue = Math.min(node.
|
|
52
|
-
} else if (node.
|
|
53
|
-
newValue = Math.max(node.
|
|
46
|
+
const elapsed = (now - node.lastUpdate) / 1000; // Seconds
|
|
47
|
+
const maxChange = node.rate * elapsed;
|
|
48
|
+
let newValue = node.currentValue;
|
|
49
|
+
|
|
50
|
+
if (node.currentValue < node.targetValue) {
|
|
51
|
+
newValue = Math.min(node.currentValue + maxChange, node.targetValue);
|
|
52
|
+
} else if (node.currentValue > node.targetValue) {
|
|
53
|
+
newValue = Math.max(node.currentValue - maxChange, node.targetValue);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
if (newValue !== node.
|
|
57
|
-
node.
|
|
58
|
-
node.
|
|
59
|
-
const msg = RED.util.cloneMessage(node.
|
|
60
|
-
msg.payload = node.
|
|
61
|
-
|
|
56
|
+
if (newValue !== node.currentValue) {
|
|
57
|
+
node.currentValue = newValue;
|
|
58
|
+
node.lastUpdate = now;
|
|
59
|
+
const msg = RED.util.cloneMessage(node.lastInputMsg);
|
|
60
|
+
msg.payload = node.currentValue;
|
|
61
|
+
utils.setStatusOK(node, `mode: rate-limit, out: ${node.currentValue.toFixed(2)}`);
|
|
62
62
|
node.send(msg);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -66,8 +66,8 @@ module.exports = function(RED) {
|
|
|
66
66
|
// Start update timer for rate-limit mode
|
|
67
67
|
function startTimer() {
|
|
68
68
|
if (updateTimer) clearInterval(updateTimer);
|
|
69
|
-
if (node.
|
|
70
|
-
updateTimer = setInterval(updateRateLimitOutput, node.
|
|
69
|
+
if (node.mode === "rate-limit") {
|
|
70
|
+
updateTimer = setInterval(updateRateLimitOutput, node.interval);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -76,7 +76,7 @@ module.exports = function(RED) {
|
|
|
76
76
|
|
|
77
77
|
// Guard against invalid message
|
|
78
78
|
if (!msg) {
|
|
79
|
-
|
|
79
|
+
utils.setStatusError(node, "invalid message");
|
|
80
80
|
if (done) done();
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
@@ -84,54 +84,54 @@ module.exports = function(RED) {
|
|
|
84
84
|
// Handle context updates
|
|
85
85
|
if (msg.hasOwnProperty("context")) {
|
|
86
86
|
if (!msg.hasOwnProperty("payload")) {
|
|
87
|
-
|
|
87
|
+
utils.setStatusError(node, `missing payload for ${msg.context}`);
|
|
88
88
|
if (done) done();
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
91
|
switch (msg.context) {
|
|
92
92
|
case "mode":
|
|
93
93
|
if (!["rate-limit", "threshold", "full-value"].includes(msg.payload)) {
|
|
94
|
-
|
|
94
|
+
utils.setStatusError(node, "invalid mode");
|
|
95
95
|
if (done) done();
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
|
-
node.
|
|
98
|
+
node.mode = msg.payload;
|
|
99
99
|
startTimer();
|
|
100
|
-
|
|
100
|
+
utils.setStatusOK(node, `mode: ${node.mode}`);
|
|
101
101
|
break;
|
|
102
102
|
case "rate":
|
|
103
103
|
const rate = parseFloat(msg.payload);
|
|
104
104
|
if (isNaN(rate) || rate <= 0 || !isFinite(rate)) {
|
|
105
|
-
|
|
105
|
+
utils.setStatusError(node, "invalid rate");
|
|
106
106
|
if (done) done();
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
|
-
node.
|
|
110
|
-
|
|
109
|
+
node.rate = rate;
|
|
110
|
+
utils.setStatusOK(node, `rate: ${node.rate.toFixed(2)}`);
|
|
111
111
|
break;
|
|
112
112
|
case "interval":
|
|
113
113
|
const interval = parseInt(msg.payload);
|
|
114
114
|
if (isNaN(interval) || interval < 10 || !Number.isInteger(interval)) {
|
|
115
|
-
|
|
115
|
+
utils.setStatusError(node, "invalid interval");
|
|
116
116
|
if (done) done();
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
|
-
node.
|
|
119
|
+
node.interval = interval;
|
|
120
120
|
startTimer();
|
|
121
|
-
|
|
121
|
+
utils.setStatusOK(node, `interval: ${node.interval}`);
|
|
122
122
|
break;
|
|
123
123
|
case "threshold":
|
|
124
124
|
const threshold = parseFloat(msg.payload);
|
|
125
125
|
if (isNaN(threshold) || threshold < 0 || !isFinite(threshold)) {
|
|
126
|
-
|
|
126
|
+
utils.setStatusError(node, "invalid threshold");
|
|
127
127
|
if (done) done();
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
|
-
node.
|
|
131
|
-
|
|
130
|
+
node.threshold = threshold;
|
|
131
|
+
utils.setStatusOK(node, `threshold: ${node.threshold.toFixed(2)}`);
|
|
132
132
|
break;
|
|
133
133
|
default:
|
|
134
|
-
|
|
134
|
+
utils.setStatusWarn(node, "unknown context");
|
|
135
135
|
if (done) done("Unknown context");
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
@@ -141,35 +141,33 @@ module.exports = function(RED) {
|
|
|
141
141
|
|
|
142
142
|
// Validate input
|
|
143
143
|
if (typeof msg.payload !== "number" || isNaN(msg.payload) || !isFinite(msg.payload)) {
|
|
144
|
-
|
|
144
|
+
utils.setStatusError(node, "invalid input");
|
|
145
145
|
if (done) done();
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const inputValue = msg.payload;
|
|
150
|
-
node.
|
|
150
|
+
node.lastInputMsg = RED.util.cloneMessage(msg);
|
|
151
151
|
|
|
152
|
-
if (node.
|
|
153
|
-
node.
|
|
154
|
-
|
|
152
|
+
if (node.mode === "rate-limit") {
|
|
153
|
+
node.targetValue = inputValue;
|
|
154
|
+
utils.setStatusOK(node, `mode: rate-limit, target: ${node.targetValue.toFixed(2)}`);
|
|
155
155
|
updateRateLimitOutput();
|
|
156
156
|
startTimer();
|
|
157
|
-
} else if (node.
|
|
158
|
-
const diff = Math.abs(inputValue - node.
|
|
159
|
-
if (diff > node.
|
|
157
|
+
} else if (node.mode === "threshold") {
|
|
158
|
+
const diff = Math.abs(inputValue - node.currentValue);
|
|
159
|
+
if (diff > node.threshold) {
|
|
160
160
|
msg.payload = inputValue;
|
|
161
|
-
node.
|
|
162
|
-
|
|
161
|
+
node.currentValue = inputValue;
|
|
162
|
+
utils.setStatusChanged(node, `mode: threshold, out: ${node.currentValue.toFixed(2)}`);
|
|
163
163
|
send(msg);
|
|
164
164
|
} else {
|
|
165
|
-
|
|
166
|
-
});
|
|
165
|
+
utils.setStatusUnchanged(node, `mode: threshold, out: ${node.currentValue.toFixed(2)}`);
|
|
167
166
|
}
|
|
168
|
-
} else if (node.
|
|
169
|
-
node.
|
|
167
|
+
} else if (node.mode === "full-value") {
|
|
168
|
+
node.currentValue = inputValue;
|
|
170
169
|
msg.payload = inputValue;
|
|
171
|
-
|
|
172
|
-
});
|
|
170
|
+
utils.setStatusChanged(node, `mode: full-value, out: ${node.currentValue.toFixed(2)}`);
|
|
173
171
|
send(msg);
|
|
174
172
|
}
|
|
175
173
|
|
|
@@ -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
|
<div class="form-row">
|
|
7
11
|
<label for="node-input-sampleSize" title="Number of samples to track (minimum 2)"><i class="fa fa-list-ol"></i> Sample Size</label>
|
|
8
12
|
<input type="number" id="node-input-sampleSize" placeholder="10" min="2" step="1">
|
|
@@ -33,6 +37,7 @@
|
|
|
33
37
|
color: "#301934",
|
|
34
38
|
defaults: {
|
|
35
39
|
name: { value: "" },
|
|
40
|
+
inputProperty: { value: "payload" },
|
|
36
41
|
sampleSize: {
|
|
37
42
|
value: 10,
|
|
38
43
|
required: true,
|
|
@@ -80,31 +85,45 @@
|
|
|
80
85
|
</script>
|
|
81
86
|
|
|
82
87
|
<script type="text/markdown" data-help-name="rate-of-change-block">
|
|
83
|
-
Calculates the rate of
|
|
88
|
+
Calculates the rate of value change over time.
|
|
84
89
|
|
|
85
90
|
### Inputs
|
|
91
|
+
: input-property (number) : Numeric value to track for rate calculation, read from the configured Input Property.
|
|
86
92
|
: context (string) : Configures reset (`"reset"`), sample size (`"sampleSize"`), or units (`"units"`).
|
|
87
|
-
: payload (number) :
|
|
88
|
-
: timestamp (optional) : Custom timestamp for the reading.
|
|
93
|
+
: payload (number | boolean | string) : Value for configuration (numeric for rate, boolean for reset, string for units).
|
|
94
|
+
: timestamp (optional) : Custom timestamp for the reading (ms since epoch).
|
|
89
95
|
|
|
90
96
|
### Outputs
|
|
91
|
-
: payload (number | null) : Rate of change in
|
|
97
|
+
: payload (number | null) : Rate of change in value per time unit.
|
|
92
98
|
: samples (number) : Current number of samples in buffer.
|
|
93
|
-
: units (string) : Rate units ("
|
|
94
|
-
: currentValue (number) : Most recent
|
|
99
|
+
: units (string) : Rate units ("per second", "per minute", "per hour").
|
|
100
|
+
: currentValue (number) : Most recent value.
|
|
95
101
|
: timeSpan (number) : Time span of sample buffer in seconds.
|
|
96
102
|
|
|
103
|
+
### Properties
|
|
104
|
+
: name (string) : Display name in editor.
|
|
105
|
+
: inputProperty (string) : Message property to read values from (default: `payload`). Supports nested properties (e.g., `data.value`).
|
|
106
|
+
: sampleSize (number) : Number of samples to maintain (≥ 2).
|
|
107
|
+
: rateUnits (string) : Units for rate output (`"seconds"`, `"minutes"`, `"hours"`).
|
|
108
|
+
|
|
97
109
|
### Details
|
|
98
|
-
Tracks
|
|
110
|
+
Tracks value changes over a rolling window of samples. Calculates rate as `(last_value - first_value) / time_difference`.
|
|
99
111
|
|
|
100
|
-
Useful for detecting
|
|
112
|
+
Useful for detecting system issues like rapid changes, droop, or anomalies.
|
|
101
113
|
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
114
|
+
Configuration via `msg.context`:
|
|
115
|
+
- `"reset"`: Clears sample buffer (no output).
|
|
116
|
+
- `"sampleSize"`: Changes number of samples with numeric `msg.payload`.
|
|
117
|
+
- `"units"`: Changes rate units with `msg.payload` as `"seconds"`, `"minutes"`, or `"hours"`.
|
|
106
118
|
|
|
107
119
|
### Status
|
|
108
|
-
-
|
|
109
|
-
-
|
|
120
|
+
- Green (dot): Configuration update
|
|
121
|
+
- Blue (dot): State changed
|
|
122
|
+
- Blue (ring): State unchanged
|
|
123
|
+
- Red (ring): Error
|
|
124
|
+
- Yellow (ring): Warning
|
|
125
|
+
|
|
126
|
+
### References
|
|
127
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
128
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
110
129
|
</script>
|