@bldgblocks/node-red-contrib-control 0.1.26 → 0.1.28
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/analog-switch-block.html +8 -9
- package/nodes/analog-switch-block.js +0 -23
- package/nodes/average-block.html +1 -1
- package/nodes/average-block.js +9 -9
- 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 +57 -28
- package/nodes/delay-block.html +1 -1
- package/nodes/delay-block.js +8 -8
- package/nodes/enum-switch-block.html +157 -0
- package/nodes/enum-switch-block.js +101 -0
- package/nodes/hysteresis-block.html +1 -1
- package/nodes/hysteresis-block.js +17 -13
- package/nodes/latch-block.html +55 -0
- package/nodes/latch-block.js +77 -0
- package/nodes/max-block.js +2 -4
- package/nodes/memory-block.js +3 -6
- package/nodes/min-block.js +2 -4
- package/nodes/minmax-block.js +9 -9
- package/nodes/on-change-block.html +0 -1
- package/nodes/on-change-block.js +4 -16
- package/nodes/pid-block.html +102 -80
- package/nodes/pid-block.js +121 -111
- 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 +105 -56
- package/nodes/utils.js +1 -22
- package/package.json +5 -1
package/nodes/tstat-block.js
CHANGED
|
@@ -10,24 +10,20 @@ module.exports = function(RED) {
|
|
|
10
10
|
node.algorithm = config.algorithm;
|
|
11
11
|
node.name = config.name;
|
|
12
12
|
|
|
13
|
-
function shouldEvaluate(type) { return type === "flow" || type === "global" || type === "msg"; }
|
|
14
|
-
|
|
15
|
-
const typedProperties = ['setpoint', 'heatingSetpoint', 'coolingSetpoint', 'coolingOn', 'coolingOff',
|
|
16
|
-
'heatingOff', 'heatingOn', 'diff', 'anticipator', 'ignoreAnticipatorCycles'];
|
|
17
|
-
|
|
18
13
|
// Evaluate typed-input properties
|
|
19
14
|
try {
|
|
20
|
-
|
|
21
|
-
node.
|
|
22
|
-
node.
|
|
23
|
-
node.
|
|
24
|
-
node.
|
|
25
|
-
node.
|
|
26
|
-
node.
|
|
27
|
-
node.
|
|
28
|
-
node.
|
|
29
|
-
node.
|
|
30
|
-
node.
|
|
15
|
+
node.setpoint = parseFloat(RED.util.evaluateNodeProperty( config.setpoint, config.setpointType, node ));
|
|
16
|
+
node.heatingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.heatingSetpoint, config.heatingSetpointType, node ));
|
|
17
|
+
node.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node ));
|
|
18
|
+
node.coolingOn = parseFloat(RED.util.evaluateNodeProperty( config.coolingOn, config.coolingOnType, node ));
|
|
19
|
+
node.coolingOff = parseFloat(RED.util.evaluateNodeProperty( config.coolingOff, config.coolingOffType, node ));
|
|
20
|
+
node.heatingOff = parseFloat(RED.util.evaluateNodeProperty( config.heatingOff, config.heatingOffType, node ));
|
|
21
|
+
node.heatingOn = parseFloat(RED.util.evaluateNodeProperty( config.heatingOn, config.heatingOnType, node ));
|
|
22
|
+
node.diff = parseFloat(RED.util.evaluateNodeProperty( config.diff, config.diffType, node ));
|
|
23
|
+
node.anticipator = parseFloat(RED.util.evaluateNodeProperty( config.anticipator, config.anticipatorType, node ));
|
|
24
|
+
node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node ));
|
|
25
|
+
node.isHeating = RED.util.evaluateNodeProperty( config.isHeating, config.isHeatingType, node ) === true;
|
|
26
|
+
node.algorithm = RED.util.evaluateNodeProperty( config.algorithm, config.algorithmType, node );
|
|
31
27
|
} catch (err) {
|
|
32
28
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
33
29
|
}
|
|
@@ -51,17 +47,42 @@ module.exports = function(RED) {
|
|
|
51
47
|
|
|
52
48
|
// Update typed-input properties if needed
|
|
53
49
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
if (utils.requiresEvaluation(config.setpointType)) {
|
|
51
|
+
node.setpoint = parseFloat(RED.util.evaluateNodeProperty( config.setpoint, config.setpointType, node, msg ));
|
|
52
|
+
}
|
|
53
|
+
if (utils.requiresEvaluation(config.heatingSetpointType)) {
|
|
54
|
+
node.heatingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.heatingSetpoint, config.heatingSetpointType, node, msg ));
|
|
55
|
+
}
|
|
56
|
+
if (utils.requiresEvaluation(config.coolingSetpointType)) {
|
|
57
|
+
node.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node, msg ));
|
|
58
|
+
}
|
|
59
|
+
if (utils.requiresEvaluation(config.coolingOnType)) {
|
|
60
|
+
node.coolingOn = parseFloat(RED.util.evaluateNodeProperty( config.coolingOn, config.coolingOnType, node, msg ));
|
|
61
|
+
}
|
|
62
|
+
if (utils.requiresEvaluation(config.coolingOffType)) {
|
|
63
|
+
node.coolingOff = parseFloat(RED.util.evaluateNodeProperty( config.coolingOff, config.coolingOffType, node, msg ));
|
|
64
|
+
}
|
|
65
|
+
if (utils.requiresEvaluation(config.heatingOffType)) {
|
|
66
|
+
node.heatingOff = parseFloat(RED.util.evaluateNodeProperty( config.heatingOff, config.heatingOffType, node, msg ));
|
|
67
|
+
}
|
|
68
|
+
if (utils.requiresEvaluation(config.heatingOnType)) {
|
|
69
|
+
node.heatingOn = parseFloat(RED.util.evaluateNodeProperty( config.heatingOn, config.heatingOnType, node, msg ));
|
|
70
|
+
}
|
|
71
|
+
if (utils.requiresEvaluation(config.diffType)) {
|
|
72
|
+
node.diff = parseFloat(RED.util.evaluateNodeProperty( config.diff, config.diffType, node, msg ));
|
|
73
|
+
}
|
|
74
|
+
if (utils.requiresEvaluation(config.anticipatorType)) {
|
|
75
|
+
node.anticipator = parseFloat(RED.util.evaluateNodeProperty( config.anticipator, config.anticipatorType, node, msg ));
|
|
76
|
+
}
|
|
77
|
+
if (utils.requiresEvaluation(config.ignoreAnticipatorCyclesType)) {
|
|
78
|
+
node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node, msg ));
|
|
79
|
+
}
|
|
80
|
+
if (utils.requiresEvaluation(config.isHeatingType)) {
|
|
81
|
+
node.isHeating = RED.util.evaluateNodeProperty( config.isHeating, config.isHeatingType, node, msg ) === true;
|
|
82
|
+
}
|
|
83
|
+
if (utils.requiresEvaluation(config.algorithmType)) {
|
|
84
|
+
node.algorithm = RED.util.evaluateNodeProperty( config.algorithm, config.algorithmType, node, msg );
|
|
85
|
+
}
|
|
65
86
|
} catch (err) {
|
|
66
87
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
67
88
|
if (done) done();
|
|
@@ -184,14 +205,14 @@ module.exports = function(RED) {
|
|
|
184
205
|
}
|
|
185
206
|
|
|
186
207
|
if (!msg.hasOwnProperty("payload")) {
|
|
187
|
-
node.status({ fill: "red", shape: "ring", text: "missing
|
|
208
|
+
node.status({ fill: "red", shape: "ring", text: "missing payload" });
|
|
188
209
|
if (done) done();
|
|
189
210
|
return;
|
|
190
211
|
}
|
|
191
212
|
|
|
192
213
|
const input = parseFloat(msg.payload);
|
|
193
214
|
if (isNaN(input)) {
|
|
194
|
-
node.status({ fill: "red", shape: "ring", text: "invalid
|
|
215
|
+
node.status({ fill: "red", shape: "ring", text: "invalid payload" });
|
|
195
216
|
if (done) done();
|
|
196
217
|
return;
|
|
197
218
|
}
|
|
@@ -223,31 +244,52 @@ module.exports = function(RED) {
|
|
|
223
244
|
|
|
224
245
|
lastAbove = above;
|
|
225
246
|
lastBelow = below;
|
|
247
|
+
let delta = 0;
|
|
248
|
+
let hiValue = 0;
|
|
249
|
+
let loValue = 0;
|
|
250
|
+
let hiOffValue = 0;
|
|
251
|
+
let loOffValue = 0;
|
|
252
|
+
let activeHeatingSetpoint = 0;
|
|
253
|
+
let activeCoolingSetpoint = 0;
|
|
226
254
|
|
|
227
255
|
// Main thermostat logic
|
|
256
|
+
// The Tstat node does not control heating/cooling mode, only operates heating or cooling according to the mode set and respective setpoints.
|
|
228
257
|
if (node.algorithm === "single") {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
258
|
+
// Note:
|
|
259
|
+
// Make sure your mode selection is handled upstream and does not osciallate modes.
|
|
260
|
+
// This was changed to allow for broader anticipator authority, or even negative (overshoot) so duty cycle can be better managed.
|
|
261
|
+
// So the same setpoint can be used year round and maintain tight control.
|
|
262
|
+
// Alternatively, you would need a larger diff value to prevent oscillation.
|
|
263
|
+
delta = node.diff / 2;
|
|
264
|
+
hiValue = node.setpoint + delta;
|
|
265
|
+
loValue = node.setpoint - delta;
|
|
266
|
+
hiOffValue = node.setpoint + effectiveAnticipator;
|
|
267
|
+
loOffValue = node.setpoint - effectiveAnticipator;
|
|
268
|
+
activeHeatingSetpoint = node.setpoint;
|
|
269
|
+
activeCoolingSetpoint = node.setpoint;
|
|
234
270
|
|
|
235
|
-
if (
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
} else if (above && input < hiOffValue) {
|
|
271
|
+
if (isHeating) {
|
|
272
|
+
if (input < loValue) {
|
|
273
|
+
below = true;
|
|
274
|
+
} else if (below && input > loOffValue) {
|
|
275
|
+
below = false;
|
|
276
|
+
}
|
|
242
277
|
above = false;
|
|
243
|
-
} else
|
|
278
|
+
} else {
|
|
279
|
+
if (input > hiValue) {
|
|
280
|
+
above = true;
|
|
281
|
+
} else if (above && input < hiOffValue) {
|
|
282
|
+
above = false;
|
|
283
|
+
}
|
|
244
284
|
below = false;
|
|
245
285
|
}
|
|
246
286
|
} else if (node.algorithm === "split") {
|
|
287
|
+
activeHeatingSetpoint = node.heatingSetpoint;
|
|
288
|
+
activeCoolingSetpoint = node.coolingSetpoint;
|
|
247
289
|
if (node.isHeating) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
290
|
+
delta = node.diff / 2;
|
|
291
|
+
loValue = node.heatingSetpoint - delta;
|
|
292
|
+
loOffValue = node.heatingSetpoint - effectiveAnticipator;
|
|
251
293
|
|
|
252
294
|
if (input < loValue) {
|
|
253
295
|
below = true;
|
|
@@ -256,9 +298,9 @@ module.exports = function(RED) {
|
|
|
256
298
|
}
|
|
257
299
|
above = false;
|
|
258
300
|
} else {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
301
|
+
delta = node.diff / 2;
|
|
302
|
+
hiValue = node.coolingSetpoint + delta;
|
|
303
|
+
hiOffValue = node.coolingSetpoint + effectiveAnticipator;
|
|
262
304
|
|
|
263
305
|
if (input > hiValue) {
|
|
264
306
|
above = true;
|
|
@@ -268,6 +310,8 @@ module.exports = function(RED) {
|
|
|
268
310
|
below = false;
|
|
269
311
|
}
|
|
270
312
|
} else if (node.algorithm === "specified") {
|
|
313
|
+
activeHeatingSetpoint = node.heatingOn;
|
|
314
|
+
activeCoolingSetpoint = node.coolingOn;
|
|
271
315
|
if (node.isHeating) {
|
|
272
316
|
if (input < node.heatingOn) {
|
|
273
317
|
below = true;
|
|
@@ -298,20 +342,25 @@ module.exports = function(RED) {
|
|
|
298
342
|
};
|
|
299
343
|
|
|
300
344
|
// Add algorithm-specific status
|
|
345
|
+
statusInfo.activeHeatingSetpoint = activeHeatingSetpoint;
|
|
346
|
+
statusInfo.activeCoolingSetpoint = activeCoolingSetpoint;
|
|
347
|
+
statusInfo.diff = node.diff;
|
|
348
|
+
statusInfo.anticipator = node.anticipator;
|
|
349
|
+
statusInfo.loValue = loValue;
|
|
350
|
+
statusInfo.hiValue = hiValue;
|
|
351
|
+
statusInfo.loOffValue = loOffValue;
|
|
352
|
+
statusInfo.hiOffValue = hiOffValue;
|
|
353
|
+
|
|
301
354
|
if (node.algorithm === "single") {
|
|
302
355
|
statusInfo.setpoint = node.setpoint;
|
|
303
|
-
statusInfo.diff = node.diff;
|
|
304
|
-
statusInfo.anticipator = node.anticipator;
|
|
305
356
|
} else if (node.algorithm === "split") {
|
|
306
357
|
statusInfo.heatingSetpoint = node.heatingSetpoint;
|
|
307
358
|
statusInfo.coolingSetpoint = node.coolingSetpoint;
|
|
308
|
-
statusInfo.diff = node.diff;
|
|
309
|
-
statusInfo.anticipator = node.anticipator;
|
|
310
359
|
} else {
|
|
311
|
-
statusInfo.
|
|
312
|
-
statusInfo.
|
|
313
|
-
statusInfo.
|
|
314
|
-
statusInfo.
|
|
360
|
+
statusInfo.hiValue = node.coolingOn;
|
|
361
|
+
statusInfo.hiOffValue = node.coolingOff;
|
|
362
|
+
statusInfo.loOffValue = node.heatingOff;
|
|
363
|
+
statusInfo.loValue = node.heatingOn;
|
|
315
364
|
statusInfo.anticipator = node.anticipator;
|
|
316
365
|
}
|
|
317
366
|
|
package/nodes/utils.js
CHANGED
|
@@ -2,30 +2,9 @@ module.exports = function(RED) {
|
|
|
2
2
|
function requiresEvaluation(type) { return type === "flow" || type === "global" || type === "msg"; }
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
function evaluateProperties(node, config, properties, msg = null, initialize = true) {
|
|
6
|
-
const results = {};
|
|
7
|
-
|
|
8
|
-
properties.forEach(prop => {
|
|
9
|
-
const type = config[`${prop}Type`];
|
|
10
|
-
let value;
|
|
11
|
-
|
|
12
|
-
if (type === "msg" && msg === null) {
|
|
13
|
-
value = null;
|
|
14
|
-
} else if (initialize || requiresEvaluation(type)) {
|
|
15
|
-
value = RED.util.evaluateNodeProperty(config[prop], type, node, msg);
|
|
16
|
-
} else {
|
|
17
|
-
value = config[prop];
|
|
18
|
-
}
|
|
19
|
-
results[prop] = value;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return results;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
5
|
// const utils = require('./utils')(RED);
|
|
26
6
|
|
|
27
7
|
return {
|
|
28
|
-
requiresEvaluation
|
|
29
|
-
evaluateProperties
|
|
8
|
+
requiresEvaluation
|
|
30
9
|
};
|
|
31
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bldgblocks/node-red-contrib-control",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Sedona-inspired control nodes for Node-RED",
|
|
5
5
|
"keywords": [ "node-red", "sedona", "control", "hvac" ],
|
|
6
6
|
"files": ["nodes/*.js", "nodes/*.html"],
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
"delay-block": "nodes/delay-block.js",
|
|
30
30
|
"divide-block": "nodes/divide-block.js",
|
|
31
31
|
"edge-block": "nodes/edge-block.js",
|
|
32
|
+
"enum-switch-block": "nodes/enum-switch-block.js",
|
|
32
33
|
"frequency-block": "nodes/frequency-block.js",
|
|
33
34
|
"hysteresis-block": "nodes/hysteresis-block.js",
|
|
34
35
|
"interpolate-block": "nodes/interpolate-block.js",
|
|
36
|
+
"latch-block": "nodes/latch-block.js",
|
|
35
37
|
"load-sequence-block": "nodes/load-sequence-block.js",
|
|
36
38
|
"max-block": "nodes/max-block.js",
|
|
37
39
|
"memory-block": "nodes/memory-block.js",
|
|
@@ -47,10 +49,12 @@
|
|
|
47
49
|
"pid-block": "nodes/pid-block.js",
|
|
48
50
|
"priority-block": "nodes/priority-block.js",
|
|
49
51
|
"rate-limit-block": "nodes/rate-limit-block.js",
|
|
52
|
+
"rate-of-change-block": "nodes/rate-of-change-block.js",
|
|
50
53
|
"round-block": "nodes/round-block.js",
|
|
51
54
|
"saw-tooth-wave-block": "nodes/saw-tooth-wave-block.js",
|
|
52
55
|
"scale-range-block": "nodes/scale-range-block.js",
|
|
53
56
|
"sine-wave-block": "nodes/sine-wave-block.js",
|
|
57
|
+
"string-builder-block": "nodes/string-builder-block.js",
|
|
54
58
|
"subtract-block": "nodes/subtract-block.js",
|
|
55
59
|
"thermistor-block": "nodes/thermistor-block.js",
|
|
56
60
|
"tick-tock-block": "nodes/tick-tock-block.js",
|