@bldgblocks/node-red-contrib-control 0.1.20 → 0.1.22

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.
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function AverageBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
@@ -10,34 +12,15 @@ module.exports = function(RED) {
10
12
  lastAvg: null
11
13
  };
12
14
 
13
- // Evaluate all properties
14
- try {
15
- node.runtime.minValid = RED.util.evaluateNodeProperty(
16
- config.minValid, config.minValidType, node
17
- );
15
+ const typedProperties = ['minValid', 'maxValid'];
18
16
 
19
- node.runtime.maxValid = RED.util.evaluateNodeProperty(
20
- config.maxValid, config.maxValidType, node
21
- );
22
-
23
- // Validate values
24
- if (isNaN(node.runtime.maxValid) || isNaN(node.runtime.minValid) || node.runtime.maxValid <= node.runtime.minValid ) {
25
- node.status({ fill: "red", shape: "ring", text: `invalid evaluated values ${node.runtime.minValid}, ${node.runtime.maxValid}` });
26
- if (done) done();
27
- return;
28
- }
29
- } catch(err) {
30
- node.status({ fill: "red", shape: "ring", text: "error evaluating properties" });
31
- if (done) done(err);
32
- return;
33
- }
34
-
35
- // Validate initial config
36
- if (isNaN(node.runtime.maxValues) || node.runtime.maxValues < 1) {
37
- node.runtime.maxValues = 10;
38
- node.status({ fill: "red", shape: "ring", text: "invalid window size, using 10" });
39
- } else {
40
- node.status({ shape: "dot", text: `name: ${config.name}, window: ${config.sampleSize}` });
17
+ // Evaluate typed-input properties
18
+ try {
19
+ const evaluatedValues = utils.evaluateProperties(node, config, typedProperties, null, true);
20
+ node.runtime.minValid = parseFloat(evaluatedValues.minValid);
21
+ node.runtime.maxValid = parseFloat(evaluatedValues.maxValid);
22
+ } catch (err) {
23
+ node.error(`Error evaluating properties: ${err.message}`);
41
24
  }
42
25
 
43
26
  node.on("input", function(msg, send, done) {
@@ -48,6 +31,30 @@ module.exports = function(RED) {
48
31
  node.status({ fill: "red", shape: "ring", text: "invalid message" });
49
32
  if (done) done();
50
33
  return;
34
+ }
35
+
36
+ // Update typed-input properties if needed
37
+ try {
38
+ const evaluatedValues = utils.evaluateProperties(node, config, typedProperties, msg);
39
+ node.runtime.minValid = parseFloat(evaluatedValues.minValid);
40
+ node.runtime.maxValid = parseFloat(evaluatedValues.maxValid);
41
+ } catch (err) {
42
+ node.error(`Error evaluating properties: ${err.message}`);
43
+ if (done) done();
44
+ return;
45
+ }
46
+
47
+ // Acceptable fallbacks
48
+ if (isNaN(node.runtime.maxValues) || node.runtime.maxValues < 1) {
49
+ node.runtime.maxValues = 10;
50
+ node.status({ fill: "red", shape: "ring", text: "invalid window size, using 10" });
51
+ }
52
+
53
+ // Validate values
54
+ if (isNaN(node.runtime.maxValid) || isNaN(node.runtime.minValid) || node.runtime.maxValid <= node.runtime.minValid ) {
55
+ node.status({ fill: "red", shape: "ring", text: `invalid evaluated values ${node.runtime.minValid}, ${node.runtime.maxValid}` });
56
+ if (done) done();
57
+ return;
51
58
  }
52
59
 
53
60
  // Handle configuration messages
@@ -18,6 +18,12 @@
18
18
  <div class="form-row single-only">
19
19
  <label for="node-input-deadband" title="Temperature range for no action (positive number)"><i class="fa fa-arrows-v"></i> Deadband</label>
20
20
  <input type="text" id="node-input-deadband" placeholder="2">
21
+ <input type="hidden" id="node-input-deadbandType">
22
+ </div>
23
+ <div class="form-row split-only" style="display: none;">
24
+ <div id="deadband-warning" style="color: red; display: none; margin-top: 5px;">
25
+ Invalid
26
+ </div>
21
27
  </div>
22
28
  <div class="form-row split-only" style="display: none;">
23
29
  <label for="node-input-heatingSetpoint" title="Heating setpoint for split algorithm (number from num, msg, flow, or global)"><i class="fa fa-thermometer-empty"></i> Heating Setpoint</label>
@@ -34,9 +40,15 @@
34
40
  Cooling setpoint must be greater than heating setpoint
35
41
  </div>
36
42
  </div>
37
- <div class="form-row">
43
+ <div class="form-row split-only">
38
44
  <label for="node-input-extent" title="Extra buffer mode switching thresholds (non-negative number)"><i class="fa fa-tachometer"></i> Extent</label>
39
45
  <input type="text" id="node-input-extent" placeholder="1">
46
+ <input type="hidden" id="node-input-extentType">
47
+ </div>
48
+ <div class="form-row split-only" style="display: none;">
49
+ <div id="extent-warning" style="color: red; display: none; margin-top: 5px;">
50
+ Invalid
51
+ </div>
40
52
  </div>
41
53
  <div class="form-row">
42
54
  <label for="node-input-swapTime" title="Minimum time before mode change (seconds, minimum 60, from num, msg, flow, or global)"><i class="fa fa-clock-o"></i> Swap Time</label>
@@ -57,6 +69,11 @@
57
69
  <label for="node-input-initWindow" title="Initialization window to collect inputs before mode selection (seconds, non-negative number)"><i class="fa fa-hourglass-start"></i> Init Window</label>
58
70
  <input type="text" id="node-input-initWindow" placeholder="10">
59
71
  </div>
72
+ <div class="form-row split-only" style="display: none;">
73
+ <div id="initWindow-warning" style="color: red; display: none; margin-top: 5px;">
74
+ Invalid
75
+ </div>
76
+ </div>
60
77
  <div class="form-row">
61
78
  <label for="node-input-operationMode" title="Operation mode"><i class="fa fa-cogs"></i> Operation Mode</label>
62
79
  <select id="node-input-operationMode">
@@ -92,7 +109,7 @@
92
109
  operationMode: { value: "auto" }
93
110
  },
94
111
  inputs: 1,
95
- outputs: 2,
112
+ outputs: 1,
96
113
  inputLabels: ["temperature"],
97
114
  outputLabels: ["isHeating", "status"],
98
115
  icon: "font-awesome/fa-retweet",
@@ -132,6 +149,18 @@
132
149
  typeField: "#node-input-swapTimeType"
133
150
  }).typedInput("type", node.swapTimeType || "num").typedInput("value", node.swapTime);
134
151
 
152
+ $("#node-input-deadband").typedInput({
153
+ default: "num",
154
+ types: ["num", "msg", "flow", "global"],
155
+ typeField: "#node-input-deadbandType"
156
+ }).typedInput("type", node.deadbandType || "num").typedInput("value", node.deadband);
157
+
158
+ $("#node-input-extent").typedInput({
159
+ default: "num",
160
+ types: ["num", "msg", "flow", "global"],
161
+ typeField: "#node-input-extentType"
162
+ }).typedInput("type", node.extentType || "num").typedInput("value", node.extent);
163
+
135
164
  $("#node-input-minTempSetpoint").typedInput({
136
165
  default: "num",
137
166
  types: ["num", "msg", "flow", "global"],
@@ -149,7 +178,6 @@
149
178
  if ($algorithm.val() === "single") {
150
179
  $singleFields.show();
151
180
  $splitFields.hide();
152
- $("#split-setpoint-warning").hide();
153
181
  } else {
154
182
  $singleFields.hide();
155
183
  $splitFields.show();
@@ -158,22 +186,6 @@
158
186
 
159
187
  $algorithm.on("change", toggleFields);
160
188
  toggleFields();
161
-
162
- // Validate non-typed inputs
163
- $("#node-input-deadband").on("input", function() {
164
- const val = parseFloat($(this).val());
165
- $(this).toggleClass("input-error", isNaN(val) || val <= 0);
166
- });
167
-
168
- $("#node-input-extent").on("input", function() {
169
- const val = parseFloat($(this).val());
170
- $(this).toggleClass("input-error", isNaN(val) || val < 0);
171
- });
172
-
173
- $("#node-input-initWindow").on("input", function() {
174
- const val = parseFloat($(this).val());
175
- $(this).toggleClass("input-error", isNaN(val) || val < 0);
176
- });
177
189
 
178
190
  } catch (err) {
179
191
  console.error("Error in changeover-block oneditprepare:", err);
@@ -200,7 +212,7 @@ The node supports two algorithms for determining HVAC mode
200
212
  - Uses a single `setpoint`, `deadband`, and `extent`.
201
213
  - **Heating** is triggered if the input temperature is below `setpoint - deadband/2 - extent`.
202
214
  - **Cooling** is triggered if the temperature exceeds `setpoint + deadband/2 + extent`.
203
- - Example With `setpoint=70`, `deadband=2`, `extent=1`, heating starts below `70 - 2/2 - 1 = 68`, and cooling starts above `70 + 2/2 + 1 = 72`.
215
+ - Example With `setpoint=70`, `deadband=2``, heating starts below `70 - 2/2 = 68`, and cooling starts above `70 + 2/2 = 72`.
204
216
  - The `extent` widens thresholds to prevent nuisance mode changes due to overshoot.
205
217
 
206
218
  - **Split Setpoint**
@@ -1,4 +1,6 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function ChangeoverBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
@@ -7,10 +9,6 @@ module.exports = function(RED) {
7
9
  node.runtime = {
8
10
  name: config.name,
9
11
  algorithm: config.algorithm,
10
- deadband: parseFloat(config.deadband),
11
- extent: parseFloat(config.extent),
12
- minTempSetpoint: parseFloat(config.minTempSetpoint),
13
- maxTempSetpoint: parseFloat(config.maxTempSetpoint),
14
12
  operationMode: config.operationMode,
15
13
  initWindow: parseFloat(config.initWindow),
16
14
  currentMode: (config.operationMode === "cool" ? "cooling" : "heating"),
@@ -18,37 +16,25 @@ module.exports = function(RED) {
18
16
  lastModeChange: 0
19
17
  };
20
18
 
21
- // Resolve typed inputs
22
- let minTemp = node.runtime.minTempSetpoint;
23
- let maxTemp = node.runtime.maxTempSetpoint;
24
-
25
- if (node.runtime.algorithm === "single") {
26
- node.runtime.setpoint = RED.util.evaluateNodeProperty(
27
- config.setpoint, config.setpointType, node
28
- );
29
- node.runtime.setpoint = parseFloat(node.runtime.setpoint);
30
- } else {
31
- node.runtime.heatingSetpoint = RED.util.evaluateNodeProperty(
32
- config.heatingSetpoint, config.heatingSetpointType, node
33
- );
34
- node.runtime.heatingSetpoint = parseFloat(node.runtime.heatingSetpoint);
35
-
36
- node.runtime.coolingSetpoint = RED.util.evaluateNodeProperty(
37
- config.coolingSetpoint, config.coolingSetpointType, node
38
- );
39
- node.runtime.coolingSetpoint = parseFloat(node.runtime.coolingSetpoint);
40
-
41
- // Validate
42
- if (node.runtime.coolingSetpoint < node.runtime.heatingSetpoint) {
43
- node.runtime.coolingSetpoint = node.runtime.heatingSetpoint + 4;
44
- node.status({ fill: "red", shape: "ring", text: "invalid setpoints, using fallback" });
45
- }
19
+ const typedProperties = ['setpoint', 'heatingSetpoint', 'coolingSetpoint', 'swapTime', 'deadband',
20
+ 'extent', 'minTempSetpoint', 'maxTempSetpoint'];
21
+
22
+ // Evaluate typed-input properties
23
+ try {
24
+ const evaluatedValues = utils.evaluateProperties(node, config, typedProperties, null, true);
25
+ node.runtime.setpoint = parseFloat(evaluatedValues.setpoint);
26
+ node.runtime.heatingSetpoint = parseFloat(evaluatedValues.heatingSetpoint);
27
+ node.runtime.coolingSetpoint = parseFloat(evaluatedValues.coolingSetpoint);
28
+ node.runtime.swapTime = parseFloat(evaluatedValues.swapTime);
29
+ node.runtime.deadband = parseFloat(evaluatedValues.deadband);
30
+ node.runtime.extent = parseFloat(evaluatedValues.extent);
31
+ node.runtime.minTempSetpoint = parseFloat(evaluatedValues.minTempSetpoint);
32
+ node.runtime.maxTempSetpoint = parseFloat(evaluatedValues.maxTempSetpoint);
33
+ } catch (err) {
34
+ node.error(`Error evaluating properties: ${err.message}`);
35
+ if (done) done();
36
+ return;
46
37
  }
47
-
48
- node.runtime.swapTime = RED.util.evaluateNodeProperty(
49
- config.swapTime, config.swapTimeType, node
50
- );
51
- node.runtime.swapTime = parseFloat(node.runtime.swapTime);
52
38
 
53
39
  // Initialize state
54
40
  let initComplete = false;
@@ -63,9 +49,34 @@ module.exports = function(RED) {
63
49
  node.status({ fill: "red", shape: "ring", text: "invalid message" });
64
50
  if (done) done();
65
51
  return;
52
+ }
53
+
54
+ // Update typed-input properties if needed
55
+ try {
56
+ const evaluatedValues = utils.evaluateProperties(node, config, typedProperties, msg);
57
+ node.runtime.setpoint = parseFloat(evaluatedValues.setpoint);
58
+ node.runtime.heatingSetpoint = parseFloat(evaluatedValues.heatingSetpoint);
59
+ node.runtime.coolingSetpoint = parseFloat(evaluatedValues.coolingSetpoint);
60
+ node.runtime.swapTime = parseFloat(evaluatedValues.swapTime);
61
+ node.runtime.deadband = parseFloat(evaluatedValues.deadband);
62
+ node.runtime.extent = parseFloat(evaluatedValues.extent);
63
+ node.runtime.minTempSetpoint = parseFloat(evaluatedValues.minTempSetpoint);
64
+ node.runtime.maxTempSetpoint = parseFloat(evaluatedValues.maxTempSetpoint);
65
+ } catch (err) {
66
+ node.error(`Error evaluating properties: ${err.message}`);
67
+ if (done) done();
68
+ return;
66
69
  }
67
-
70
+
68
71
  // Validate
72
+ if (node.runtime.coolingSetpoint < node.runtime.heatingSetpoint
73
+ || node.runtime.maxTempSetpoint < node.runtime.minTempSetpoint
74
+ || node.runtime.deadband <= 0 || node.runtime.extent < 0) {
75
+ node.status({ fill: "red", shape: "ring", text: "error validating properties, check setpoints" });
76
+ if (done) done(err);
77
+ return;
78
+ }
79
+
69
80
  if (node.runtime.swapTime < 60) {
70
81
  node.runtime.swapTime = 60;
71
82
  node.status({ fill: "red", shape: "ring", text: "swapTime below 60s, using 60" });
@@ -104,12 +115,7 @@ module.exports = function(RED) {
104
115
  node.status({ fill: "green", shape: "dot", text: `in: algorithm=${msg.payload}, out: ${node.runtime.currentMode}` });
105
116
  break;
106
117
  case "setpoint":
107
- if (node.runtime.algorithm !== "single") {
108
- node.status({ fill: "red", shape: "ring", text: "setpoint not used in split algorithm" });
109
- if (done) done();
110
- return;
111
- }
112
- if (isNaN(value) || value < minTemp || value > maxTemp) {
118
+ if (isNaN(value) || value < node.runtime.minTempSetpoint || value > node.runtime.maxTempSetpoint) {
113
119
  node.status({ fill: "red", shape: "ring", text: "invalid setpoint" });
114
120
  if (done) done();
115
121
  return;
@@ -119,11 +125,6 @@ module.exports = function(RED) {
119
125
  node.status({ fill: "green", shape: "dot", text: `in: setpoint=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
120
126
  break;
121
127
  case "deadband":
122
- if (node.runtime.algorithm !== "single") {
123
- node.status({ fill: "red", shape: "ring", text: "deadband not used in split algorithm" });
124
- if (done) done();
125
- return;
126
- }
127
128
  if (isNaN(value) || value <= 0) {
128
129
  node.status({ fill: "red", shape: "ring", text: "invalid deadband" });
129
130
  if (done) done();
@@ -133,12 +134,7 @@ module.exports = function(RED) {
133
134
  node.status({ fill: "green", shape: "dot", text: `in: deadband=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
134
135
  break;
135
136
  case "heatingSetpoint":
136
- if (node.runtime.algorithm !== "split") {
137
- node.status({ fill: "red", shape: "ring", text: "heatingSetpoint not used in single algorithm" });
138
- if (done) done();
139
- return;
140
- }
141
- if (isNaN(value) || value < minTemp || value > maxTemp || value > node.runtime.coolingSetpoint) {
137
+ if (isNaN(value) || value < node.runtime.minTempSetpoint || value > node.runtime.maxTempSetpoint || value > node.runtime.coolingSetpoint) {
142
138
  node.status({ fill: "red", shape: "ring", text: "invalid heatingSetpoint" });
143
139
  if (done) done();
144
140
  return;
@@ -148,12 +144,7 @@ module.exports = function(RED) {
148
144
  node.status({ fill: "green", shape: "dot", text: `in: heatingSetpoint=${value.toFixed(1)}, out: ${node.runtime.currentMode}` });
149
145
  break;
150
146
  case "coolingSetpoint":
151
- if (node.runtime.algorithm !== "split") {
152
- node.status({ fill: "red", shape: "ring", text: "coolingSetpoint not used in single algorithm" });
153
- if (done) done();
154
- return;
155
- }
156
- if (isNaN(value) || value < minTemp || value > maxTemp || value < node.runtime.heatingSetpoint) {
147
+ if (isNaN(value) || value < node.runtime.minTempSetpoint || value > node.runtime.maxTempSetpoint || value < node.runtime.heatingSetpoint) {
157
148
  node.status({ fill: "red", shape: "ring", text: "invalid coolingSetpoint" });
158
149
  if (done) done();
159
150
  return;
@@ -226,14 +217,14 @@ module.exports = function(RED) {
226
217
  }
227
218
 
228
219
  if (!msg.hasOwnProperty("payload")) {
229
- node.status({ fill: "red", shape: "ring", text: "missing temperature" });
220
+ node.status({ fill: "red", shape: "ring", text: "missing temperature payload property" });
230
221
  if (done) done();
231
222
  return;
232
223
  }
233
224
 
234
225
  let input = parseFloat(msg.payload);
235
226
  if (isNaN(input)) {
236
- node.status({ fill: "red", shape: "ring", text: "invalid temperature" });
227
+ node.status({ fill: "red", shape: "ring", text: "invalid temperature payload" });
237
228
  if (done) done();
238
229
  return;
239
230
  }
@@ -254,8 +245,6 @@ module.exports = function(RED) {
254
245
  return;
255
246
  }
256
247
 
257
-
258
-
259
248
  send(evaluateState() || buildOutputs());
260
249
  updateStatus();
261
250
  if (done) done();
@@ -273,8 +262,8 @@ module.exports = function(RED) {
273
262
  } else {
274
263
  let heatingThreshold, coolingThreshold;
275
264
  if (node.runtime.algorithm === "single") {
276
- heatingThreshold = node.runtime.setpoint - node.runtime.deadband / 2 - node.runtime.extent;
277
- coolingThreshold = node.runtime.setpoint + node.runtime.deadband / 2 + node.runtime.extent;
265
+ heatingThreshold = node.runtime.setpoint - node.runtime.deadband / 2;
266
+ coolingThreshold = node.runtime.setpoint + node.runtime.deadband / 2;
278
267
  } else {
279
268
  heatingThreshold = node.runtime.heatingSetpoint - node.runtime.extent;
280
269
  coolingThreshold = node.runtime.coolingSetpoint + node.runtime.extent;
@@ -307,8 +296,8 @@ module.exports = function(RED) {
307
296
  } else if (node.runtime.lastTemperature !== null) {
308
297
  let heatingThreshold, coolingThreshold;
309
298
  if (node.runtime.algorithm === "single") {
310
- heatingThreshold = node.runtime.setpoint - node.runtime.deadband / 2 - node.runtime.extent;
311
- coolingThreshold = node.runtime.setpoint + node.runtime.deadband / 2 + node.runtime.extent;
299
+ heatingThreshold = node.runtime.setpoint - node.runtime.deadband / 2;
300
+ coolingThreshold = node.runtime.setpoint + node.runtime.deadband / 2;
312
301
  } else {
313
302
  heatingThreshold = node.runtime.heatingSetpoint - node.runtime.extent;
314
303
  coolingThreshold = node.runtime.coolingSetpoint + node.runtime.extent;
@@ -346,26 +335,27 @@ module.exports = function(RED) {
346
335
 
347
336
  function buildOutputs() {
348
337
  const isHeating = node.runtime.currentMode === "heating";
349
- let heatingSetpoint, coolingSetpoint;
338
+ let effectiveHeatingSetpoint, effectiveCoolingSetpoint;
350
339
  if (node.runtime.algorithm === "single") {
351
- heatingSetpoint = node.runtime.setpoint - node.runtime.deadband / 2;
352
- coolingSetpoint = node.runtime.setpoint + node.runtime.deadband / 2;
340
+ effectiveHeatingSetpoint = node.runtime.setpoint - node.runtime.deadband / 2;
341
+ effectiveCoolingSetpoint = node.runtime.setpoint + node.runtime.deadband / 2;
353
342
  } else {
354
- heatingSetpoint = node.runtime.heatingSetpoint;
355
- coolingSetpoint = node.runtime.coolingSetpoint;
343
+ effectiveHeatingSetpoint = node.runtime.heatingSetpoint;
344
+ effectiveCoolingSetpoint = node.runtime.coolingSetpoint;
356
345
  }
357
346
 
358
347
  return [
359
- { payload: isHeating, context: "isHeating" },
360
- {
361
- payload: {
348
+ {
349
+ payload: isHeating,
350
+ context: "isHeating",
351
+ status: {
362
352
  mode: node.runtime.currentMode,
363
353
  isHeating,
364
- heatingSetpoint,
365
- coolingSetpoint,
354
+ heatingSetpoint: effectiveHeatingSetpoint,
355
+ coolingSetpoint: effectiveCoolingSetpoint,
366
356
  temperature: node.runtime.lastTemperature
367
357
  }
368
- }
358
+ },
369
359
  ];
370
360
  }
371
361
 
@@ -1,45 +1,45 @@
1
- const validConversions = [
2
- "F to C",
3
- "C to F",
4
- "K to C",
5
- "C to K",
6
- "K to F",
7
- "F to K",
8
- "R to F",
9
- "F to R",
10
- "decimal to %",
11
- "% to decimal",
12
- "Pa to inH₂O",
13
- "inH₂O to Pa",
14
- "Pa to inHg",
15
- "inHg to Pa",
16
- "Pa to bar",
17
- "bar to Pa",
18
- "Pa to psi",
19
- "psi to Pa",
20
- "m to ft",
21
- "ft to m",
22
- "m to in",
23
- "in to m",
24
- "mm to in",
25
- "in to mm",
26
- "kg to lb",
27
- "lb to kg",
28
- "L to gal",
29
- "gal to L",
30
- "kW to hp",
31
- "hp to kW",
32
- "rad to deg",
33
- "deg to rad",
34
- "s to min",
35
- "min to s"
36
- ];
37
-
38
1
  module.exports = function(RED) {
39
2
  function ConvertBlockNode(config) {
40
3
  RED.nodes.createNode(this, config);
41
4
  const node = this;
42
5
 
6
+ const validConversions = [
7
+ "F to C",
8
+ "C to F",
9
+ "K to C",
10
+ "C to K",
11
+ "K to F",
12
+ "F to K",
13
+ "R to F",
14
+ "F to R",
15
+ "decimal to %",
16
+ "% to decimal",
17
+ "Pa to inH₂O",
18
+ "inH₂O to Pa",
19
+ "Pa to inHg",
20
+ "inHg to Pa",
21
+ "Pa to bar",
22
+ "bar to Pa",
23
+ "Pa to psi",
24
+ "psi to Pa",
25
+ "m to ft",
26
+ "ft to m",
27
+ "m to in",
28
+ "in to m",
29
+ "mm to in",
30
+ "in to mm",
31
+ "kg to lb",
32
+ "lb to kg",
33
+ "L to gal",
34
+ "gal to L",
35
+ "kW to hp",
36
+ "hp to kW",
37
+ "rad to deg",
38
+ "deg to rad",
39
+ "s to min",
40
+ "min to s"
41
+ ];
42
+
43
43
  // Initialize runtime state
44
44
  node.runtime = {
45
45
  conversion: validConversions.includes(config.conversion) ? config.conversion : "C to F"
@@ -87,7 +87,7 @@ module.exports = function(RED) {
87
87
 
88
88
  const inputValue = parseFloat(msg.payload);
89
89
  if (isNaN(inputValue) || !isFinite(inputValue)) {
90
- node.status({ fill: "red", shape: "ring", text: "invalid payload" });
90
+ node.status({ fill: "red", shape: "ring", text: `Invalid payload` });
91
91
  if (done) done();
92
92
  return;
93
93
  }
@@ -96,62 +96,62 @@ module.exports = function(RED) {
96
96
  let output, inUnit, outUnit;
97
97
  switch (node.runtime.conversion) {
98
98
  case "F to C":
99
- output = (msg.payload - 32) * 5 / 9;
99
+ output = (inputValue - 32) * 5 / 9;
100
100
  inUnit = "°F";
101
101
  outUnit = "°C";
102
102
  break;
103
103
  case "C to F":
104
- output = (msg.payload * 9 / 5) + 32;
104
+ output = (inputValue * 9 / 5) + 32;
105
105
  inUnit = "°C";
106
106
  outUnit = "°F";
107
107
  break;
108
108
  case "decimal to %":
109
- output = msg.payload * 100;
109
+ output = inputValue * 100;
110
110
  inUnit = "";
111
111
  outUnit = "%";
112
112
  break;
113
113
  case "% to decimal":
114
- output = msg.payload / 100;
114
+ output = inputValue / 100;
115
115
  inUnit = "%";
116
116
  outUnit = "";
117
117
  break;
118
118
  case "Pa to inH₂O":
119
- output = msg.payload * 0.00401463;
119
+ output = inputValue * 0.00401463;
120
120
  inUnit = "Pa";
121
121
  outUnit = "inH₂O";
122
122
  break;
123
123
  case "inH₂O to Pa":
124
- output = msg.payload / 0.00401463;
124
+ output = inputValue / 0.00401463;
125
125
  inUnit = "inH₂O";
126
126
  outUnit = "Pa";
127
127
  break;
128
128
  case "Pa to inHg":
129
- output = msg.payload * 0.0002953;
129
+ output = inputValue * 0.0002953;
130
130
  inUnit = "Pa";
131
131
  outUnit = "inHg";
132
132
  break;
133
133
  case "inHg to Pa":
134
- output = msg.payload / 0.0002953;
134
+ output = inputValue / 0.0002953;
135
135
  inUnit = "inHg";
136
136
  outUnit = "Pa";
137
137
  break;
138
138
  case "Pa to bar":
139
- output = msg.payload * 0.00001;
139
+ output = inputValue * 0.00001;
140
140
  inUnit = "Pa";
141
141
  outUnit = "bar";
142
142
  break;
143
143
  case "bar to Pa":
144
- output = msg.payload / 0.00001;
144
+ output = inputValue / 0.00001;
145
145
  inUnit = "bar";
146
146
  outUnit = "Pa";
147
147
  break;
148
148
  case "Pa to psi":
149
- output = msg.payload * 0.000145038;
149
+ output = inputValue * 0.000145038;
150
150
  inUnit = "Pa";
151
151
  outUnit = "psi";
152
152
  break;
153
153
  case "psi to Pa":
154
- output = msg.payload / 0.000145038;
154
+ output = inputValue / 0.000145038;
155
155
  inUnit = "psi";
156
156
  outUnit = "Pa";
157
157
  break;