@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.
- package/nodes/average-block.js +34 -27
- package/nodes/changeover-block.html +32 -20
- package/nodes/changeover-block.js +67 -77
- package/nodes/convert-block.js +50 -50
- package/nodes/delay-block.js +28 -21
- package/nodes/hysteresis-block.js +26 -28
- package/nodes/max-block.js +24 -13
- package/nodes/memory-block.js +9 -3
- package/nodes/min-block.js +23 -12
- package/nodes/minmax-block.js +30 -22
- package/nodes/negate-block.html +1 -3
- package/nodes/negate-block.js +4 -23
- package/nodes/on-change-block.html +2 -2
- package/nodes/on-change-block.js +27 -30
- package/nodes/rate-limit-block.js +10 -44
- package/nodes/tstat-block.html +1 -329
- package/nodes/tstat-block.js +93 -231
- package/nodes/utils.js +31 -0
- package/package.json +4 -4
package/nodes/average-block.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
try {
|
|
15
|
-
node.runtime.minValid = RED.util.evaluateNodeProperty(
|
|
16
|
-
config.minValid, config.minValidType, node
|
|
17
|
-
);
|
|
15
|
+
const typedProperties = ['minValid', 'maxValid'];
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
);
|
|
29
|
-
node.runtime.
|
|
30
|
-
|
|
31
|
-
node.runtime.
|
|
32
|
-
|
|
33
|
-
);
|
|
34
|
-
node.runtime.
|
|
35
|
-
|
|
36
|
-
node.
|
|
37
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
277
|
-
coolingThreshold = node.runtime.setpoint + node.runtime.deadband / 2
|
|
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
|
|
311
|
-
coolingThreshold = node.runtime.setpoint + node.runtime.deadband / 2
|
|
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
|
|
338
|
+
let effectiveHeatingSetpoint, effectiveCoolingSetpoint;
|
|
350
339
|
if (node.runtime.algorithm === "single") {
|
|
351
|
-
|
|
352
|
-
|
|
340
|
+
effectiveHeatingSetpoint = node.runtime.setpoint - node.runtime.deadband / 2;
|
|
341
|
+
effectiveCoolingSetpoint = node.runtime.setpoint + node.runtime.deadband / 2;
|
|
353
342
|
} else {
|
|
354
|
-
|
|
355
|
-
|
|
343
|
+
effectiveHeatingSetpoint = node.runtime.heatingSetpoint;
|
|
344
|
+
effectiveCoolingSetpoint = node.runtime.coolingSetpoint;
|
|
356
345
|
}
|
|
357
346
|
|
|
358
347
|
return [
|
|
359
|
-
{
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
package/nodes/convert-block.js
CHANGED
|
@@ -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:
|
|
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 = (
|
|
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 = (
|
|
104
|
+
output = (inputValue * 9 / 5) + 32;
|
|
105
105
|
inUnit = "°C";
|
|
106
106
|
outUnit = "°F";
|
|
107
107
|
break;
|
|
108
108
|
case "decimal to %":
|
|
109
|
-
output =
|
|
109
|
+
output = inputValue * 100;
|
|
110
110
|
inUnit = "";
|
|
111
111
|
outUnit = "%";
|
|
112
112
|
break;
|
|
113
113
|
case "% to decimal":
|
|
114
|
-
output =
|
|
114
|
+
output = inputValue / 100;
|
|
115
115
|
inUnit = "%";
|
|
116
116
|
outUnit = "";
|
|
117
117
|
break;
|
|
118
118
|
case "Pa to inH₂O":
|
|
119
|
-
output =
|
|
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 =
|
|
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 =
|
|
129
|
+
output = inputValue * 0.0002953;
|
|
130
130
|
inUnit = "Pa";
|
|
131
131
|
outUnit = "inHg";
|
|
132
132
|
break;
|
|
133
133
|
case "inHg to Pa":
|
|
134
|
-
output =
|
|
134
|
+
output = inputValue / 0.0002953;
|
|
135
135
|
inUnit = "inHg";
|
|
136
136
|
outUnit = "Pa";
|
|
137
137
|
break;
|
|
138
138
|
case "Pa to bar":
|
|
139
|
-
output =
|
|
139
|
+
output = inputValue * 0.00001;
|
|
140
140
|
inUnit = "Pa";
|
|
141
141
|
outUnit = "bar";
|
|
142
142
|
break;
|
|
143
143
|
case "bar to Pa":
|
|
144
|
-
output =
|
|
144
|
+
output = inputValue / 0.00001;
|
|
145
145
|
inUnit = "bar";
|
|
146
146
|
outUnit = "Pa";
|
|
147
147
|
break;
|
|
148
148
|
case "Pa to psi":
|
|
149
|
-
output =
|
|
149
|
+
output = inputValue * 0.000145038;
|
|
150
150
|
inUnit = "Pa";
|
|
151
151
|
outUnit = "psi";
|
|
152
152
|
break;
|
|
153
153
|
case "psi to Pa":
|
|
154
|
-
output =
|
|
154
|
+
output = inputValue / 0.000145038;
|
|
155
155
|
inUnit = "psi";
|
|
156
156
|
outUnit = "Pa";
|
|
157
157
|
break;
|