@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.
- package/nodes/accumulate-block.html +18 -8
- package/nodes/accumulate-block.js +39 -44
- package/nodes/add-block.html +1 -1
- package/nodes/add-block.js +18 -11
- package/nodes/alarm-collector.html +260 -0
- package/nodes/alarm-collector.js +292 -0
- package/nodes/alarm-config.html +129 -0
- package/nodes/alarm-config.js +126 -0
- package/nodes/alarm-service.html +96 -0
- package/nodes/alarm-service.js +142 -0
- package/nodes/analog-switch-block.js +25 -36
- package/nodes/and-block.js +44 -15
- package/nodes/average-block.js +46 -41
- package/nodes/boolean-switch-block.js +10 -28
- package/nodes/boolean-to-number-block.html +18 -5
- package/nodes/boolean-to-number-block.js +24 -16
- package/nodes/cache-block.js +24 -37
- package/nodes/call-status-block.html +91 -32
- package/nodes/call-status-block.js +398 -115
- package/nodes/changeover-block.html +5 -0
- package/nodes/changeover-block.js +167 -162
- package/nodes/comment-block.html +1 -1
- package/nodes/comment-block.js +14 -9
- package/nodes/compare-block.html +14 -4
- package/nodes/compare-block.js +23 -18
- package/nodes/contextual-label-block.html +5 -0
- package/nodes/contextual-label-block.js +6 -16
- package/nodes/convert-block.html +25 -39
- package/nodes/convert-block.js +31 -16
- package/nodes/count-block.html +11 -5
- package/nodes/count-block.js +34 -32
- package/nodes/delay-block.js +58 -53
- package/nodes/divide-block.js +43 -45
- package/nodes/edge-block.html +17 -10
- package/nodes/edge-block.js +43 -41
- package/nodes/enum-switch-block.js +6 -6
- package/nodes/frequency-block.html +6 -1
- package/nodes/frequency-block.js +64 -74
- package/nodes/global-getter.html +51 -15
- package/nodes/global-getter.js +43 -13
- package/nodes/global-setter.html +1 -1
- package/nodes/global-setter.js +40 -12
- package/nodes/history-buffer.html +96 -0
- package/nodes/history-buffer.js +461 -0
- package/nodes/history-collector.html +29 -1
- package/nodes/history-collector.js +37 -16
- package/nodes/history-config.html +13 -1
- package/nodes/history-service.html +84 -0
- package/nodes/history-service.js +52 -0
- package/nodes/hysteresis-block.html +5 -0
- package/nodes/hysteresis-block.js +13 -16
- package/nodes/interpolate-block.html +20 -2
- package/nodes/interpolate-block.js +39 -50
- package/nodes/join.html +78 -0
- package/nodes/join.js +78 -0
- package/nodes/latch-block.js +12 -14
- package/nodes/load-sequence-block.js +102 -110
- package/nodes/max-block.js +26 -26
- package/nodes/memory-block.js +57 -58
- package/nodes/min-block.js +26 -25
- package/nodes/minmax-block.js +35 -34
- package/nodes/modulo-block.js +45 -43
- package/nodes/multiply-block.js +43 -41
- package/nodes/negate-block.html +17 -7
- package/nodes/negate-block.js +25 -19
- package/nodes/network-point-read.html +128 -0
- package/nodes/network-point-read.js +230 -0
- package/nodes/{network-register.html → network-point-register.html} +94 -7
- package/nodes/{network-register.js → network-point-register.js} +18 -4
- package/nodes/network-point-write.html +149 -0
- package/nodes/network-point-write.js +222 -0
- package/nodes/network-service-bridge.html +131 -0
- package/nodes/network-service-bridge.js +376 -0
- package/nodes/network-service-read.html +81 -0
- package/nodes/{network-read.js → network-service-read.js} +4 -3
- package/nodes/{network-point-registry.html → network-service-registry.html} +19 -4
- package/nodes/{network-point-registry.js → network-service-registry.js} +7 -2
- package/nodes/network-service-write.html +89 -0
- package/nodes/{network-write.js → network-service-write.js} +3 -3
- package/nodes/nullify-block.js +13 -15
- package/nodes/on-change-block.html +17 -9
- package/nodes/on-change-block.js +49 -46
- package/nodes/oneshot-block.html +13 -10
- package/nodes/oneshot-block.js +57 -75
- package/nodes/or-block.js +44 -15
- package/nodes/pid-block.html +54 -4
- package/nodes/pid-block.js +459 -248
- package/nodes/priority-block.js +24 -35
- package/nodes/rate-limit-block.js +70 -72
- package/nodes/rate-of-change-block.html +33 -14
- package/nodes/rate-of-change-block.js +74 -62
- package/nodes/round-block.html +14 -9
- package/nodes/round-block.js +32 -25
- package/nodes/saw-tooth-wave-block.js +49 -76
- package/nodes/scale-range-block.html +12 -6
- package/nodes/scale-range-block.js +46 -39
- package/nodes/sine-wave-block.js +49 -57
- package/nodes/string-builder-block.js +6 -6
- package/nodes/subtract-block.js +38 -34
- package/nodes/thermistor-block.js +44 -44
- package/nodes/tick-tock-block.js +32 -32
- package/nodes/time-sequence-block.js +30 -42
- package/nodes/triangle-wave-block.js +49 -69
- package/nodes/tstat-block.js +34 -44
- package/nodes/units-block.html +90 -69
- package/nodes/units-block.js +22 -30
- package/nodes/utils.js +206 -3
- package/package.json +14 -6
- package/nodes/network-read.html +0 -56
- 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
|
|
10
|
-
node.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
64
|
-
if (!isNaN(results[1])) node.
|
|
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.
|
|
76
|
-
node.
|
|
77
|
-
|
|
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.
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
utils.setStatusError(node, "invalid reset");
|
|
99
98
|
if (done) done();
|
|
100
99
|
return;
|
|
101
100
|
}
|
|
102
101
|
if (msg.payload === true) {
|
|
103
|
-
node.
|
|
104
|
-
node.
|
|
105
|
-
|
|
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
|
-
|
|
111
|
+
utils.setStatusError(node, "sample size must be at least 2");
|
|
113
112
|
if (done) done();
|
|
114
113
|
return;
|
|
115
114
|
}
|
|
116
|
-
node.
|
|
115
|
+
node.maxSamples = newMaxSamples;
|
|
117
116
|
// Trim samples if new window is smaller
|
|
118
|
-
if (node.
|
|
119
|
-
node.
|
|
117
|
+
if (node.samples.length > newMaxSamples) {
|
|
118
|
+
node.samples = node.samples.slice(-newMaxSamples);
|
|
120
119
|
}
|
|
121
|
-
|
|
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.
|
|
128
|
-
|
|
126
|
+
node.units = msg.payload.toLowerCase();
|
|
127
|
+
utils.setStatusOK(node, `units: ${msg.payload}`);
|
|
129
128
|
} else {
|
|
130
|
-
|
|
129
|
+
utils.setStatusError(node, "invalid units");
|
|
131
130
|
}
|
|
132
131
|
break;
|
|
133
132
|
|
|
134
133
|
default:
|
|
135
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
154
|
-
|
|
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.
|
|
172
|
+
node.samples.push({ timestamp: timestamp, value: inputValue });
|
|
161
173
|
|
|
162
174
|
// Maintain sample window
|
|
163
|
-
if (node.
|
|
164
|
-
node.
|
|
165
|
-
} else if (node.
|
|
166
|
-
node.
|
|
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.
|
|
184
|
+
if (node.samples.length >= node.maxSamples * 0.20) {
|
|
173
185
|
// Use linear regression for more stable rate calculation
|
|
174
|
-
const n = node.
|
|
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.
|
|
190
|
+
const baseTime = node.samples[0].timestamp;
|
|
179
191
|
let timeScale; // Conversion factor from ms to selected units
|
|
180
192
|
|
|
181
|
-
switch (node.
|
|
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.
|
|
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.
|
|
216
|
-
const lastSample = node.
|
|
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.
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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.
|
|
249
|
+
node.lastRate = rate;
|
|
238
250
|
|
|
239
251
|
// Enhanced output with metadata
|
|
240
252
|
const outputMsg = {
|
|
241
253
|
payload: rate,
|
|
242
|
-
samples: node.
|
|
243
|
-
units: `${unitsDisplay[node.
|
|
254
|
+
samples: node.samples.length,
|
|
255
|
+
units: `${unitsDisplay[node.units] || "/min"}`,
|
|
244
256
|
currentValue: inputValue,
|
|
245
|
-
timeSpan: node.
|
|
246
|
-
(node.
|
|
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.
|
|
261
|
+
if (node.samples.length >= node.maxSamples) {
|
|
250
262
|
send(outputMsg);
|
|
251
263
|
}
|
|
252
264
|
|
package/nodes/round-block.html
CHANGED
|
@@ -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
|
|
46
|
+
Rounds a float to the nearest configurable precision (tenth, half, or whole number).
|
|
42
47
|
|
|
43
48
|
### Inputs
|
|
44
|
-
:
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
If
|
|
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
|
package/nodes/round-block.js
CHANGED
|
@@ -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
|
|
7
|
-
node.
|
|
8
|
-
|
|
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.
|
|
15
|
-
node.
|
|
16
|
-
|
|
14
|
+
if (!validPrecisions.includes(node.precision)) {
|
|
15
|
+
node.precision = "1.0";
|
|
16
|
+
utils.setStatusError(node, "invalid precision, using 1.0");
|
|
17
17
|
} else {
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
+
utils.setStatusError(node, "invalid precision");
|
|
41
41
|
if (done) done();
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
node.
|
|
45
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
|
59
|
-
if (
|
|
60
|
-
|
|
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.
|
|
75
|
+
const precision = parseFloat(node.precision);
|
|
69
76
|
if (precision === 0.01) {
|
|
70
|
-
result = Math.round(
|
|
77
|
+
result = Math.round(inputValue * 100) / 100;
|
|
71
78
|
} else if (precision === 0.1) {
|
|
72
|
-
result = Math.round(
|
|
79
|
+
result = Math.round(inputValue * 10) / 10;
|
|
73
80
|
} else if (precision === 0.5) {
|
|
74
|
-
result = Math.round(
|
|
81
|
+
result = Math.round(inputValue / 0.5) * 0.5;
|
|
75
82
|
} else {
|
|
76
|
-
result = Math.round(
|
|
83
|
+
result = Math.round(inputValue);
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
msg.payload = result;
|
|
80
|
-
|
|
87
|
+
utils.setStatusOK(node, `in: ${inputValue.toFixed(3)}, out: ${result}`);
|
|
81
88
|
send(msg);
|
|
82
89
|
if (done) done();
|
|
83
90
|
});
|