@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
@@ -6,22 +6,21 @@ module.exports = function(RED) {
6
6
  const node = this;
7
7
  node.isBusy = false;
8
8
 
9
- // Initialize runtime state
10
- node.runtime = {
11
- maxSamples: parseInt(config.sampleSize),
12
- samples: [], // Array of {timestamp: Date, value: number}
13
- units: config.units || "minutes", // minutes, seconds, hours
14
- lastRate: null,
15
- minValid: parseFloat(config.minValid),
16
- maxValid: parseFloat(config.maxValid)
17
- };
9
+ // Initialize state
10
+ node.maxSamples = parseInt(config.sampleSize);
11
+ node.inputProperty = config.inputProperty || "payload";
12
+ node.samples = []; // Array of {timestamp: Date, value: number}
13
+ node.units = config.units || "minutes"; // minutes, seconds, hours
14
+ node.lastRate = null;
15
+ node.minValid = parseFloat(config.minValid);
16
+ node.maxValid = parseFloat(config.maxValid);
18
17
 
19
18
  node.on("input", async function(msg, send, done) {
20
19
  send = send || function() { node.send.apply(node, arguments); };
21
20
 
22
21
  // Guard against invalid msg
23
22
  if (!msg) {
24
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
23
+ utils.setStatusError(node, "invalid message");
25
24
  if (done) done();
26
25
  return;
27
26
  }
@@ -32,7 +31,7 @@ module.exports = function(RED) {
32
31
  // Check busy lock
33
32
  if (node.isBusy) {
34
33
  // Update status to let user know they are pushing too fast
35
- node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
34
+ utils.setStatusBusy(node, "busy - dropped msg");
36
35
  if (done) done();
37
36
  return;
38
37
  }
@@ -47,21 +46,21 @@ module.exports = function(RED) {
47
46
  utils.requiresEvaluation(config.minValidType)
48
47
  ? utils.evaluateNodeProperty(config.minValid, config.minValidType, node, msg)
49
48
  .then(val => parseFloat(val))
50
- : Promise.resolve(node.runtime.minValid),
49
+ : Promise.resolve(node.minValid),
51
50
  );
52
51
 
53
52
  evaluations.push(
54
53
  utils.requiresEvaluation(config.maxValidType)
55
54
  ? utils.evaluateNodeProperty(config.maxValid, config.maxValidType, node, msg)
56
55
  .then(val => parseFloat(val))
57
- : Promise.resolve(node.runtime.maxValid),
56
+ : Promise.resolve(node.maxValid),
58
57
  );
59
58
 
60
59
  const results = await Promise.all(evaluations);
61
60
 
62
61
  // Update runtime with evaluated values
63
- if (!isNaN(results[0])) node.runtime.minValid = results[0];
64
- if (!isNaN(results[1])) node.runtime.maxValid = results[1];
62
+ if (!isNaN(results[0])) node.minValid = results[0];
63
+ if (!isNaN(results[1])) node.maxValid = results[1];
65
64
  } catch (err) {
66
65
  node.error(`Error evaluating properties: ${err.message}`);
67
66
  if (done) done();
@@ -72,14 +71,14 @@ module.exports = function(RED) {
72
71
  }
73
72
 
74
73
  // Acceptable fallbacks
75
- if (isNaN(node.runtime.maxSamples) || node.runtime.maxSamples < 2) {
76
- node.runtime.maxSamples = 10;
77
- node.status({ fill: "red", shape: "ring", text: "invalid sample size, using 10" });
74
+ if (isNaN(node.maxSamples) || node.maxSamples < 2) {
75
+ node.maxSamples = 10;
76
+ utils.setStatusError(node, "invalid sample size, using 10");
78
77
  }
79
78
 
80
79
  // Validate values
81
- if (isNaN(node.runtime.maxValid) || isNaN(node.runtime.minValid) || node.runtime.maxValid <= node.runtime.minValid ) {
82
- node.status({ fill: "red", shape: "ring", text: `invalid evaluated values ${node.runtime.minValid}, ${node.runtime.maxValid}` });
80
+ if (isNaN(node.maxValid) || isNaN(node.minValid) || node.maxValid <= node.minValid ) {
81
+ utils.setStatusError(node, `invalid evaluated values ${node.minValid}, ${node.maxValid}`);
83
82
  if (done) done();
84
83
  return;
85
84
  }
@@ -87,7 +86,7 @@ module.exports = function(RED) {
87
86
  // Handle configuration messages
88
87
  if (msg.hasOwnProperty("context")) {
89
88
  if (!msg.hasOwnProperty("payload")) {
90
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
89
+ utils.setStatusError(node, "missing payload");
91
90
  if (done) done();
92
91
  return;
93
92
  }
@@ -95,44 +94,44 @@ module.exports = function(RED) {
95
94
  switch (msg.context) {
96
95
  case "reset":
97
96
  if (typeof msg.payload !== "boolean") {
98
- node.status({ fill: "red", shape: "ring", text: "invalid reset" });
97
+ utils.setStatusError(node, "invalid reset");
99
98
  if (done) done();
100
99
  return;
101
100
  }
102
101
  if (msg.payload === true) {
103
- node.runtime.samples = [];
104
- node.runtime.lastRate = null;
105
- node.status({ fill: "green", shape: "dot", text: "state reset" });
102
+ node.samples = [];
103
+ node.lastRate = null;
104
+ utils.setStatusOK(node, "state reset");
106
105
  }
107
106
  break;
108
107
 
109
108
  case "sampleSize":
110
109
  let newMaxSamples = parseInt(msg.payload);
111
110
  if (isNaN(newMaxSamples) || newMaxSamples < 2) {
112
- node.status({ fill: "red", shape: "ring", text: "sample size must be at least 2" });
111
+ utils.setStatusError(node, "sample size must be at least 2");
113
112
  if (done) done();
114
113
  return;
115
114
  }
116
- node.runtime.maxSamples = newMaxSamples;
115
+ node.maxSamples = newMaxSamples;
117
116
  // Trim samples if new window is smaller
118
- if (node.runtime.samples.length > newMaxSamples) {
119
- node.runtime.samples = node.runtime.samples.slice(-newMaxSamples);
117
+ if (node.samples.length > newMaxSamples) {
118
+ node.samples = node.samples.slice(-newMaxSamples);
120
119
  }
121
- node.status({ fill: "green", shape: "dot", text: `samples: ${newMaxSamples}` });
120
+ utils.setStatusOK(node, `samples: ${newMaxSamples}`);
122
121
  break;
123
122
 
124
123
  case "units":
125
124
  const validUnits = ["seconds", "minutes", "hours"];
126
125
  if (typeof msg.payload === "string" && validUnits.includes(msg.payload.toLowerCase())) {
127
- node.runtime.units = msg.payload.toLowerCase();
128
- node.status({ fill: "green", shape: "dot", text: `units: ${msg.payload}` });
126
+ node.units = msg.payload.toLowerCase();
127
+ utils.setStatusOK(node, `units: ${msg.payload}`);
129
128
  } else {
130
- node.status({ fill: "red", shape: "ring", text: "invalid units" });
129
+ utils.setStatusError(node, "invalid units");
131
130
  }
132
131
  break;
133
132
 
134
133
  default:
135
- node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
134
+ utils.setStatusWarn(node, "unknown context");
136
135
  break;
137
136
  }
138
137
  if (done) done();
@@ -141,44 +140,57 @@ module.exports = function(RED) {
141
140
 
142
141
  // Check for missing payload
143
142
  if (!msg.hasOwnProperty("payload")) {
144
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
143
+ utils.setStatusError(node, "missing payload");
144
+ if (done) done();
145
+ return;
146
+ }
147
+
148
+ // Get input from configured property
149
+ let input;
150
+ try {
151
+ input = RED.util.getMessageProperty(msg, node.inputProperty);
152
+ } catch (err) {
153
+ input = undefined;
154
+ }
155
+ if (input === undefined) {
156
+ utils.setStatusError(node, "missing or invalid input property");
145
157
  if (done) done();
146
158
  return;
147
159
  }
148
160
 
149
161
  // Process input
150
- const inputValue = parseFloat(msg.payload);
162
+ const inputValue = parseFloat(input);
151
163
  const timestamp = msg.timestamp ? new Date(msg.timestamp) : new Date();
152
164
 
153
- if (isNaN(inputValue) || inputValue < node.runtime.minValid || inputValue > node.runtime.maxValid) {
154
- node.status({ fill: "yellow", shape: "ring", text: "out of range" });
165
+ if (isNaN(inputValue) || inputValue < node.minValid || inputValue > node.maxValid) {
166
+ utils.setStatusWarn(node, "out of range");
155
167
  if (done) done();
156
168
  return;
157
169
  }
158
170
 
159
171
  // Add new sample
160
- node.runtime.samples.push({ timestamp: timestamp, value: inputValue });
172
+ node.samples.push({ timestamp: timestamp, value: inputValue });
161
173
 
162
174
  // Maintain sample window
163
- if (node.runtime.samples.length > node.runtime.maxSamples + 1) {
164
- node.runtime.samples = node.runtime.samples.slice(-node.runtime.maxSamples);
165
- } else if (node.runtime.samples.length > node.runtime.maxSamples) {
166
- node.runtime.samples.shift();
175
+ if (node.samples.length > node.maxSamples + 1) {
176
+ node.samples = node.samples.slice(-node.maxSamples);
177
+ } else if (node.samples.length > node.maxSamples) {
178
+ node.samples.shift();
167
179
  }
168
180
 
169
181
  // Calculate rate of change (temperature per time unit)
170
182
  let rate = null;
171
183
  // Require at least 20% of samples for calculation
172
- if (node.runtime.samples.length >= node.runtime.maxSamples * 0.20) {
184
+ if (node.samples.length >= node.maxSamples * 0.20) {
173
185
  // Use linear regression for more stable rate calculation
174
- const n = node.runtime.samples.length;
186
+ const n = node.samples.length;
175
187
  let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
176
188
 
177
189
  // Convert timestamps to relative time in the selected units
178
- const baseTime = node.runtime.samples[0].timestamp;
190
+ const baseTime = node.samples[0].timestamp;
179
191
  let timeScale; // Conversion factor from ms to selected units
180
192
 
181
- switch (node.runtime.units) {
193
+ switch (node.units) {
182
194
  case "seconds":
183
195
  timeScale = 1000; // ms to seconds
184
196
  break;
@@ -193,7 +205,7 @@ module.exports = function(RED) {
193
205
  }
194
206
 
195
207
  // Calculate regression sums
196
- node.runtime.samples.forEach((sample, i) => {
208
+ node.samples.forEach((sample, i) => {
197
209
  // Time in selected units
198
210
  const x = (sample.timestamp - baseTime) / timeScale;
199
211
  const y = sample.value;
@@ -212,14 +224,14 @@ module.exports = function(RED) {
212
224
  rate = (n * sumXY - sumX * sumY) / denominator;
213
225
  } else {
214
226
  // Fallback to original endpoint method if regression is unstable
215
- const firstSample = node.runtime.samples[0];
216
- const lastSample = node.runtime.samples[node.runtime.samples.length - 1];
227
+ const firstSample = node.samples[0];
228
+ const lastSample = node.samples[node.samples.length - 1];
217
229
  const timeDiff = (lastSample.timestamp - firstSample.timestamp) / timeScale;
218
230
  rate = timeDiff > 0 ? (lastSample.value - firstSample.value) / timeDiff : 0;
219
231
  }
220
232
  }
221
233
 
222
- const isUnchanged = rate === node.runtime.lastRate;
234
+ const isUnchanged = rate === node.lastRate;
223
235
 
224
236
  // Send new message
225
237
  const unitsDisplay = {
@@ -228,25 +240,25 @@ module.exports = function(RED) {
228
240
  hours: "/hr"
229
241
  };
230
242
 
231
- node.status({
232
- fill: "blue",
233
- shape: isUnchanged ? "ring" : "dot",
234
- text: `rate: ${rate !== null ? rate.toFixed(2) : "not ready"} ${unitsDisplay[node.runtime.units] || "/min"}`
235
- });
243
+ if (isUnchanged) {
244
+ utils.setStatusUnchanged(node, `rate: ${rate !== null ? rate.toFixed(2) : "not ready"} ${unitsDisplay[node.units] || "/min"}`);
245
+ } else {
246
+ utils.setStatusChanged(node, `rate: ${rate !== null ? rate.toFixed(2) : "not ready"} ${unitsDisplay[node.units] || "/min"}`);
247
+ }
236
248
 
237
- node.runtime.lastRate = rate;
249
+ node.lastRate = rate;
238
250
 
239
251
  // Enhanced output with metadata
240
252
  const outputMsg = {
241
253
  payload: rate,
242
- samples: node.runtime.samples.length,
243
- units: `${unitsDisplay[node.runtime.units] || "/min"}`,
254
+ samples: node.samples.length,
255
+ units: `${unitsDisplay[node.units] || "/min"}`,
244
256
  currentValue: inputValue,
245
- timeSpan: node.runtime.samples.length >= 2 ?
246
- (node.runtime.samples[node.runtime.samples.length - 1].timestamp - node.runtime.samples[0].timestamp) / 1000 : 0
257
+ timeSpan: node.samples.length >= 2 ?
258
+ (node.samples[node.samples.length - 1].timestamp - node.samples[0].timestamp) / 1000 : 0
247
259
  };
248
260
 
249
- if (node.runtime.samples.length >= node.runtime.maxSamples) {
261
+ if (node.samples.length >= node.maxSamples) {
250
262
  send(outputMsg);
251
263
  }
252
264
 
@@ -4,6 +4,10 @@
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">
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>
7
11
  <div class="form-row">
8
12
  <label for="node-input-precision" title="Rounding precision (0.1 for tenths, 0.5 for halves, 1.0 for whole numbers)"><i class="fa fa-ruler"></i> Precision</label>
9
13
  <select id="node-input-precision">
@@ -22,6 +26,7 @@
22
26
  color: "#301934",
23
27
  defaults: {
24
28
  name: { value: "" },
29
+ inputProperty: { value: "payload" },
25
30
  precision: { value: "0.01", required: true }
26
31
  },
27
32
  inputs: 1,
@@ -38,10 +43,10 @@
38
43
 
39
44
  <!-- Help Section -->
40
45
  <script type="text/markdown" data-help-name="round-block">
41
- Rounds a float in `msg.payload` to the nearest configurable precision (tenth, half, or whole number).
46
+ Rounds a float to the nearest configurable precision (tenth, half, or whole number).
42
47
 
43
48
  ### Inputs
44
- : payload (number) : Float to round.
49
+ : input-property (number) : Float to round, read from the configured Input Property.
45
50
  : context (string, optional) : Action (`"precision"` to set precision). Unknown `msg.context` values are ignored.
46
51
  : payload (string, for `"precision"`) : Precision value
47
52
 
@@ -50,16 +55,16 @@ Rounds a float in `msg.payload` to the nearest configurable precision (tenth, ha
50
55
 
51
56
  ### Properties
52
57
  : name (string) : Display name in editor.
58
+ : inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
53
59
  : precision (string) : Rounding precision
54
60
 
55
61
  ### Details
56
- Rounds a float in `msg.payload` to the nearest hundreth (0.01), tenth (0.1), half (0.5), or whole number (1.0), set via editor or `msg.context = "precision"`.
57
-
58
- Operates as a passthrough node, modifying `msg.payload` and forwarding the original message.
59
-
60
- If `msg.payload` is not a finite number, the message is passed unchanged.
61
-
62
- Unknown `msg.context` values are ignored.
62
+ Rounds a float to the nearest hundreth (0.01), tenth (0.1), half (0.5), or whole number (1.0), set via editor or `msg.context = "precision"`.
63
+ - Reads input from the message property specified in the **Input Property** field (default: `msg.payload`)
64
+ - Output is always written to `msg.payload`
65
+ - Operates as a passthrough node, modifying `msg.payload` and forwarding the original message
66
+ - If input is not a finite number, the message is passed unchanged
67
+ - Unknown `msg.context` values are ignored
63
68
 
64
69
  ### Status
65
70
  - Green (dot): Configuration update
@@ -1,21 +1,21 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
2
4
  function RoundBlockNode(config) {
3
5
  RED.nodes.createNode(this, config);
4
6
  const node = this;
5
7
 
6
- // Initialize runtime state
7
- node.runtime = {
8
- name: config.name,
9
- precision: config.precision
10
- };
8
+ // Initialize state
9
+ node.inputProperty = config.inputProperty || "payload";
10
+ node.precision = config.precision;
11
11
 
12
12
  // Validate initial config
13
13
  const validPrecisions = ["0.01", "0.1", "0.5", "1.0"];
14
- if (!validPrecisions.includes(node.runtime.precision)) {
15
- node.runtime.precision = "1.0";
16
- node.status({ fill: "red", shape: "ring", text: "invalid precision, using 1.0" });
14
+ if (!validPrecisions.includes(node.precision)) {
15
+ node.precision = "1.0";
16
+ utils.setStatusError(node, "invalid precision, using 1.0");
17
17
  } else {
18
- node.status({ fill: "green", shape: "dot", text: `name: ${node.runtime.name || "round"}, precision: ${node.runtime.precision}` });
18
+ utils.setStatusOK(node, `name: ${config.name || "round"}, precision: ${node.precision}`);
19
19
  }
20
20
 
21
21
  node.on("input", function(msg, send, done) {
@@ -23,7 +23,7 @@ module.exports = function(RED) {
23
23
 
24
24
  // Guard against invalid message
25
25
  if (!msg) {
26
- node.status({ fill: "red", shape: "ring", text: "invalid message" });
26
+ utils.setStatusError(node, "invalid message");
27
27
  if (done) done();
28
28
  return;
29
29
  }
@@ -31,53 +31,60 @@ module.exports = function(RED) {
31
31
  // Handle precision configuration
32
32
  if (msg.hasOwnProperty("context") && msg.context === "precision") {
33
33
  if (!msg.hasOwnProperty("payload")) {
34
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
34
+ utils.setStatusError(node, "missing payload");
35
35
  if (done) done();
36
36
  return;
37
37
  }
38
38
  const newPrecision = String(msg.payload);
39
39
  if (!validPrecisions.includes(newPrecision)) {
40
- node.status({ fill: "red", shape: "ring", text: "invalid precision" });
40
+ utils.setStatusError(node, "invalid precision");
41
41
  if (done) done();
42
42
  return;
43
43
  }
44
- node.runtime.precision = newPrecision;
45
- node.status({ fill: "green", shape: "dot", text: `precision: ${newPrecision}` });
44
+ node.precision = newPrecision;
45
+ utils.setStatusOK(node, `precision: ${newPrecision}`);
46
46
  if (done) done();
47
47
  return;
48
48
  }
49
49
 
50
50
  // Passthrough: Process payload if numeric, else pass unchanged
51
- if (!msg.hasOwnProperty("payload")) {
52
- node.status({ fill: "red", shape: "ring", text: "missing payload" });
51
+ let input;
52
+ try {
53
+ input = RED.util.getMessageProperty(msg, node.inputProperty);
54
+ } catch (err) {
55
+ input = undefined;
56
+ }
57
+ if (input === undefined) {
58
+ utils.setStatusError(node, "missing or invalid input property");
53
59
  send(msg);
54
60
  if (done) done();
55
61
  return;
56
62
  }
57
63
 
58
- const input = parseFloat(msg.payload);
59
- if (isNaN(input) || !isFinite(input)) {
60
- node.status({ fill: "red", shape: "ring", text: "invalid input" });
64
+ const numVal = utils.validateNumericPayload(input);
65
+ if (!numVal.valid) {
66
+ utils.setStatusError(node, numVal.error);
61
67
  send(msg);
62
68
  if (done) done();
63
69
  return;
64
70
  }
71
+ const inputValue = numVal.value;
65
72
 
66
73
  // Round based on precision
67
74
  let result;
68
- const precision = parseFloat(node.runtime.precision);
75
+ const precision = parseFloat(node.precision);
69
76
  if (precision === 0.01) {
70
- result = Math.round(input * 100) / 100;
77
+ result = Math.round(inputValue * 100) / 100;
71
78
  } else if (precision === 0.1) {
72
- result = Math.round(input * 10) / 10;
79
+ result = Math.round(inputValue * 10) / 10;
73
80
  } else if (precision === 0.5) {
74
- result = Math.round(input / 0.5) * 0.5;
81
+ result = Math.round(inputValue / 0.5) * 0.5;
75
82
  } else {
76
- result = Math.round(input);
83
+ result = Math.round(inputValue);
77
84
  }
78
85
 
79
86
  msg.payload = result;
80
- node.status({ fill: "blue", shape: "dot", text: `in: ${input.toFixed(2)}, out: ${result}` });
87
+ utils.setStatusOK(node, `in: ${inputValue.toFixed(3)}, out: ${result}`);
81
88
  send(msg);
82
89
  if (done) done();
83
90
  });