@bldgblocks/node-red-contrib-control 0.1.34 → 0.1.36

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 +461 -0
  45. package/nodes/history-collector.html +29 -1
  46. package/nodes/history-collector.js +37 -16
  47. package/nodes/history-config.html +13 -1
  48. package/nodes/history-service.html +84 -0
  49. package/nodes/history-service.js +52 -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
@@ -1,32 +1,32 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
2
3
  function TriangleWaveBlockNode(config) {
3
4
  RED.nodes.createNode(this, config);
4
5
  const node = this;
5
6
 
6
7
  // Initialize runtime state
7
- node.runtime = {
8
- name: config.name || "",
9
- lowerLimit: parseFloat(config.lowerLimit),
10
- upperLimit: parseFloat(config.upperLimit),
11
- period: (parseFloat(config.period)) * (config.periodUnits === "minutes" ? 60000 : config.periodUnits === "seconds" ? 1000 : 1),
12
- periodUnits: config.periodUnits,
13
- lastExecution: Date.now(),
14
- phase: 0
15
- };
8
+ // Initialize state
9
+ node.name = config.name || "";
10
+ node.lowerLimit = parseFloat(config.lowerLimit);
11
+ node.upperLimit = parseFloat(config.upperLimit);
12
+ node.period = (parseFloat(config.period)) * (config.periodUnits === "minutes" ? 60000 : config.periodUnits === "seconds" ? 1000 : 1);
13
+ node.periodUnits = config.periodUnits;
14
+ node.lastExecution = Date.now();
15
+ node.phase = 0;
16
16
 
17
17
  // Validate initial config
18
- if (isNaN(node.runtime.lowerLimit) || isNaN(node.runtime.upperLimit) || !isFinite(node.runtime.lowerLimit) || !isFinite(node.runtime.upperLimit)) {
19
- node.runtime.lowerLimit = 0;
20
- node.runtime.upperLimit = 100;
21
- node.status({ fill: "red", shape: "ring", text: "invalid limits" });
22
- } else if (node.runtime.lowerLimit > node.runtime.upperLimit) {
23
- node.runtime.upperLimit = node.runtime.lowerLimit;
24
- node.status({ fill: "red", shape: "ring", text: "invalid limits" });
18
+ if (isNaN(node.lowerLimit) || isNaN(node.upperLimit) || !isFinite(node.lowerLimit) || !isFinite(node.upperLimit)) {
19
+ node.lowerLimit = 0;
20
+ node.upperLimit = 100;
21
+ utils.setStatusError(node, "invalid limits");
22
+ } else if (node.lowerLimit > node.upperLimit) {
23
+ node.upperLimit = node.lowerLimit;
24
+ utils.setStatusError(node, "invalid limits");
25
25
  }
26
- if (isNaN(node.runtime.period) || node.runtime.period <= 0 || !isFinite(node.runtime.period)) {
27
- node.runtime.period = 10000;
28
- node.runtime.periodUnits = "milliseconds";
29
- node.status({ fill: "red", shape: "ring", text: "invalid period" });
26
+ if (isNaN(node.period) || node.period <= 0 || !isFinite(node.period)) {
27
+ node.period = 10000;
28
+ node.periodUnits = "milliseconds";
29
+ utils.setStatusError(node, "invalid period");
30
30
  }
31
31
 
32
32
  node.on("input", function(msg, send, done) {
@@ -34,7 +34,7 @@ module.exports = function(RED) {
34
34
 
35
35
  // Guard against invalid message
36
36
  if (!msg) {
37
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
37
+ utils.setStatusError(node, "invalid message");
38
38
  if (done) done();
39
39
  return;
40
40
  }
@@ -42,74 +42,54 @@ module.exports = function(RED) {
42
42
  // Handle context updates
43
43
  if (msg.hasOwnProperty("context")) {
44
44
  if (!msg.hasOwnProperty("payload")) {
45
- node.status({ fill: "red", shape: "ring", text: `missing payload for ${msg.context}` });
45
+ utils.setStatusError(node, `missing payload for ${msg.context}`);
46
46
  if (done) done();
47
47
  return;
48
48
  }
49
49
  if (typeof msg.context !== "string") {
50
- node.status({ fill: "red", shape: "ring", text: "invalid context" });
50
+ utils.setStatusError(node, "invalid context");
51
51
  if (done) done();
52
52
  return;
53
53
  }
54
54
  let value = parseFloat(msg.payload);
55
55
  if (isNaN(value) || !isFinite(value)) {
56
- node.status({ fill: "red", shape: "ring", text: `invalid ${msg.context}` });
56
+ utils.setStatusError(node, `invalid ${msg.context}`);
57
57
  if (done) done();
58
58
  return;
59
59
  }
60
60
  switch (msg.context) {
61
61
  case "lowerLimit":
62
- node.runtime.lowerLimit = value;
63
- if (node.runtime.lowerLimit > node.runtime.upperLimit) {
64
- node.runtime.upperLimit = node.runtime.lowerLimit;
65
- node.status({
66
- fill: "green",
67
- shape: "dot",
68
- text: `lower: ${node.runtime.lowerLimit.toFixed(2)}, upper adjusted to ${node.runtime.upperLimit.toFixed(2)}`
69
- });
62
+ node.lowerLimit = value;
63
+ if (node.lowerLimit > node.upperLimit) {
64
+ node.upperLimit = node.lowerLimit;
65
+ utils.setStatusOK(node, `lower: ${node.lowerLimit.toFixed(2)}, upper adjusted to ${node.upperLimit.toFixed(2)}`);
70
66
  } else {
71
- node.status({
72
- fill: "green",
73
- shape: "dot",
74
- text: `lower: ${node.runtime.lowerLimit.toFixed(2)}`
75
- });
67
+ utils.setStatusOK(node, `lower: ${node.lowerLimit.toFixed(2)}`);
76
68
  }
77
69
  break;
78
70
  case "upperLimit":
79
- node.runtime.upperLimit = value;
80
- if (node.runtime.upperLimit < node.runtime.lowerLimit) {
81
- node.runtime.lowerLimit = node.runtime.upperLimit;
82
- node.status({
83
- fill: "green",
84
- shape: "dot",
85
- text: `upper: ${node.runtime.upperLimit.toFixed(2)}, lower adjusted to ${node.runtime.lowerLimit.toFixed(2)}`
86
- });
71
+ node.upperLimit = value;
72
+ if (node.upperLimit < node.lowerLimit) {
73
+ node.lowerLimit = node.upperLimit;
74
+ utils.setStatusOK(node, `upper: ${node.upperLimit.toFixed(2)}, lower adjusted to ${node.lowerLimit.toFixed(2)}`);
87
75
  } else {
88
- node.status({
89
- fill: "green",
90
- shape: "dot",
91
- text: `upper: ${node.runtime.upperLimit.toFixed(2)}`
92
- });
76
+ utils.setStatusOK(node, `upper: ${node.upperLimit.toFixed(2)}`);
93
77
  }
94
78
  break;
95
79
  case "period":
96
80
  const multiplier = msg.units === "minutes" ? 60000 : msg.units === "seconds" ? 1000 : 1;
97
81
  value *= multiplier;
98
82
  if (value <= 0) {
99
- node.status({ fill: "red", shape: "ring", text: "invalid period" });
83
+ utils.setStatusError(node, "invalid period");
100
84
  if (done) done();
101
85
  return;
102
86
  }
103
- node.runtime.period = value;
104
- node.runtime.periodUnits = msg.units || "milliseconds";
105
- node.status({
106
- fill: "green",
107
- shape: "dot",
108
- text: `period: ${node.runtime.period.toFixed(2)} ms`
109
- });
87
+ node.period = value;
88
+ node.periodUnits = msg.units || "milliseconds";
89
+ utils.setStatusOK(node, `period: ${node.period.toFixed(2)} ms`);
110
90
  break;
111
91
  default:
112
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
92
+ utils.setStatusWarn(node, "unknown context");
113
93
  if (done) done("Unknown context");
114
94
  return;
115
95
  }
@@ -119,27 +99,27 @@ module.exports = function(RED) {
119
99
 
120
100
  // Calculate time difference
121
101
  const now = Date.now();
122
- const deltaTime = (now - node.runtime.lastExecution) / 1000; // Seconds
123
- node.runtime.lastExecution = now;
102
+ const deltaTime = (now - node.lastExecution) / 1000; // Seconds
103
+ node.lastExecution = now;
124
104
 
125
105
  // Return lowerLimit if period is invalid
126
- if (node.runtime.period <= 0) {
127
- node.status({ fill: "blue", shape: "dot", text: `out: ${node.runtime.lowerLimit.toFixed(2)}, phase: ${node.runtime.phase.toFixed(2)}` });
128
- send({ payload: node.runtime.lowerLimit });
106
+ if (node.period <= 0) {
107
+ utils.setStatusOK(node, `out: ${node.lowerLimit.toFixed(2)}, phase: ${node.phase.toFixed(2)}`);
108
+ send({ payload: node.lowerLimit });
129
109
  if (done) done();
130
110
  return;
131
111
  }
132
112
 
133
113
  // Update phase
134
- node.runtime.phase = (node.runtime.phase + deltaTime / (node.runtime.period / 1000)) % 1;
114
+ node.phase = (node.phase + deltaTime / (node.period / 1000)) % 1;
135
115
 
136
116
  // Triangle wave calculation
137
- const triangleValue = node.runtime.phase < 0.5 ? 2 * node.runtime.phase : 2 * (1 - node.runtime.phase);
138
- const amplitude = (node.runtime.upperLimit - node.runtime.lowerLimit) / 2;
139
- const value = node.runtime.lowerLimit + amplitude * triangleValue;
117
+ const triangleValue = node.phase < 0.5 ? 2 * node.phase : 2 * (1 - node.phase);
118
+ const amplitude = (node.upperLimit - node.lowerLimit) / 2;
119
+ const value = node.lowerLimit + amplitude * triangleValue;
140
120
 
141
121
  // Output new message
142
- node.status({ fill: "blue", shape: "dot", text: `out: ${value.toFixed(2)}, phase: ${node.runtime.phase.toFixed(2)}` });
122
+ utils.setStatusOK(node, `out: ${value.toFixed(2)}, phase: ${node.phase.toFixed(2)}`);
143
123
  send({ payload: value });
144
124
 
145
125
  if (done) done();
@@ -33,7 +33,7 @@ module.exports = function(RED) {
33
33
  send = send || function() { node.send.apply(node, arguments); };
34
34
 
35
35
  if (!msg) {
36
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
36
+ utils.setStatusError(node, "invalid message");
37
37
  if (done) done();
38
38
  return;
39
39
  }
@@ -44,7 +44,7 @@ module.exports = function(RED) {
44
44
  // Check busy lock
45
45
  if (node.isBusy) {
46
46
  // Update status to let user know they are pushing too fast
47
- node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
47
+ utils.setStatusBusy(node, "busy - dropped msg");
48
48
  if (done) done();
49
49
  return;
50
50
  }
@@ -166,7 +166,7 @@ module.exports = function(RED) {
166
166
  // Handle configuration messages
167
167
  if (msg.hasOwnProperty("context")) {
168
168
  if (!msg.hasOwnProperty("payload")) {
169
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
169
+ utils.setStatusError(node, "missing payload");
170
170
  if (done) done();
171
171
  return;
172
172
  }
@@ -175,103 +175,101 @@ module.exports = function(RED) {
175
175
  case "algorithm":
176
176
  if (["single", "split", "specified"].includes(msg.payload)) {
177
177
  node.algorithm = msg.payload;
178
- node.status({ fill: "green", shape: "dot", text: `algorithm: ${msg.payload}` });
178
+ utils.setStatusOK(node, `algorithm: ${msg.payload}`);
179
179
  } else {
180
- node.status({ fill: "red", shape: "ring", text: "invalid algorithm" });
180
+ utils.setStatusError(node, "invalid algorithm");
181
181
  }
182
182
  break;
183
183
  case "setpoint":
184
184
  if (typeof msg.payload === 'number') {
185
185
  node.setpoint = msg.payload;
186
- node.status({ fill: "green", shape: "dot", text: `setpoint: ${msg.payload.toFixed(2)}` });
186
+ utils.setStatusOK(node, `setpoint: ${msg.payload.toFixed(2)}`);
187
187
  } else {
188
- node.status({ fill: "red", shape: "ring", text: "invalid setpoint" });
188
+ utils.setStatusError(node, "invalid setpoint");
189
189
  }
190
190
  break;
191
191
  case "heatingSetpoint":
192
192
  if (typeof msg.payload === 'number') {
193
193
  node.heatingSetpoint = msg.payload;
194
- node.status({ fill: "green", shape: "dot", text: `heatingSetpoint: ${msg.payload.toFixed(2)}`
195
- });
194
+ utils.setStatusOK(node, `heatingSetpoint: ${msg.payload.toFixed(2)}`);
196
195
  } else {
197
- node.status({ fill: "red", shape: "ring", text: "invalid heatingSetpoint" });
196
+ utils.setStatusError(node, "invalid heatingSetpoint");
198
197
  }
199
198
  break;
200
199
  case "coolingSetpoint":
201
200
  if (typeof msg.payload === 'number') {
202
201
  node.coolingSetpoint = msg.payload;
203
- node.status({ fill: "green", shape: "dot", text: `coolingSetpoint: ${msg.payload.toFixed(2)}` });
202
+ utils.setStatusOK(node, `coolingSetpoint: ${msg.payload.toFixed(2)}`);
204
203
  } else {
205
- node.status({ fill: "red", shape: "ring", text: "invalid coolingSetpoint" });
204
+ utils.setStatusError(node, "invalid coolingSetpoint");
206
205
  }
207
206
  break;
208
207
  case "coolingOn":
209
208
  if (typeof msg.payload === 'number') {
210
209
  node.coolingOn = msg.payload;
211
- node.status({ fill: "green", shape: "dot", text: `coolingOn: ${msg.payload.toFixed(2)}` });
210
+ utils.setStatusOK(node, `coolingOn: ${msg.payload.toFixed(2)}`);
212
211
  } else {
213
- node.status({ fill: "red", shape: "ring", text: "invalid coolingOn" });
212
+ utils.setStatusError(node, "invalid coolingOn");
214
213
  }
215
214
  break;
216
215
  case "coolingOff":
217
216
  if (typeof msg.payload === 'number') {
218
217
  node.coolingOff = msg.payload;
219
- node.status({ fill: "green", shape: "dot", text: `coolingOff: ${msg.payload.toFixed(2)}`
220
- });
218
+ utils.setStatusOK(node, `coolingOff: ${msg.payload.toFixed(2)}`);
221
219
  } else {
222
- node.status({ fill: "red", shape: "ring", text: "invalid coolingOff" });
220
+ utils.setStatusError(node, "invalid coolingOff");
223
221
  }
224
222
  break;
225
223
  case "heatingOff":
226
224
  if (typeof msg.payload === 'number') {
227
225
  node.heatingOff = msg.payload;
228
- node.status({ fill: "green", shape: "dot", text: `heatingOff: ${msg.payload.toFixed(2)}` });
226
+ utils.setStatusOK(node, `heatingOff: ${msg.payload.toFixed(2)}`);
229
227
  } else {
230
- node.status({ fill: "red", shape: "ring", text: "invalid heatingOff" });
228
+ utils.setStatusError(node, "invalid heatingOff");
231
229
  }
232
230
  break;
233
231
  case "heatingOn":
234
232
  if (typeof msg.payload === 'number') {
235
233
  node.heatingOn = msg.payload;
236
- node.status({ fill: "green", shape: "dot", text: `heatingOn: ${msg.payload.toFixed(2)}` });
234
+ utils.setStatusOK(node, `heatingOn: ${msg.payload.toFixed(2)}`);
237
235
  } else {
238
- node.status({ fill: "red", shape: "ring", text: "invalid heatingOn" });
236
+ utils.setStatusError(node, "invalid heatingOn");
239
237
  }
240
238
  break;
241
239
  case "diff":
242
240
  if (typeof msg.payload === 'number' && msg.payload >= 0.01) {
243
241
  node.diff = msg.payload;
244
- node.status({ fill: "green", shape: "dot", text: `diff: ${msg.payload.toFixed(2)}` });
242
+ utils.setStatusOK(node, `diff: ${msg.payload.toFixed(2)}`);
245
243
  } else {
246
- node.status({ fill: "red", shape: "ring", text: "invalid diff" });
244
+ utils.setStatusError(node, "invalid diff");
247
245
  }
248
246
  break;
249
247
  case "anticipator":
250
248
  if (typeof msg.payload === 'number' && msg.payload >= -2) {
251
249
  node.anticipator = msg.payload;
252
- node.status({ fill: "green", shape: "dot", text: `anticipator: ${msg.payload.toFixed(2)}` });
250
+ utils.setStatusOK(node, `anticipator: ${msg.payload.toFixed(2)}`);
253
251
  } else {
254
- node.status({ fill: "red", shape: "ring", text: "invalid anticipator" });
252
+ utils.setStatusError(node, "invalid anticipator");
255
253
  }
256
254
  break;
257
255
  case "ignoreAnticipatorCycles":
258
256
  if (typeof msg.payload === 'number' && msg.payload >= 0) {
259
257
  node.ignoreAnticipatorCycles = Math.floor(msg.payload);
260
- node.status({ fill: "green", shape: "dot", text: `ignoreAnticipatorCycles: ${Math.floor(msg.payload)}` });
258
+ utils.setStatusOK(node, `ignoreAnticipatorCycles: ${Math.floor(msg.payload)}`);
261
259
  } else {
262
- node.status({ fill: "red", shape: "ring", text: "invalid ignoreAnticipatorCycles" });
260
+ utils.setStatusError(node, "invalid ignoreAnticipatorCycles");
263
261
  }
264
262
  break;
265
263
  case "isHeating":
266
264
  if (typeof msg.payload === "boolean") {
267
265
  node.isHeating = msg.payload;
268
- node.status({ fill: "green", shape: "dot", text: `isHeating: ${msg.payload}` });
266
+ utils.setStatusOK(node, `isHeating: ${msg.payload}`);
269
267
  } else {
270
- node.status({ fill: "red", shape: "ring", text: "invalid isHeating" });
268
+ utils.setStatusError(node, "invalid isHeating");
271
269
  }
272
270
  break;
273
271
  default:
274
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
272
+ utils.setStatusWarn(node, "unknown context");
275
273
  break;
276
274
  }
277
275
  if (done) done();
@@ -279,21 +277,21 @@ module.exports = function(RED) {
279
277
  }
280
278
 
281
279
  if (!msg.hasOwnProperty("payload")) {
282
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
280
+ utils.setStatusError(node, "missing payload");
283
281
  if (done) done();
284
282
  return;
285
283
  }
286
284
 
287
285
  const input = parseFloat(msg.payload);
288
286
  if (isNaN(input)) {
289
- node.status({ fill: "red", shape: "ring", text: "invalid payload" });
287
+ utils.setStatusError(node, "invalid payload");
290
288
  if (done) done();
291
289
  return;
292
290
  }
293
291
 
294
292
  const isHeating = msg.hasOwnProperty("isHeating") && typeof msg.isHeating === "boolean" ? msg.isHeating : node.isHeating;
295
293
  if (msg.hasOwnProperty("isHeating") && typeof msg.isHeating !== "boolean") {
296
- node.status({ fill: "red", shape: "ring", text: "invalid isHeating (must be boolean)" });
294
+ utils.setStatusError(node, "invalid isHeating (must be boolean)");
297
295
  if (done) done();
298
296
  return;
299
297
  }
@@ -330,7 +328,7 @@ module.exports = function(RED) {
330
328
  // The Tstat node does not control heating/cooling mode, only operates heating or cooling according to the mode set and respective setpoints.
331
329
  if (node.algorithm === "single") {
332
330
  // Note:
333
- // Make sure your mode selection is handled upstream and does not osciallate modes.
331
+ // Make sure your mode selection is handled upstream and does not oscillate modes.
334
332
  // This was changed to allow for broader anticipator authority, or even negative (overshoot) so duty cycle can be better managed.
335
333
  // So the same setpoint can be used year round and maintain tight control.
336
334
  // Alternatively, you would need a larger diff value to prevent oscillation.
@@ -458,17 +456,9 @@ module.exports = function(RED) {
458
456
  send(outputs);
459
457
 
460
458
  if (above === lastAbove && below === lastBelow) {
461
- node.status({
462
- fill: "blue",
463
- shape: "ring",
464
- text: `in: ${input.toFixed(2)}, out: ${node.isHeating ? "heating" : "cooling"}, above: ${above}, below: ${below}`
465
- });
459
+ utils.setStatusUnchanged(node, `in: ${input.toFixed(2)}, out: ${node.isHeating ? "heating" : "cooling"}, above: ${above}, below: ${below}`);
466
460
  } else {
467
- node.status({
468
- fill: "blue",
469
- shape: "dot",
470
- text: `in: ${input.toFixed(2)}, out: ${node.isHeating ? "heating" : "cooling"}, above: ${above}, below: ${below}`
471
- });
461
+ utils.setStatusChanged(node, `in: ${input.toFixed(2)}, out: ${node.isHeating ? "heating" : "cooling"}, above: ${above}, below: ${below}`);
472
462
  }
473
463
 
474
464
  if (done) done();
@@ -4,10 +4,82 @@
4
4
  <label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
5
5
  <input type="text" id="node-input-name" placeholder="Name">
6
6
  </div>
7
- <div class="form-row">
7
+ <div class="form-row">
8
+ <label for="node-input-inputProperty" title="Message property to read input from"><i class="fa fa-folder-open"></i> Input Property</label>
9
+ <input type="text" id="node-input-inputProperty" placeholder="payload">
10
+ </div>
11
+ <div class="form-row">
8
12
  <label for="node-input-unit" title="Unit to append to msg.units"><i class="fa fa-tag"></i> Unit</label>
9
- <input type="text" id="node-input-unit">
10
- <input type="hidden" id="node-input-unitType">
13
+ <select id="node-input-unit">
14
+ <option value="">Text (none)</option>
15
+ <optgroup label="Temperature">
16
+ <option value="°C">°C (Celsius)</option>
17
+ <option value="°F">°F (Fahrenheit)</option>
18
+ <option value="K">K (Kelvin)</option>
19
+ <option value="°R">°R (Rankine)</option>
20
+ </optgroup>
21
+ <optgroup label="Humidity">
22
+ <option value="%RH">%RH (Relative Humidity)</option>
23
+ <option value="%">% (Percent)</option>
24
+ </optgroup>
25
+ <optgroup label="Pressure">
26
+ <option value="Pa">Pa (Pascal)</option>
27
+ <option value="kPa">kPa (Kilopascal)</option>
28
+ <option value="bar">bar</option>
29
+ <option value="mbar">mbar (Millibar)</option>
30
+ <option value="psi">psi</option>
31
+ <option value="inHg">inHg (Inches of Mercury)</option>
32
+ <option value="atm">atm (Atmosphere)</option>
33
+ <option value="inH₂O">inH₂O (Inches of Water)</option>
34
+ <option value="mmH₂O">mmH₂O (Millimeters of Water)</option>
35
+ </optgroup>
36
+ <optgroup label="Flow Rate">
37
+ <option value="CFM">CFM (Cubic Feet per Minute)</option>
38
+ <option value="m³/h">m³/h (Cubic Meters per Hour)</option>
39
+ <option value="L/s">L/s (Liters per Second)</option>
40
+ </optgroup>
41
+ <optgroup label="Electrical">
42
+ <option value="V">V (Volt)</option>
43
+ <option value="mV">mV (Millivolt)</option>
44
+ <option value="A">A (Ampere)</option>
45
+ <option value="mA">mA (Milliampere)</option>
46
+ <option value="W">W (Watt)</option>
47
+ <option value="kW">kW (Kilowatt)</option>
48
+ <option value="hp">hp (Horsepower)</option>
49
+ <option value="Ω">Ω (Ohm)</option>
50
+ </optgroup>
51
+ <optgroup label="Distance">
52
+ <option value="m">m (Meter)</option>
53
+ <option value="cm">cm (Centimeter)</option>
54
+ <option value="mm">mm (Millimeter)</option>
55
+ <option value="km">km (Kilometer)</option>
56
+ <option value="ft">ft (Foot)</option>
57
+ <option value="in">in (Inch)</option>
58
+ </optgroup>
59
+ <optgroup label="Mass">
60
+ <option value="kg">kg (Kilogram)</option>
61
+ <option value="g">g (Gram)</option>
62
+ <option value="lb">lb (Pound)</option>
63
+ </optgroup>
64
+ <optgroup label="Time">
65
+ <option value="s">s (Second)</option>
66
+ <option value="min">min (Minute)</option>
67
+ <option value="h">h (Hour)</option>
68
+ </optgroup>
69
+ <optgroup label="Volume">
70
+ <option value="L">L (Liter)</option>
71
+ <option value="mL">mL (Milliliter)</option>
72
+ <option value="gal">gal (Gallon)</option>
73
+ </optgroup>
74
+ <optgroup label="Light">
75
+ <option value="lx">lx (Lux)</option>
76
+ <option value="cd">cd (Candela)</option>
77
+ </optgroup>
78
+ <optgroup label="Other">
79
+ <option value="B">B (Bel)</option>
80
+ <option value="T">T (Tesla)</option>
81
+ </optgroup>
82
+ </select>
11
83
  </div>
12
84
  </script>
13
85
 
@@ -18,8 +90,8 @@
18
90
  color: "#301934",
19
91
  defaults: {
20
92
  name: { value: "" },
21
- unit: { value: "°F" },
22
- unitType: { value: "str" }
93
+ inputProperty: { value: "payload" },
94
+ unit: { value: "°F" }
23
95
  },
24
96
  inputs: 1,
25
97
  outputs: 1,
@@ -30,62 +102,6 @@
30
102
  label: function() {
31
103
  return this.name || `units ${this.unit}`;
32
104
  },
33
- oneditprepare: function() {
34
- $("#node-input-unit").typedInput({
35
- default: "str",
36
- types: [{
37
- value: "str",
38
- options: [
39
- { value: "", label: "Text (none)" },
40
- { value: "°C", label: "°C (Celsius)" },
41
- { value: "°F", label: "°F (Fahrenheit)" },
42
- { value: "K", label: "K (Kelvin)" },
43
- { value: "°R", label: "°R (Rankine)" },
44
- { value: "%RH", label: "%RH (Relative Humidity)" },
45
- { value: "%", label: "% (Percent)" },
46
- { value: "Pa", label: "Pa (Pascal)" },
47
- { value: "kPa", label: "kPa (Kilopascal)" },
48
- { value: "bar", label: "bar" },
49
- { value: "mbar", label: "mbar (Millibar)" },
50
- { value: "psi", label: "psi" },
51
- { value: "inHg", label: "inHg (Inches of Mercury)" },
52
- { value: "atm", label: "atm (Atmosphere)" },
53
- { value: "inH₂O", label: "inH₂O (Inches of Water)" },
54
- { value: "mmH₂O", label: "mmH₂O (Millimeters of Water)" },
55
- { value: "CFM", label: "CFM (Cubic Feet per Minute)" },
56
- { value: "m³/h", label: "m³/h (Cubic Meters per Hour)" },
57
- { value: "L/s", label: "L/s (Liters per Second)" },
58
- { value: "V", label: "V (Volt)" },
59
- { value: "mV", label: "mV (Millivolt)" },
60
- { value: "A", label: "A (Ampere)" },
61
- { value: "mA", label: "mA (Milliampere)" },
62
- { value: "W", label: "W (Watt)" },
63
- { value: "kW", label: "kW (Kilowatt)" },
64
- { value: "hp", label: "hp (Horsepower)" },
65
- { value: "Ω", label: "Ω (Ohm)" },
66
- { value: "m", label: "m (Meter)" },
67
- { value: "cm", label: "cm (Centimeter)" },
68
- { value: "mm", label: "mm (Millimeter)" },
69
- { value: "km", label: "km (Kilometer)" },
70
- { value: "ft", label: "ft (Foot)" },
71
- { value: "in", label: "in (Inch)" },
72
- { value: "kg", label: "kg (Kilogram)" },
73
- { value: "g", label: "g (Gram)" },
74
- { value: "lb", label: "lb (Pound)" },
75
- { value: "s", label: "s (Second)" },
76
- { value: "min", label: "min (Minute)" },
77
- { value: "h", label: "h (Hour)" },
78
- { value: "L", label: "L (Liter)" },
79
- { value: "mL", label: "mL (Milliliter)" },
80
- { value: "gal", label: "gal (Gallon)" },
81
- { value: "lx", label: "lx (Lux)" },
82
- { value: "cd", label: "cd (Candela)" },
83
- { value: "B", label: "B (Bel)" },
84
- { value: "T", label: "T (Tesla)" }
85
- ]
86
- }], typeField: "#node-input-unitType"
87
- }).typedInput("type", node.unitType).typedInput("value", node.unit);
88
- }
89
105
  });
90
106
  </script>
91
107
 
@@ -94,17 +110,22 @@
94
110
  Appends a selected unit to `msg.units` of every input message.
95
111
 
96
112
  ### Inputs
97
- : context (string) : Configures unit (`"unit"`) if provided. Ignored otherwise.
98
- : payload (any) : Input payload to pass through unchanged.
113
+ : input-property (any) : Input value to read from the configured Input Property.
114
+ : context (string) : Configures unit (`"unit"`) if provided. Ignored otherwise.
99
115
 
100
116
  ### Outputs
101
- : payload (any) : Original payload.
102
- : units (string) : Selected unit (e.g., °F, %RH, inH₂O).
117
+ : payload (any) : Original payload from configured Input Property.
118
+ : units (string) : Selected unit (e.g., °F, %RH, inH₂O).
119
+
120
+ ### Properties
121
+ : name (string) : Display name in editor.
122
+ : inputProperty (string) : Message property to read from (default: `payload`). Supports nested properties (e.g., `data.value`).
123
+ : unit (string) : Unit label to append to `msg.units`.
103
124
 
104
125
  ### Details
105
- Appends `msg.units` with the configured unit to every input message.
106
- Unit can be set via editor or dynamically with `msg.context = "unit"` and a valid unit in `msg.payload`.
107
- Supports units like °C, °F, %RH, inH₂O, CFM for HVAC and control systems.
126
+ Appends `msg.units` with the configured unit to every input message. The input value is read from the configured **Input Property** (default: `msg.payload`).
127
+ Unit can be set via editor or dynamically with `msg.context = "unit"` and a valid unit in `msg.payload`.
128
+ Supports units like °C, °F, %RH, inH₂O, CFM for HVAC and control systems.
108
129
  Processes every input message, preserving all original properties, just adding `msg.units`.
109
130
 
110
131
  ### Status
@@ -115,6 +136,6 @@ Processes every input message, preserving all original properties, just adding `
115
136
  - Yellow (ring): Warning
116
137
 
117
138
  ### References
118
- - [Node-RED Documentation](https://nodered.org/docs/)
139
+ - [Node-RED Documentation](https://nodered.org/docs/)
119
140
  - [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
120
141
  </script>