@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.
Files changed (110) hide show
  1. package/nodes/accumulate-block.html +18 -8
  2. package/nodes/accumulate-block.js +39 -44
  3. package/nodes/add-block.html +1 -1
  4. package/nodes/add-block.js +18 -11
  5. package/nodes/alarm-collector.html +260 -0
  6. package/nodes/alarm-collector.js +292 -0
  7. package/nodes/alarm-config.html +129 -0
  8. package/nodes/alarm-config.js +126 -0
  9. package/nodes/alarm-service.html +96 -0
  10. package/nodes/alarm-service.js +142 -0
  11. package/nodes/analog-switch-block.js +25 -36
  12. package/nodes/and-block.js +44 -15
  13. package/nodes/average-block.js +46 -41
  14. package/nodes/boolean-switch-block.js +10 -28
  15. package/nodes/boolean-to-number-block.html +18 -5
  16. package/nodes/boolean-to-number-block.js +24 -16
  17. package/nodes/cache-block.js +24 -37
  18. package/nodes/call-status-block.html +91 -32
  19. package/nodes/call-status-block.js +398 -115
  20. package/nodes/changeover-block.html +5 -0
  21. package/nodes/changeover-block.js +167 -162
  22. package/nodes/comment-block.html +1 -1
  23. package/nodes/comment-block.js +14 -9
  24. package/nodes/compare-block.html +14 -4
  25. package/nodes/compare-block.js +23 -18
  26. package/nodes/contextual-label-block.html +5 -0
  27. package/nodes/contextual-label-block.js +6 -16
  28. package/nodes/convert-block.html +25 -39
  29. package/nodes/convert-block.js +31 -16
  30. package/nodes/count-block.html +11 -5
  31. package/nodes/count-block.js +34 -32
  32. package/nodes/delay-block.js +58 -53
  33. package/nodes/divide-block.js +43 -45
  34. package/nodes/edge-block.html +17 -10
  35. package/nodes/edge-block.js +43 -41
  36. package/nodes/enum-switch-block.js +6 -6
  37. package/nodes/frequency-block.html +6 -1
  38. package/nodes/frequency-block.js +64 -74
  39. package/nodes/global-getter.html +51 -15
  40. package/nodes/global-getter.js +43 -13
  41. package/nodes/global-setter.html +1 -1
  42. package/nodes/global-setter.js +40 -12
  43. package/nodes/history-buffer.html +96 -0
  44. package/nodes/history-buffer.js +464 -0
  45. package/nodes/history-collector.html +29 -1
  46. package/nodes/history-collector.js +46 -16
  47. package/nodes/history-config.html +13 -1
  48. package/nodes/history-service.html +84 -0
  49. package/nodes/history-service.js +66 -0
  50. package/nodes/hysteresis-block.html +5 -0
  51. package/nodes/hysteresis-block.js +13 -16
  52. package/nodes/interpolate-block.html +20 -2
  53. package/nodes/interpolate-block.js +39 -50
  54. package/nodes/join.html +78 -0
  55. package/nodes/join.js +78 -0
  56. package/nodes/latch-block.js +12 -14
  57. package/nodes/load-sequence-block.js +102 -110
  58. package/nodes/max-block.js +26 -26
  59. package/nodes/memory-block.js +57 -58
  60. package/nodes/min-block.js +26 -25
  61. package/nodes/minmax-block.js +35 -34
  62. package/nodes/modulo-block.js +45 -43
  63. package/nodes/multiply-block.js +43 -41
  64. package/nodes/negate-block.html +17 -7
  65. package/nodes/negate-block.js +25 -19
  66. package/nodes/network-point-read.html +128 -0
  67. package/nodes/network-point-read.js +230 -0
  68. package/nodes/{network-register.html → network-point-register.html} +94 -7
  69. package/nodes/{network-register.js → network-point-register.js} +18 -4
  70. package/nodes/network-point-write.html +149 -0
  71. package/nodes/network-point-write.js +222 -0
  72. package/nodes/network-service-bridge.html +131 -0
  73. package/nodes/network-service-bridge.js +376 -0
  74. package/nodes/network-service-read.html +81 -0
  75. package/nodes/{network-read.js → network-service-read.js} +4 -3
  76. package/nodes/{network-point-registry.html → network-service-registry.html} +19 -4
  77. package/nodes/{network-point-registry.js → network-service-registry.js} +7 -2
  78. package/nodes/network-service-write.html +89 -0
  79. package/nodes/{network-write.js → network-service-write.js} +3 -3
  80. package/nodes/nullify-block.js +13 -15
  81. package/nodes/on-change-block.html +17 -9
  82. package/nodes/on-change-block.js +49 -46
  83. package/nodes/oneshot-block.html +13 -10
  84. package/nodes/oneshot-block.js +57 -75
  85. package/nodes/or-block.js +44 -15
  86. package/nodes/pid-block.html +54 -4
  87. package/nodes/pid-block.js +459 -248
  88. package/nodes/priority-block.js +24 -35
  89. package/nodes/rate-limit-block.js +70 -72
  90. package/nodes/rate-of-change-block.html +33 -14
  91. package/nodes/rate-of-change-block.js +74 -62
  92. package/nodes/round-block.html +14 -9
  93. package/nodes/round-block.js +32 -25
  94. package/nodes/saw-tooth-wave-block.js +49 -76
  95. package/nodes/scale-range-block.html +12 -6
  96. package/nodes/scale-range-block.js +46 -39
  97. package/nodes/sine-wave-block.js +49 -57
  98. package/nodes/string-builder-block.js +6 -6
  99. package/nodes/subtract-block.js +38 -34
  100. package/nodes/thermistor-block.js +44 -44
  101. package/nodes/tick-tock-block.js +32 -32
  102. package/nodes/time-sequence-block.js +30 -42
  103. package/nodes/triangle-wave-block.js +49 -69
  104. package/nodes/tstat-block.js +34 -44
  105. package/nodes/units-block.html +90 -69
  106. package/nodes/units-block.js +22 -30
  107. package/nodes/utils.js +206 -3
  108. package/package.json +14 -6
  109. package/nodes/network-read.html +0 -56
  110. package/nodes/network-write.html +0 -65
@@ -8,23 +8,23 @@ module.exports = function(RED) {
8
8
  const node = this;
9
9
 
10
10
  // Initialize runtime state
11
- node.runtime = {
12
- name: config.name,
13
- initWindow: parseFloat(config.initWindow),
14
- lastTemperature: null,
15
- lastModeChange: 0,
16
- setpoint: parseFloat(config.setpoint),
17
- heatingSetpoint: parseFloat(config.heatingSetpoint),
18
- coolingSetpoint: parseFloat(config.coolingSetpoint),
19
- swapTime: parseFloat(config.swapTime),
20
- deadband: parseFloat(config.deadband),
21
- extent: parseFloat(config.extent),
22
- minTempSetpoint: parseFloat(config.minTempSetpoint),
23
- maxTempSetpoint: parseFloat(config.maxTempSetpoint),
24
- algorithm: config.algorithm,
25
- operationMode: config.operationMode,
26
- currentMode: config.operationMode === "cool" ? "cooling" : "heating",
27
- };
11
+ // Initialize state
12
+ node.name = config.name;
13
+ node.inputProperty = config.inputProperty || "payload";
14
+ node.initWindow = parseFloat(config.initWindow);
15
+ node.lastTemperature = null;
16
+ node.lastModeChange = 0;
17
+ node.setpoint = parseFloat(config.setpoint);
18
+ node.heatingSetpoint = parseFloat(config.heatingSetpoint);
19
+ node.coolingSetpoint = parseFloat(config.coolingSetpoint);
20
+ node.swapTime = parseFloat(config.swapTime);
21
+ node.deadband = parseFloat(config.deadband);
22
+ node.extent = parseFloat(config.extent);
23
+ node.minTempSetpoint = parseFloat(config.minTempSetpoint);
24
+ node.maxTempSetpoint = parseFloat(config.maxTempSetpoint);
25
+ node.algorithm = config.algorithm;
26
+ node.operationMode = config.operationMode;
27
+ node.currentMode = config.operationMode === "cool" ? "cooling" : "heating";
28
28
 
29
29
  // Initialize state
30
30
  let initComplete = false;
@@ -38,7 +38,7 @@ module.exports = function(RED) {
38
38
  send = send || function() { node.send.apply(node, arguments); };
39
39
 
40
40
  if (!msg) {
41
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
41
+ utils.setStatusError(node, "invalid message");
42
42
  if (done) done();
43
43
  return;
44
44
  }
@@ -48,7 +48,7 @@ module.exports = function(RED) {
48
48
  // Check busy lock
49
49
  if (node.isBusy) {
50
50
  // Update status to let user know they are pushing too fast
51
- node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
51
+ utils.setStatusBusy(node, "busy - dropped msg");
52
52
  if (done) done();
53
53
  return;
54
54
  }
@@ -63,85 +63,85 @@ module.exports = function(RED) {
63
63
  utils.requiresEvaluation(config.setpointType)
64
64
  ? utils.evaluateNodeProperty(config.setpoint, config.setpointType, node, msg)
65
65
  .then(val => parseFloat(val))
66
- : Promise.resolve(node.runtime.setpoint),
66
+ : Promise.resolve(node.setpoint),
67
67
  );
68
68
 
69
69
  evaluations.push(
70
70
  utils.requiresEvaluation(config.heatingSetpointType)
71
71
  ? utils.evaluateNodeProperty(config.heatingSetpoint, config.heatingSetpointType, node, msg)
72
72
  .then(val => parseFloat(val))
73
- : Promise.resolve(node.runtime.heatingSetpoint),
73
+ : Promise.resolve(node.heatingSetpoint),
74
74
  );
75
75
 
76
76
  evaluations.push(
77
77
  utils.requiresEvaluation(config.coolingSetpointType)
78
78
  ? utils.evaluateNodeProperty(config.coolingSetpoint, config.coolingSetpointType, node, msg)
79
79
  .then(val => parseFloat(val))
80
- : Promise.resolve(node.runtime.coolingSetpoint),
80
+ : Promise.resolve(node.coolingSetpoint),
81
81
  );
82
82
 
83
83
  evaluations.push(
84
84
  utils.requiresEvaluation(config.swapTimeType)
85
85
  ? utils.evaluateNodeProperty(config.swapTime, config.swapTimeType, node, msg)
86
86
  .then(val => parseFloat(val))
87
- : Promise.resolve(node.runtime.swapTime),
87
+ : Promise.resolve(node.swapTime),
88
88
  );
89
89
 
90
90
  evaluations.push(
91
91
  utils.requiresEvaluation(config.deadbandType)
92
92
  ? utils.evaluateNodeProperty(config.deadband, config.deadbandType, node, msg)
93
93
  .then(val => parseFloat(val))
94
- : Promise.resolve(node.runtime.deadband),
94
+ : Promise.resolve(node.deadband),
95
95
  );
96
96
 
97
97
  evaluations.push(
98
98
  utils.requiresEvaluation(config.extentType)
99
99
  ? utils.evaluateNodeProperty(config.extent, config.extentType, node, msg)
100
100
  .then(val => parseFloat(val))
101
- : Promise.resolve(node.runtime.extent),
101
+ : Promise.resolve(node.extent),
102
102
  );
103
103
 
104
104
  evaluations.push(
105
105
  utils.requiresEvaluation(config.minTempSetpointType)
106
106
  ? utils.evaluateNodeProperty(config.minTempSetpoint, config.minTempSetpointType, node, msg)
107
107
  .then(val => parseFloat(val))
108
- : Promise.resolve(node.runtime.minTempSetpoint),
108
+ : Promise.resolve(node.minTempSetpoint),
109
109
  );
110
110
 
111
111
  evaluations.push(
112
112
  utils.requiresEvaluation(config.maxTempSetpointType)
113
113
  ? utils.evaluateNodeProperty(config.maxTempSetpoint, config.maxTempSetpointType, node, msg)
114
114
  .then(val => parseFloat(val))
115
- : Promise.resolve(node.runtime.maxTempSetpoint),
115
+ : Promise.resolve(node.maxTempSetpoint),
116
116
  );
117
117
 
118
118
  evaluations.push(
119
119
  utils.requiresEvaluation(config.algorithmType)
120
120
  ? utils.evaluateNodeProperty(config.algorithm, config.algorithmType, node, msg)
121
- : Promise.resolve(node.runtime.algorithm),
121
+ : Promise.resolve(node.algorithm),
122
122
  );
123
123
 
124
124
  evaluations.push(
125
125
  utils.requiresEvaluation(config.operationModeType)
126
126
  ? utils.evaluateNodeProperty(config.operationMode, config.operationModeType, node, msg)
127
- : Promise.resolve(node.runtime.operationMode),
127
+ : Promise.resolve(node.operationMode),
128
128
  );
129
129
 
130
130
  const results = await Promise.all(evaluations);
131
131
 
132
132
  // Update runtime with evaluated values
133
133
 
134
- if (!isNaN(results[0])) node.runtime.setpoint = results[0];
135
- if (!isNaN(results[1])) node.runtime.heatingSetpoint = results[1];
136
- if (!isNaN(results[2])) node.runtime.coolingSetpoint = results[2];
137
- if (!isNaN(results[3])) node.runtime.swapTime = results[3];
138
- if (!isNaN(results[4])) node.runtime.deadband = results[4];
139
- if (!isNaN(results[5])) node.runtime.extent = results[5];
140
- if (!isNaN(results[6])) node.runtime.minTempSetpoint = results[6];
141
- if (!isNaN(results[7])) node.runtime.maxTempSetpoint = results[7];
142
- if (results[8]) node.runtime.algorithm = results[8];
143
- if (results[9]) node.runtime.operationMode = results[9];
144
- node.runtime.currentMode = node.runtime.operationMode === "cool" ? "cooling" : "heating";
134
+ if (!isNaN(results[0])) node.setpoint = results[0];
135
+ if (!isNaN(results[1])) node.heatingSetpoint = results[1];
136
+ if (!isNaN(results[2])) node.coolingSetpoint = results[2];
137
+ if (!isNaN(results[3])) node.swapTime = results[3];
138
+ if (!isNaN(results[4])) node.deadband = results[4];
139
+ if (!isNaN(results[5])) node.extent = results[5];
140
+ if (!isNaN(results[6])) node.minTempSetpoint = results[6];
141
+ if (!isNaN(results[7])) node.maxTempSetpoint = results[7];
142
+ if (results[8]) node.algorithm = results[8];
143
+ if (results[9]) node.operationMode = results[9];
144
+ node.currentMode = node.operationMode === "cool" ? "cooling" : "heating";
145
145
 
146
146
  } catch (err) {
147
147
  node.error(`Error evaluating properties: ${err.message}`);
@@ -153,27 +153,27 @@ module.exports = function(RED) {
153
153
  }
154
154
 
155
155
  // Validate
156
- if (node.runtime.coolingSetpoint < node.runtime.heatingSetpoint
157
- || node.runtime.maxTempSetpoint < node.runtime.minTempSetpoint
158
- || node.runtime.deadband <= 0 || node.runtime.extent < 0) {
159
- node.status({ fill: "red", shape: "ring", text: "error validating properties, check setpoints" });
156
+ if (node.coolingSetpoint < node.heatingSetpoint
157
+ || node.maxTempSetpoint < node.minTempSetpoint
158
+ || node.deadband <= 0 || node.extent < 0) {
159
+ utils.setStatusError(node, "error validating properties, check setpoints");
160
160
  if (done) done(err);
161
161
  return;
162
162
  }
163
163
 
164
- if (node.runtime.swapTime < 60) {
165
- node.runtime.swapTime = 60;
166
- node.status({ fill: "red", shape: "ring", text: "swapTime below 60s, using 60" });
164
+ if (node.swapTime < 60) {
165
+ node.swapTime = 60;
166
+ utils.setStatusError(node, "swapTime below 60s, using 60");
167
167
  }
168
168
 
169
- if (node.runtime.coolingSetpoint < node.runtime.heatingSetpoint) {
170
- node.runtime.coolingSetpoint = node.runtime.heatingSetpoint + 4;
171
- node.status({ fill: "red", shape: "ring", text: "invalid setpoints, using fallback" });
169
+ if (node.coolingSetpoint < node.heatingSetpoint) {
170
+ node.coolingSetpoint = node.heatingSetpoint + 4;
171
+ utils.setStatusError(node, "invalid setpoints, using fallback");
172
172
  }
173
173
 
174
174
  if (msg.hasOwnProperty("context")) {
175
175
  if (!msg.hasOwnProperty("payload")) {
176
- node.status({ fill: "red", shape: "ring", text: `missing payload for ${msg.context}` });
176
+ utils.setStatusError(node, `missing payload for ${msg.context}`);
177
177
  if (done) done();
178
178
  return;
179
179
  }
@@ -182,113 +182,113 @@ module.exports = function(RED) {
182
182
  switch (msg.context) {
183
183
  case "operationMode":
184
184
  if (!["auto", "heat", "cool"].includes(msg.payload)) {
185
- node.status({ fill: "red", shape: "ring", text: "invalid operationMode" });
185
+ utils.setStatusError(node, "invalid operationMode");
186
186
  if (done) done();
187
187
  return;
188
188
  }
189
- node.runtime.operationMode = msg.payload;
190
- node.status({ fill: "green", shape: "dot", text: `in: operationMode=${msg.payload}, out: ${node.runtime.currentMode}` });
189
+ node.operationMode = msg.payload;
190
+ utils.setStatusOK(node, `in: operationMode=${msg.payload}, out: ${node.currentMode}`);
191
191
  break;
192
192
  case "algorithm":
193
193
  if (!["single", "split"].includes(msg.payload)) {
194
- node.status({ fill: "red", shape: "ring", text: "invalid algorithm" });
194
+ utils.setStatusError(node, "invalid algorithm");
195
195
  if (done) done();
196
196
  return;
197
197
  }
198
- node.runtime.algorithm = msg.payload;
199
- node.status({ fill: "green", shape: "dot", text: `in: algorithm=${msg.payload}, out: ${node.runtime.currentMode}` });
198
+ node.algorithm = msg.payload;
199
+ utils.setStatusOK(node, `in: algorithm=${msg.payload}, out: ${node.currentMode}`);
200
200
  break;
201
201
  case "setpoint":
202
- if (isNaN(value) || value < node.runtime.minTempSetpoint || value > node.runtime.maxTempSetpoint) {
203
- node.status({ fill: "red", shape: "ring", text: "invalid setpoint" });
202
+ if (isNaN(value) || value < node.minTempSetpoint || value > node.maxTempSetpoint) {
203
+ utils.setStatusError(node, "invalid setpoint");
204
204
  if (done) done();
205
205
  return;
206
206
  }
207
- node.runtime.setpoint = value.toString();
208
- node.runtime.setpointType = "num";
209
- node.status({ fill: "green", shape: "dot", text: `in: setpoint=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
207
+ node.setpoint = value.toString();
208
+ node.setpointType = "num";
209
+ utils.setStatusOK(node, `in: setpoint=${value.toFixed(1)}, out: ${node.currentMode}`);
210
210
  break;
211
211
  case "deadband":
212
212
  if (isNaN(value) || value <= 0) {
213
- node.status({ fill: "red", shape: "ring", text: "invalid deadband" });
213
+ utils.setStatusError(node, "invalid deadband");
214
214
  if (done) done();
215
215
  return;
216
216
  }
217
- node.runtime.deadband = value;
218
- node.status({ fill: "green", shape: "dot", text: `in: deadband=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
217
+ node.deadband = value;
218
+ utils.setStatusOK(node, `in: deadband=${value.toFixed(1)}, out: ${node.currentMode}`);
219
219
  break;
220
220
  case "heatingSetpoint":
221
- if (isNaN(value) || value < node.runtime.minTempSetpoint || value > node.runtime.maxTempSetpoint || value > node.runtime.coolingSetpoint) {
222
- node.status({ fill: "red", shape: "ring", text: "invalid heatingSetpoint" });
221
+ if (isNaN(value) || value < node.minTempSetpoint || value > node.maxTempSetpoint || value > node.coolingSetpoint) {
222
+ utils.setStatusError(node, "invalid heatingSetpoint");
223
223
  if (done) done();
224
224
  return;
225
225
  }
226
- node.runtime.heatingSetpoint = value.toString();
227
- node.runtime.heatingSetpointType = "num";
228
- node.status({ fill: "green", shape: "dot", text: `in: heatingSetpoint=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
226
+ node.heatingSetpoint = value.toString();
227
+ node.heatingSetpointType = "num";
228
+ utils.setStatusOK(node, `in: heatingSetpoint=${value.toFixed(1)}, out: ${node.currentMode}`);
229
229
  break;
230
230
  case "coolingSetpoint":
231
- if (isNaN(value) || value < node.runtime.minTempSetpoint || value > node.runtime.maxTempSetpoint || value < node.runtime.heatingSetpoint) {
232
- node.status({ fill: "red", shape: "ring", text: "invalid coolingSetpoint" });
231
+ if (isNaN(value) || value < node.minTempSetpoint || value > node.maxTempSetpoint || value < node.heatingSetpoint) {
232
+ utils.setStatusError(node, "invalid coolingSetpoint");
233
233
  if (done) done();
234
234
  return;
235
235
  }
236
- node.runtime.coolingSetpoint = value.toString();
237
- node.runtime.coolingSetpointType = "num";
238
- node.status({ fill: "green", shape: "dot", text: `in: coolingSetpoint=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
236
+ node.coolingSetpoint = value.toString();
237
+ node.coolingSetpointType = "num";
238
+ utils.setStatusOK(node, `in: coolingSetpoint=${value.toFixed(1)}, out: ${node.currentMode}`);
239
239
  break;
240
240
  case "extent":
241
241
  if (isNaN(value) || value < 0) {
242
- node.status({ fill: "red", shape: "ring", text: "invalid extent" });
242
+ utils.setStatusError(node, "invalid extent");
243
243
  if (done) done();
244
244
  return;
245
245
  }
246
- node.runtime.extent = value;
247
- node.status({ fill: "green", shape: "dot", text: `in: extent=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
246
+ node.extent = value;
247
+ utils.setStatusOK(node, `in: extent=${value.toFixed(1)}, out: ${node.currentMode}`);
248
248
  break;
249
249
  case "swapTime":
250
250
  if (isNaN(value) || value < 60) {
251
- node.status({ fill: "red", shape: "ring", text: "invalid swapTime, minimum 60s" });
251
+ utils.setStatusError(node, "invalid swapTime, minimum 60s");
252
252
  if (done) done();
253
253
  return;
254
254
  }
255
- node.runtime.swapTime = value.toString();
256
- node.runtime.swapTimeType = "num";
257
- node.status({ fill: "green", shape: "dot", text: `in: swapTime=${value.toFixed(0)}, out: ${node.runtime.currentMode}` });
255
+ node.swapTime = value.toString();
256
+ node.swapTimeType = "num";
257
+ utils.setStatusOK(node, `in: swapTime=${value.toFixed(0)}, out: ${node.currentMode}`);
258
258
  break;
259
259
  case "minTempSetpoint":
260
- if (isNaN(value) || value >= node.runtime.maxTempSetpoint ||
261
- (node.runtime.algorithm === "single" && value > node.runtime.setpoint) ||
262
- (node.runtime.algorithm === "split" && (value > node.runtime.heatingSetpoint || value > node.runtime.coolingSetpoint))) {
263
- node.status({ fill: "red", shape: "ring", text: "invalid minTempSetpoint" });
260
+ if (isNaN(value) || value >= node.maxTempSetpoint ||
261
+ (node.algorithm === "single" && value > node.setpoint) ||
262
+ (node.algorithm === "split" && (value > node.heatingSetpoint || value > node.coolingSetpoint))) {
263
+ utils.setStatusError(node, "invalid minTempSetpoint");
264
264
  if (done) done();
265
265
  return;
266
266
  }
267
- node.runtime.minTempSetpoint = value;
268
- node.status({ fill: "green", shape: "dot", text: `in: minTempSetpoint=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
267
+ node.minTempSetpoint = value;
268
+ utils.setStatusOK(node, `in: minTempSetpoint=${value.toFixed(1)}, out: ${node.currentMode}`);
269
269
  break;
270
270
  case "maxTempSetpoint":
271
- if (isNaN(value) || value <= node.runtime.minTempSetpoint ||
272
- (node.runtime.algorithm === "single" && value < node.runtime.setpoint) ||
273
- (node.runtime.algorithm === "split" && (value < node.runtime.heatingSetpoint || value < node.runtime.coolingSetpoint))) {
274
- node.status({ fill: "red", shape: "ring", text: "invalid maxTempSetpoint" });
271
+ if (isNaN(value) || value <= node.minTempSetpoint ||
272
+ (node.algorithm === "single" && value < node.setpoint) ||
273
+ (node.algorithm === "split" && (value < node.heatingSetpoint || value < node.coolingSetpoint))) {
274
+ utils.setStatusError(node, "invalid maxTempSetpoint");
275
275
  if (done) done();
276
276
  return;
277
277
  }
278
- node.runtime.maxTempSetpoint = value;
279
- node.status({ fill: "green", shape: "dot", text: `in: maxTempSetpoint=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
278
+ node.maxTempSetpoint = value;
279
+ utils.setStatusOK(node, `in: maxTempSetpoint=${value.toFixed(1)}, out: ${node.currentMode}`);
280
280
  break;
281
281
  case "initWindow":
282
282
  if (isNaN(value) || value < 0) {
283
- node.status({ fill: "red", shape: "ring", text: "invalid initWindow" });
283
+ utils.setStatusError(node, "invalid initWindow");
284
284
  if (done) done();
285
285
  return;
286
286
  }
287
- node.runtime.initWindow = value;
288
- node.status({ fill: "green", shape: "dot", text: `in: initWindow=${value.toFixed(0)}, out: ${node.runtime.currentMode}` });
287
+ node.initWindow = value;
288
+ utils.setStatusOK(node, `in: initWindow=${value.toFixed(0)}, out: ${node.currentMode}`);
289
289
  break;
290
290
  default:
291
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
291
+ utils.setStatusWarn(node, "unknown context");
292
292
  if (done) done();
293
293
  return;
294
294
  }
@@ -301,24 +301,29 @@ module.exports = function(RED) {
301
301
  }
302
302
 
303
303
  if (!msg.hasOwnProperty("payload")) {
304
- node.status({ fill: "red", shape: "ring", text: "missing temperature payload property" });
304
+ utils.setStatusError(node, "missing temperature payload property");
305
305
  if (done) done();
306
306
  return;
307
307
  }
308
308
 
309
- let input = parseFloat(msg.payload);
309
+ let input;
310
+ try {
311
+ input = parseFloat(RED.util.getMessageProperty(msg, node.inputProperty));
312
+ } catch (err) {
313
+ input = NaN;
314
+ }
310
315
  if (isNaN(input)) {
311
- node.status({ fill: "red", shape: "ring", text: "invalid temperature payload" });
316
+ utils.setStatusError(node, "missing or invalid input property");
312
317
  if (done) done();
313
318
  return;
314
319
  }
315
320
 
316
- if (node.runtime.lastTemperature !== input) {
317
- node.runtime.lastTemperature = input;
321
+ if (node.lastTemperature !== input) {
322
+ node.lastTemperature = input;
318
323
  }
319
324
 
320
325
  const now = Date.now() / 1000;
321
- if (!initComplete && now - initStartTime >= node.runtime.initWindow) {
326
+ if (!initComplete && now - initStartTime >= node.initWindow) {
322
327
  initComplete = true;
323
328
  evaluateInitialMode();
324
329
  }
@@ -335,25 +340,25 @@ module.exports = function(RED) {
335
340
  });
336
341
 
337
342
  function evaluateInitialMode() {
338
- if (node.runtime.lastTemperature === null) return;
339
- const temp = node.runtime.lastTemperature;
340
- let newMode = node.runtime.currentMode;
343
+ if (node.lastTemperature === null) return;
344
+ const temp = node.lastTemperature;
345
+ let newMode = node.currentMode;
341
346
 
342
- if (node.runtime.operationMode === "heat") {
347
+ if (node.operationMode === "heat") {
343
348
  newMode = "heating";
344
- } else if (node.runtime.operationMode === "cool") {
349
+ } else if (node.operationMode === "cool") {
345
350
  newMode = "cooling";
346
351
  } else {
347
352
  let heatingThreshold, coolingThreshold;
348
- if (node.runtime.algorithm === "single") {
349
- heatingThreshold = node.runtime.setpoint - node.runtime.deadband / 2;
350
- coolingThreshold = node.runtime.setpoint + node.runtime.deadband / 2;
351
- } else if (node.runtime.algorithm === "split") {
352
- heatingThreshold = node.runtime.heatingSetpoint - node.runtime.extent;
353
- coolingThreshold = node.runtime.coolingSetpoint + node.runtime.extent;
354
- } else if (node.runtime.algorithm === "specified") {
355
- heatingThreshold = node.runtime.heatingOn - node.runtime.extent;
356
- coolingThreshold = node.runtime.coolingOn + node.runtime.extent;
353
+ if (node.algorithm === "single") {
354
+ heatingThreshold = node.setpoint - node.deadband / 2;
355
+ coolingThreshold = node.setpoint + node.deadband / 2;
356
+ } else if (node.algorithm === "split") {
357
+ heatingThreshold = node.heatingSetpoint - node.extent;
358
+ coolingThreshold = node.coolingSetpoint + node.extent;
359
+ } else if (node.algorithm === "specified") {
360
+ heatingThreshold = node.heatingOn - node.extent;
361
+ coolingThreshold = node.coolingOn + node.extent;
357
362
  }
358
363
 
359
364
  if (temp < heatingThreshold) {
@@ -363,48 +368,48 @@ module.exports = function(RED) {
363
368
  }
364
369
  }
365
370
 
366
- node.runtime.currentMode = newMode;
367
- node.runtime.lastModeChange = Date.now() / 1000;
371
+ node.currentMode = newMode;
372
+ node.lastModeChange = Date.now() / 1000;
368
373
  }
369
374
 
370
375
  function evaluateState() {
371
376
  const now = Date.now() / 1000;
372
377
  if (!initComplete) return null;
373
378
 
374
- let newMode = node.runtime.currentMode;
375
- if (node.runtime.operationMode === "heat") {
379
+ let newMode = node.currentMode;
380
+ if (node.operationMode === "heat") {
376
381
  newMode = "heating";
377
382
  conditionStartTime = null;
378
383
  pendingMode = null;
379
- } else if (node.runtime.operationMode === "cool") {
384
+ } else if (node.operationMode === "cool") {
380
385
  newMode = "cooling";
381
386
  conditionStartTime = null;
382
387
  pendingMode = null;
383
- } else if (node.runtime.lastTemperature !== null) {
388
+ } else if (node.lastTemperature !== null) {
384
389
  let heatingThreshold, coolingThreshold;
385
- if (node.runtime.algorithm === "single") {
386
- heatingThreshold = node.runtime.setpoint - node.runtime.deadband / 2;
387
- coolingThreshold = node.runtime.setpoint + node.runtime.deadband / 2;
388
- } else if (node.runtime.algorithm === "split") {
389
- heatingThreshold = node.runtime.heatingSetpoint - node.runtime.extent;
390
- coolingThreshold = node.runtime.coolingSetpoint + node.runtime.extent;
391
- } else if (node.runtime.algorithm === "specified") {
392
- heatingThreshold = node.runtime.heatingOn - node.runtime.extent;
393
- coolingThreshold = node.runtime.coolingOn + node.runtime.extent;
390
+ if (node.algorithm === "single") {
391
+ heatingThreshold = node.setpoint - node.deadband / 2;
392
+ coolingThreshold = node.setpoint + node.deadband / 2;
393
+ } else if (node.algorithm === "split") {
394
+ heatingThreshold = node.heatingSetpoint - node.extent;
395
+ coolingThreshold = node.coolingSetpoint + node.extent;
396
+ } else if (node.algorithm === "specified") {
397
+ heatingThreshold = node.heatingOn - node.extent;
398
+ coolingThreshold = node.coolingOn + node.extent;
394
399
  }
395
400
 
396
- let desiredMode = node.runtime.currentMode;
397
- if (node.runtime.lastTemperature < heatingThreshold) {
401
+ let desiredMode = node.currentMode;
402
+ if (node.lastTemperature < heatingThreshold) {
398
403
  desiredMode = "heating";
399
- } else if (node.runtime.lastTemperature > coolingThreshold) {
404
+ } else if (node.lastTemperature > coolingThreshold) {
400
405
  desiredMode = "cooling";
401
406
  }
402
407
 
403
- if (desiredMode !== node.runtime.currentMode) {
408
+ if (desiredMode !== node.currentMode) {
404
409
  if (pendingMode !== desiredMode) {
405
410
  conditionStartTime = now;
406
411
  pendingMode = desiredMode;
407
- } else if (conditionStartTime && now - conditionStartTime >= node.runtime.swapTime) {
412
+ } else if (conditionStartTime && now - conditionStartTime >= node.swapTime) {
408
413
  newMode = desiredMode;
409
414
  conditionStartTime = null;
410
415
  pendingMode = null;
@@ -415,26 +420,26 @@ module.exports = function(RED) {
415
420
  }
416
421
  }
417
422
 
418
- if (newMode !== node.runtime.currentMode) {
419
- node.runtime.currentMode = newMode;
420
- node.runtime.lastModeChange = now;
423
+ if (newMode !== node.currentMode) {
424
+ node.currentMode = newMode;
425
+ node.lastModeChange = now;
421
426
  }
422
427
 
423
428
  return null;
424
429
  }
425
430
 
426
431
  function buildOutputs() {
427
- const isHeating = node.runtime.currentMode === "heating";
432
+ const isHeating = node.currentMode === "heating";
428
433
  let effectiveHeatingSetpoint, effectiveCoolingSetpoint;
429
- if (node.runtime.algorithm === "single") {
430
- effectiveHeatingSetpoint = node.runtime.setpoint - node.runtime.deadband / 2;
431
- effectiveCoolingSetpoint = node.runtime.setpoint + node.runtime.deadband / 2;
432
- } else if (node.runtime.algorithm === "split") {
433
- effectiveHeatingSetpoint = node.runtime.heatingSetpoint;
434
- effectiveCoolingSetpoint = node.runtime.coolingSetpoint;
435
- } else if (node.runtime.algorithm === "specified") {
436
- effectiveHeatingSetpoint = node.runtime.heatingOn;
437
- effectiveCoolingSetpoint = node.runtime.coolingOn;
434
+ if (node.algorithm === "single") {
435
+ effectiveHeatingSetpoint = node.setpoint - node.deadband / 2;
436
+ effectiveCoolingSetpoint = node.setpoint + node.deadband / 2;
437
+ } else if (node.algorithm === "split") {
438
+ effectiveHeatingSetpoint = node.heatingSetpoint;
439
+ effectiveCoolingSetpoint = node.coolingSetpoint;
440
+ } else if (node.algorithm === "specified") {
441
+ effectiveHeatingSetpoint = node.heatingOn;
442
+ effectiveCoolingSetpoint = node.coolingOn;
438
443
  }
439
444
 
440
445
  return [
@@ -442,11 +447,11 @@ module.exports = function(RED) {
442
447
  payload: isHeating,
443
448
  context: "isHeating",
444
449
  status: {
445
- mode: node.runtime.currentMode,
450
+ mode: node.currentMode,
446
451
  isHeating,
447
452
  heatingSetpoint: effectiveHeatingSetpoint,
448
453
  coolingSetpoint: effectiveCoolingSetpoint,
449
- temperature: node.runtime.lastTemperature
454
+ temperature: node.lastTemperature
450
455
  }
451
456
  },
452
457
  ];
@@ -454,21 +459,21 @@ module.exports = function(RED) {
454
459
 
455
460
  function updateStatus() {
456
461
  const now = Date.now() / 1000;
457
- const inInitWindow = !initComplete && now - initStartTime < node.runtime.initWindow;
462
+ const inInitWindow = !initComplete && now - initStartTime < node.initWindow;
458
463
 
459
464
  if (inInitWindow) {
460
- node.status({ fill: "yellow", shape: "ring", text: `initializing, out: ${node.runtime.currentMode}` });
465
+ utils.setStatusBusy(node, `initializing, out: ${node.currentMode}`);
461
466
  } else {
462
- let statusText = `in: temp=${node.runtime.lastTemperature !== null ? node.runtime.lastTemperature.toFixed(1) : "unknown"}, out: ${node.runtime.currentMode}`;
467
+ let statusText = `in: temp=${node.lastTemperature !== null ? node.lastTemperature.toFixed(1) : "unknown"}, out: ${node.currentMode}`;
463
468
  if (pendingMode && conditionStartTime) {
464
- const remaining = Math.max(0, node.runtime.swapTime - (now - conditionStartTime));
469
+ const remaining = Math.max(0, node.swapTime - (now - conditionStartTime));
465
470
  statusText += `, pending: ${pendingMode} in ${remaining.toFixed(0)}s`;
466
471
  }
467
- node.status({
468
- fill: "blue",
469
- shape: now - node.runtime.lastModeChange < 1 ? "dot" : "ring",
470
- text: statusText
471
- });
472
+ if (now - node.lastModeChange < 1) {
473
+ utils.setStatusChanged(node, statusText);
474
+ } else {
475
+ utils.setStatusUnchanged(node, statusText);
476
+ }
472
477
  }
473
478
  }
474
479
 
@@ -75,7 +75,7 @@ Used for flow annotations or debugging without requiring input to trigger status
75
75
  - Blue (dot): State changed
76
76
  - Blue (ring): State unchanged
77
77
  - Red (ring): Error
78
- - Yellow: Warning
78
+ - Yellow (ring): Warning
79
79
 
80
80
  ### References
81
81
  - [Node-RED Documentation](https://nodered.org/docs/)