@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.
@@ -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
- const evaluatedValues = utils.evaluateProperties(node, config, typedProperties, null, true);
21
- node.setpoint = parseFloat(evaluatedValues.setpoint);
22
- node.heatingSetpoint = parseFloat(evaluatedValues.heatingSetpoint);
23
- node.coolingSetpoint = parseFloat(evaluatedValues.coolingSetpoint);
24
- node.coolingOn = parseFloat(evaluatedValues.coolingOn);
25
- node.coolingOff = parseFloat(evaluatedValues.coolingOff);
26
- node.heatingOff = parseFloat(evaluatedValues.heatingOff);
27
- node.heatingOn = parseFloat(evaluatedValues.heatingOn);
28
- node.diff = parseFloat(evaluatedValues.diff);
29
- node.anticipator = parseFloat(evaluatedValues.anticipator);
30
- node.ignoreAnticipatorCycles = Math.floor(evaluatedValues.ignoreAnticipatorCycles);
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
- const evaluatedValues = utils.evaluateProperties(node, config, typedProperties, msg);
55
- node.setpoint = parseFloat(evaluatedValues.setpoint);
56
- node.heatingSetpoint = parseFloat(evaluatedValues.heatingSetpoint);
57
- node.coolingSetpoint = parseFloat(evaluatedValues.coolingSetpoint);
58
- node.coolingOn = parseFloat(evaluatedValues.coolingOn);
59
- node.coolingOff = parseFloat(evaluatedValues.coolingOff);
60
- node.heatingOff = parseFloat(evaluatedValues.heatingOff);
61
- node.heatingOn = parseFloat(evaluatedValues.heatingOn);
62
- node.diff = parseFloat(evaluatedValues.diff);
63
- node.anticipator = parseFloat(evaluatedValues.anticipator);
64
- node.ignoreAnticipatorCycles = Math.floor(evaluatedValues.ignoreAnticipatorCycles);
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 input" });
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 input" });
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
- const delta = node.diff / 2;
230
- const hiValue = node.setpoint + delta;
231
- const loValue = node.setpoint - delta;
232
- const hiOffValue = node.setpoint + effectiveAnticipator;
233
- const loOffValue = node.setpoint - effectiveAnticipator;
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 (input > hiValue) {
236
- above = true;
237
- below = false;
238
- } else if (input < loValue) {
239
- above = false;
240
- below = true;
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 if (below && input > loOffValue) {
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
- const delta = node.diff / 2;
249
- const loValue = node.heatingSetpoint - delta;
250
- const loOffValue = node.heatingSetpoint - effectiveAnticipator;
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
- const delta = node.diff / 2;
260
- const hiValue = node.coolingSetpoint + delta;
261
- const hiOffValue = node.coolingSetpoint + effectiveAnticipator;
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.coolingOn = node.coolingOn;
312
- statusInfo.coolingOff = node.coolingOff;
313
- statusInfo.heatingOff = node.heatingOff;
314
- statusInfo.heatingOn = node.heatingOn;
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.26",
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",