@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
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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.
|
|
19
|
-
node.
|
|
20
|
-
node.
|
|
21
|
-
|
|
22
|
-
} else if (node.
|
|
23
|
-
node.
|
|
24
|
-
|
|
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.
|
|
27
|
-
node.
|
|
28
|
-
node.
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
63
|
-
if (node.
|
|
64
|
-
node.
|
|
65
|
-
node.
|
|
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.
|
|
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.
|
|
80
|
-
if (node.
|
|
81
|
-
node.
|
|
82
|
-
node.
|
|
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.
|
|
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
|
-
|
|
83
|
+
utils.setStatusError(node, "invalid period");
|
|
100
84
|
if (done) done();
|
|
101
85
|
return;
|
|
102
86
|
}
|
|
103
|
-
node.
|
|
104
|
-
node.
|
|
105
|
-
node.
|
|
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
|
-
|
|
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.
|
|
123
|
-
node.
|
|
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.
|
|
127
|
-
|
|
128
|
-
send({ payload: node.
|
|
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.
|
|
114
|
+
node.phase = (node.phase + deltaTime / (node.period / 1000)) % 1;
|
|
135
115
|
|
|
136
116
|
// Triangle wave calculation
|
|
137
|
-
const triangleValue = node.
|
|
138
|
-
const amplitude = (node.
|
|
139
|
-
const value = node.
|
|
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
|
-
|
|
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();
|
package/nodes/tstat-block.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
utils.setStatusOK(node, `algorithm: ${msg.payload}`);
|
|
179
179
|
} else {
|
|
180
|
-
|
|
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
|
-
|
|
186
|
+
utils.setStatusOK(node, `setpoint: ${msg.payload.toFixed(2)}`);
|
|
187
187
|
} else {
|
|
188
|
-
|
|
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
|
-
|
|
195
|
-
});
|
|
194
|
+
utils.setStatusOK(node, `heatingSetpoint: ${msg.payload.toFixed(2)}`);
|
|
196
195
|
} else {
|
|
197
|
-
|
|
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
|
-
|
|
202
|
+
utils.setStatusOK(node, `coolingSetpoint: ${msg.payload.toFixed(2)}`);
|
|
204
203
|
} else {
|
|
205
|
-
|
|
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
|
-
|
|
210
|
+
utils.setStatusOK(node, `coolingOn: ${msg.payload.toFixed(2)}`);
|
|
212
211
|
} else {
|
|
213
|
-
|
|
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
|
-
|
|
220
|
-
});
|
|
218
|
+
utils.setStatusOK(node, `coolingOff: ${msg.payload.toFixed(2)}`);
|
|
221
219
|
} else {
|
|
222
|
-
|
|
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
|
-
|
|
226
|
+
utils.setStatusOK(node, `heatingOff: ${msg.payload.toFixed(2)}`);
|
|
229
227
|
} else {
|
|
230
|
-
|
|
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
|
-
|
|
234
|
+
utils.setStatusOK(node, `heatingOn: ${msg.payload.toFixed(2)}`);
|
|
237
235
|
} else {
|
|
238
|
-
|
|
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
|
-
|
|
242
|
+
utils.setStatusOK(node, `diff: ${msg.payload.toFixed(2)}`);
|
|
245
243
|
} else {
|
|
246
|
-
|
|
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
|
-
|
|
250
|
+
utils.setStatusOK(node, `anticipator: ${msg.payload.toFixed(2)}`);
|
|
253
251
|
} else {
|
|
254
|
-
|
|
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
|
-
|
|
258
|
+
utils.setStatusOK(node, `ignoreAnticipatorCycles: ${Math.floor(msg.payload)}`);
|
|
261
259
|
} else {
|
|
262
|
-
|
|
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
|
-
|
|
266
|
+
utils.setStatusOK(node, `isHeating: ${msg.payload}`);
|
|
269
267
|
} else {
|
|
270
|
-
|
|
268
|
+
utils.setStatusError(node, "invalid isHeating");
|
|
271
269
|
}
|
|
272
270
|
break;
|
|
273
271
|
default:
|
|
274
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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();
|
package/nodes/units-block.html
CHANGED
|
@@ -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
|
-
<
|
|
10
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
:
|
|
98
|
-
:
|
|
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>
|