@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.
@@ -21,7 +21,9 @@ module.exports = function(RED) {
21
21
  node.heatingOn = parseFloat(RED.util.evaluateNodeProperty( config.heatingOn, config.heatingOnType, node ));
22
22
  node.diff = parseFloat(RED.util.evaluateNodeProperty( config.diff, config.diffType, node ));
23
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 ));
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 );
25
27
  } catch (err) {
26
28
  node.error(`Error evaluating properties: ${err.message}`);
27
29
  }
@@ -75,6 +77,12 @@ module.exports = function(RED) {
75
77
  if (utils.requiresEvaluation(config.ignoreAnticipatorCyclesType)) {
76
78
  node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node, msg ));
77
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
+ }
78
86
  } catch (err) {
79
87
  node.error(`Error evaluating properties: ${err.message}`);
80
88
  if (done) done();
@@ -197,14 +205,14 @@ module.exports = function(RED) {
197
205
  }
198
206
 
199
207
  if (!msg.hasOwnProperty("payload")) {
200
- node.status({ fill: "red", shape: "ring", text: "missing input" });
208
+ node.status({ fill: "red", shape: "ring", text: "missing payload" });
201
209
  if (done) done();
202
210
  return;
203
211
  }
204
212
 
205
213
  const input = parseFloat(msg.payload);
206
214
  if (isNaN(input)) {
207
- node.status({ fill: "red", shape: "ring", text: "invalid input" });
215
+ node.status({ fill: "red", shape: "ring", text: "invalid payload" });
208
216
  if (done) done();
209
217
  return;
210
218
  }
@@ -236,31 +244,52 @@ module.exports = function(RED) {
236
244
 
237
245
  lastAbove = above;
238
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;
239
254
 
240
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.
241
257
  if (node.algorithm === "single") {
242
- const delta = node.diff / 2;
243
- const hiValue = node.setpoint + delta;
244
- const loValue = node.setpoint - delta;
245
- const hiOffValue = node.setpoint + effectiveAnticipator;
246
- 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;
247
270
 
248
- if (input > hiValue) {
249
- above = true;
250
- below = false;
251
- } else if (input < loValue) {
252
- above = false;
253
- below = true;
254
- } 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
+ }
255
277
  above = false;
256
- } else if (below && input > loOffValue) {
278
+ } else {
279
+ if (input > hiValue) {
280
+ above = true;
281
+ } else if (above && input < hiOffValue) {
282
+ above = false;
283
+ }
257
284
  below = false;
258
285
  }
259
286
  } else if (node.algorithm === "split") {
287
+ activeHeatingSetpoint = node.heatingSetpoint;
288
+ activeCoolingSetpoint = node.coolingSetpoint;
260
289
  if (node.isHeating) {
261
- const delta = node.diff / 2;
262
- const loValue = node.heatingSetpoint - delta;
263
- const loOffValue = node.heatingSetpoint - effectiveAnticipator;
290
+ delta = node.diff / 2;
291
+ loValue = node.heatingSetpoint - delta;
292
+ loOffValue = node.heatingSetpoint - effectiveAnticipator;
264
293
 
265
294
  if (input < loValue) {
266
295
  below = true;
@@ -269,9 +298,9 @@ module.exports = function(RED) {
269
298
  }
270
299
  above = false;
271
300
  } else {
272
- const delta = node.diff / 2;
273
- const hiValue = node.coolingSetpoint + delta;
274
- const hiOffValue = node.coolingSetpoint + effectiveAnticipator;
301
+ delta = node.diff / 2;
302
+ hiValue = node.coolingSetpoint + delta;
303
+ hiOffValue = node.coolingSetpoint + effectiveAnticipator;
275
304
 
276
305
  if (input > hiValue) {
277
306
  above = true;
@@ -281,6 +310,8 @@ module.exports = function(RED) {
281
310
  below = false;
282
311
  }
283
312
  } else if (node.algorithm === "specified") {
313
+ activeHeatingSetpoint = node.heatingOn;
314
+ activeCoolingSetpoint = node.coolingOn;
284
315
  if (node.isHeating) {
285
316
  if (input < node.heatingOn) {
286
317
  below = true;
@@ -311,20 +342,25 @@ module.exports = function(RED) {
311
342
  };
312
343
 
313
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
+
314
354
  if (node.algorithm === "single") {
315
355
  statusInfo.setpoint = node.setpoint;
316
- statusInfo.diff = node.diff;
317
- statusInfo.anticipator = node.anticipator;
318
356
  } else if (node.algorithm === "split") {
319
357
  statusInfo.heatingSetpoint = node.heatingSetpoint;
320
358
  statusInfo.coolingSetpoint = node.coolingSetpoint;
321
- statusInfo.diff = node.diff;
322
- statusInfo.anticipator = node.anticipator;
323
359
  } else {
324
- statusInfo.coolingOn = node.coolingOn;
325
- statusInfo.coolingOff = node.coolingOff;
326
- statusInfo.heatingOff = node.heatingOff;
327
- statusInfo.heatingOn = node.heatingOn;
360
+ statusInfo.hiValue = node.coolingOn;
361
+ statusInfo.hiOffValue = node.coolingOff;
362
+ statusInfo.loOffValue = node.heatingOff;
363
+ statusInfo.loValue = node.heatingOn;
328
364
  statusInfo.anticipator = node.anticipator;
329
365
  }
330
366
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bldgblocks/node-red-contrib-control",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
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"],
@@ -31,8 +31,11 @@
31
31
  "edge-block": "nodes/edge-block.js",
32
32
  "enum-switch-block": "nodes/enum-switch-block.js",
33
33
  "frequency-block": "nodes/frequency-block.js",
34
+ "global-getter": "nodes/global-getter.js",
35
+ "global-setter": "nodes/global-setter.js",
34
36
  "hysteresis-block": "nodes/hysteresis-block.js",
35
37
  "interpolate-block": "nodes/interpolate-block.js",
38
+ "latch-block": "nodes/latch-block.js",
36
39
  "load-sequence-block": "nodes/load-sequence-block.js",
37
40
  "max-block": "nodes/max-block.js",
38
41
  "memory-block": "nodes/memory-block.js",
@@ -48,10 +51,12 @@
48
51
  "pid-block": "nodes/pid-block.js",
49
52
  "priority-block": "nodes/priority-block.js",
50
53
  "rate-limit-block": "nodes/rate-limit-block.js",
54
+ "rate-of-change-block": "nodes/rate-of-change-block.js",
51
55
  "round-block": "nodes/round-block.js",
52
56
  "saw-tooth-wave-block": "nodes/saw-tooth-wave-block.js",
53
57
  "scale-range-block": "nodes/scale-range-block.js",
54
58
  "sine-wave-block": "nodes/sine-wave-block.js",
59
+ "string-builder-block": "nodes/string-builder-block.js",
55
60
  "subtract-block": "nodes/subtract-block.js",
56
61
  "thermistor-block": "nodes/thermistor-block.js",
57
62
  "tick-tock-block": "nodes/tick-tock-block.js",