@bldgblocks/node-red-contrib-control 0.1.30 → 0.1.31
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/LICENSE.md +202 -0
- package/nodes/average-block.js +43 -22
- package/nodes/changeover-block.js +119 -55
- package/nodes/delay-block.html +2 -2
- package/nodes/delay-block.js +47 -18
- package/nodes/enum-switch-block.js +36 -7
- package/nodes/global-getter.html +21 -56
- package/nodes/global-getter.js +64 -30
- package/nodes/global-setter.html +53 -14
- package/nodes/global-setter.js +150 -35
- package/nodes/hysteresis-block.js +67 -11
- package/nodes/max-block.js +37 -16
- package/nodes/min-block.js +36 -19
- package/nodes/minmax-block.js +43 -17
- package/nodes/on-change-block.js +34 -13
- package/nodes/pid-block.js +108 -44
- package/nodes/rate-of-change-block.js +43 -17
- package/nodes/string-builder-block.js +58 -25
- package/nodes/tstat-block.js +134 -60
- package/nodes/utils.js +17 -2
- package/package.json +2 -2
|
@@ -4,19 +4,15 @@ module.exports = function(RED) {
|
|
|
4
4
|
function StringBuilderBlockNode(config) {
|
|
5
5
|
RED.nodes.createNode(this, config);
|
|
6
6
|
const node = this;
|
|
7
|
+
node.isBusy = false;
|
|
8
|
+
|
|
7
9
|
node.name = config.name;
|
|
10
|
+
node.in1 = config.in1;
|
|
11
|
+
node.in2 = config.in2;
|
|
12
|
+
node.in3 = config.in3;
|
|
13
|
+
node.in4 = config.in4;
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
node.in1 = RED.util.evaluateNodeProperty( config.in1, config.in1Type, node );
|
|
12
|
-
node.in2 = RED.util.evaluateNodeProperty( config.in2, config.in2Type, node );
|
|
13
|
-
node.in3 = RED.util.evaluateNodeProperty( config.in3, config.in3Type, node );
|
|
14
|
-
node.in4 = RED.util.evaluateNodeProperty( config.in4, config.in4Type, node );
|
|
15
|
-
} catch (err) {
|
|
16
|
-
node.error(`Error evaluating properties: ${err.message}`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
node.on("input", function(msg, send, done) {
|
|
15
|
+
node.on("input", async function(msg, send, done) {
|
|
20
16
|
send = send || function() { node.send.apply(node, arguments); };
|
|
21
17
|
|
|
22
18
|
if (!msg) {
|
|
@@ -24,25 +20,62 @@ module.exports = function(RED) {
|
|
|
24
20
|
if (done) done();
|
|
25
21
|
return;
|
|
26
22
|
}
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
node.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
node.in3 = RED.util.evaluateNodeProperty( config.in3, config.in3Type, node, msg );
|
|
38
|
-
}
|
|
39
|
-
if (utils.requiresEvaluation(config.in4Type)) {
|
|
40
|
-
node.in4 = RED.util.evaluateNodeProperty( config.in4, config.in4Type, node, msg );
|
|
23
|
+
|
|
24
|
+
// Evaluate dynamic properties
|
|
25
|
+
try {
|
|
26
|
+
|
|
27
|
+
// Check busy lock
|
|
28
|
+
if (node.isBusy) {
|
|
29
|
+
// Update status to let user know they are pushing too fast
|
|
30
|
+
node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
|
|
31
|
+
if (done) done();
|
|
32
|
+
return;
|
|
41
33
|
}
|
|
34
|
+
|
|
35
|
+
// Lock node during evaluation
|
|
36
|
+
node.isBusy = true;
|
|
37
|
+
|
|
38
|
+
// Begin evaluations
|
|
39
|
+
const evaluations = [];
|
|
40
|
+
|
|
41
|
+
evaluations.push(
|
|
42
|
+
utils.requiresEvaluation(config.in1Type)
|
|
43
|
+
? utils.evaluateNodeProperty(config.in1, config.in1Type, node, msg)
|
|
44
|
+
: Promise.resolve(node.in1),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
evaluations.push(
|
|
48
|
+
utils.requiresEvaluation(config.in2Type)
|
|
49
|
+
? utils.evaluateNodeProperty(config.in2, config.in2Type, node, msg)
|
|
50
|
+
: Promise.resolve(node.in2),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
evaluations.push(
|
|
54
|
+
utils.requiresEvaluation(config.in3Type)
|
|
55
|
+
? utils.evaluateNodeProperty(config.in3, config.in3Type, node, msg)
|
|
56
|
+
: Promise.resolve(node.in3),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
evaluations.push(
|
|
60
|
+
utils.requiresEvaluation(config.in4Type)
|
|
61
|
+
? utils.evaluateNodeProperty(config.in4, config.in4Type, node, msg)
|
|
62
|
+
: Promise.resolve(node.in4),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const results = await Promise.all(evaluations);
|
|
66
|
+
|
|
67
|
+
// Update runtime with evaluated values
|
|
68
|
+
if (results[0] != null) node.in1 = results[0];
|
|
69
|
+
if (results[1] != null) node.in2 = results[1];
|
|
70
|
+
if (results[2] != null) node.in3 = results[2];
|
|
71
|
+
if (results[3] != null) node.in4 = results[3];
|
|
42
72
|
} catch (err) {
|
|
43
73
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
44
74
|
if (done) done();
|
|
45
75
|
return;
|
|
76
|
+
} finally {
|
|
77
|
+
// Release, all synchronous from here on
|
|
78
|
+
node.isBusy = false;
|
|
46
79
|
}
|
|
47
80
|
|
|
48
81
|
// Check required properties
|
package/nodes/tstat-block.js
CHANGED
|
@@ -4,29 +4,22 @@ module.exports = function(RED) {
|
|
|
4
4
|
function TstatBlockNode(config) {
|
|
5
5
|
RED.nodes.createNode(this, config);
|
|
6
6
|
const node = this;
|
|
7
|
+
node.isBusy = false;
|
|
7
8
|
|
|
8
9
|
// Store typed-input properties
|
|
9
|
-
node.isHeating = config.isHeating;
|
|
10
|
-
node.algorithm = config.algorithm;
|
|
11
10
|
node.name = config.name;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node ));
|
|
25
|
-
node.isHeating = RED.util.evaluateNodeProperty( config.isHeating, config.isHeatingType, node ) === true;
|
|
26
|
-
node.algorithm = RED.util.evaluateNodeProperty( config.algorithm, config.algorithmType, node );
|
|
27
|
-
} catch (err) {
|
|
28
|
-
node.error(`Error evaluating properties: ${err.message}`);
|
|
29
|
-
}
|
|
11
|
+
node.setpoint = parseFloat(config.setpoint);
|
|
12
|
+
node.heatingSetpoint = parseFloat(config.heatingSetpoint);
|
|
13
|
+
node.coolingSetpoint = parseFloat(config.coolingSetpoint);
|
|
14
|
+
node.coolingOn = parseFloat(config.coolingOn);
|
|
15
|
+
node.coolingOff = parseFloat(config.coolingOff);
|
|
16
|
+
node.heatingOff = parseFloat(config.heatingOff);
|
|
17
|
+
node.heatingOn = parseFloat(config.heatingOn);
|
|
18
|
+
node.diff = parseFloat(config.diff);
|
|
19
|
+
node.anticipator = parseFloat(config.anticipator);
|
|
20
|
+
node.ignoreAnticipatorCycles = Math.floor(config.ignoreAnticipatorCycles);
|
|
21
|
+
node.isHeating = config.isHeating === true;
|
|
22
|
+
node.algorithm = config.algorithm;
|
|
30
23
|
|
|
31
24
|
let above = false;
|
|
32
25
|
let below = false;
|
|
@@ -36,7 +29,7 @@ module.exports = function(RED) {
|
|
|
36
29
|
let cyclesSinceModeChange = 0;
|
|
37
30
|
let modeChanged = false;
|
|
38
31
|
|
|
39
|
-
node.on("input", function(msg, send, done) {
|
|
32
|
+
node.on("input", async function(msg, send, done) {
|
|
40
33
|
send = send || function() { node.send.apply(node, arguments); };
|
|
41
34
|
|
|
42
35
|
if (!msg) {
|
|
@@ -44,49 +37,130 @@ module.exports = function(RED) {
|
|
|
44
37
|
if (done) done();
|
|
45
38
|
return;
|
|
46
39
|
}
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
node.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
node.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node, msg ));
|
|
58
|
-
}
|
|
59
|
-
if (utils.requiresEvaluation(config.coolingOnType)) {
|
|
60
|
-
node.coolingOn = parseFloat(RED.util.evaluateNodeProperty( config.coolingOn, config.coolingOnType, node, msg ));
|
|
61
|
-
}
|
|
62
|
-
if (utils.requiresEvaluation(config.coolingOffType)) {
|
|
63
|
-
node.coolingOff = parseFloat(RED.util.evaluateNodeProperty( config.coolingOff, config.coolingOffType, node, msg ));
|
|
64
|
-
}
|
|
65
|
-
if (utils.requiresEvaluation(config.heatingOffType)) {
|
|
66
|
-
node.heatingOff = parseFloat(RED.util.evaluateNodeProperty( config.heatingOff, config.heatingOffType, node, msg ));
|
|
67
|
-
}
|
|
68
|
-
if (utils.requiresEvaluation(config.heatingOnType)) {
|
|
69
|
-
node.heatingOn = parseFloat(RED.util.evaluateNodeProperty( config.heatingOn, config.heatingOnType, node, msg ));
|
|
70
|
-
}
|
|
71
|
-
if (utils.requiresEvaluation(config.diffType)) {
|
|
72
|
-
node.diff = parseFloat(RED.util.evaluateNodeProperty( config.diff, config.diffType, node, msg ));
|
|
73
|
-
}
|
|
74
|
-
if (utils.requiresEvaluation(config.anticipatorType)) {
|
|
75
|
-
node.anticipator = parseFloat(RED.util.evaluateNodeProperty( config.anticipator, config.anticipatorType, node, msg ));
|
|
76
|
-
}
|
|
77
|
-
if (utils.requiresEvaluation(config.ignoreAnticipatorCyclesType)) {
|
|
78
|
-
node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node, msg ));
|
|
79
|
-
}
|
|
80
|
-
if (utils.requiresEvaluation(config.isHeatingType)) {
|
|
81
|
-
node.isHeating = RED.util.evaluateNodeProperty( config.isHeating, config.isHeatingType, node, msg ) === true;
|
|
82
|
-
}
|
|
83
|
-
if (utils.requiresEvaluation(config.algorithmType)) {
|
|
84
|
-
node.algorithm = RED.util.evaluateNodeProperty( config.algorithm, config.algorithmType, node, msg );
|
|
40
|
+
|
|
41
|
+
// Evaluate dynamic properties
|
|
42
|
+
try {
|
|
43
|
+
|
|
44
|
+
// Check busy lock
|
|
45
|
+
if (node.isBusy) {
|
|
46
|
+
// Update status to let user know they are pushing too fast
|
|
47
|
+
node.status({ fill: "yellow", shape: "ring", text: "busy - dropped msg" });
|
|
48
|
+
if (done) done();
|
|
49
|
+
return;
|
|
85
50
|
}
|
|
51
|
+
|
|
52
|
+
// Lock node during evaluation
|
|
53
|
+
node.isBusy = true;
|
|
54
|
+
|
|
55
|
+
// Begin evaluations
|
|
56
|
+
const evaluations = [];
|
|
57
|
+
|
|
58
|
+
//0
|
|
59
|
+
evaluations.push(
|
|
60
|
+
utils.requiresEvaluation(config.setpointType)
|
|
61
|
+
? utils.evaluateNodeProperty(config.setpoint, config.setpointType, node, msg)
|
|
62
|
+
.then(val => parseFloat(val))
|
|
63
|
+
: Promise.resolve(node.setpoint),
|
|
64
|
+
);
|
|
65
|
+
//1
|
|
66
|
+
evaluations.push(
|
|
67
|
+
utils.requiresEvaluation(config.heatingSetpointType)
|
|
68
|
+
? utils.evaluateNodeProperty(config.heatingSetpoint, config.heatingSetpointType, node, msg)
|
|
69
|
+
.then(val => parseFloat(val))
|
|
70
|
+
: Promise.resolve(node.heatingSetpoint),
|
|
71
|
+
);
|
|
72
|
+
//2
|
|
73
|
+
evaluations.push(
|
|
74
|
+
utils.requiresEvaluation(config.coolingSetpointType)
|
|
75
|
+
? utils.evaluateNodeProperty(config.coolingSetpoint, config.coolingSetpointType, node, msg)
|
|
76
|
+
.then(val => parseFloat(val))
|
|
77
|
+
: Promise.resolve(node.coolingSetpoint),
|
|
78
|
+
);
|
|
79
|
+
//3
|
|
80
|
+
evaluations.push(
|
|
81
|
+
utils.requiresEvaluation(config.coolingOnType)
|
|
82
|
+
? utils.evaluateNodeProperty(config.coolingOn, config.coolingOnType, node, msg)
|
|
83
|
+
.then(val => parseFloat(val))
|
|
84
|
+
: Promise.resolve(node.coolingOn),
|
|
85
|
+
);
|
|
86
|
+
//4
|
|
87
|
+
evaluations.push(
|
|
88
|
+
utils.requiresEvaluation(config.coolingOffType)
|
|
89
|
+
? utils.evaluateNodeProperty(config.coolingOff, config.coolingOffType, node, msg)
|
|
90
|
+
.then(val => parseFloat(val))
|
|
91
|
+
: Promise.resolve(node.coolingOff),
|
|
92
|
+
);
|
|
93
|
+
//5
|
|
94
|
+
evaluations.push(
|
|
95
|
+
utils.requiresEvaluation(config.heatingOffType)
|
|
96
|
+
? utils.evaluateNodeProperty(config.heatingOff, config.heatingOffType, node, msg)
|
|
97
|
+
.then(val => parseFloat(val))
|
|
98
|
+
: Promise.resolve(node.heatingOff),
|
|
99
|
+
);
|
|
100
|
+
//6
|
|
101
|
+
evaluations.push(
|
|
102
|
+
utils.requiresEvaluation(config.heatingOnType)
|
|
103
|
+
? utils.evaluateNodeProperty(config.heatingOn, config.heatingOnType, node, msg)
|
|
104
|
+
.then(val => parseFloat(val))
|
|
105
|
+
: Promise.resolve(node.heatingOn),
|
|
106
|
+
);
|
|
107
|
+
//7
|
|
108
|
+
evaluations.push(
|
|
109
|
+
utils.requiresEvaluation(config.diffType)
|
|
110
|
+
? utils.evaluateNodeProperty(config.diff, config.diffType, node, msg)
|
|
111
|
+
.then(val => parseFloat(val))
|
|
112
|
+
: Promise.resolve(node.diff),
|
|
113
|
+
);
|
|
114
|
+
//8
|
|
115
|
+
evaluations.push(
|
|
116
|
+
utils.requiresEvaluation(config.anticipatorType)
|
|
117
|
+
? utils.evaluateNodeProperty(config.anticipator, config.anticipatorType, node, msg)
|
|
118
|
+
.then(val => parseFloat(val))
|
|
119
|
+
: Promise.resolve(node.anticipator),
|
|
120
|
+
);
|
|
121
|
+
//9
|
|
122
|
+
evaluations.push(
|
|
123
|
+
utils.requiresEvaluation(config.ignoreAnticipatorCyclesType)
|
|
124
|
+
? utils.evaluateNodeProperty(config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node, msg)
|
|
125
|
+
.then(val => Math.floor(val))
|
|
126
|
+
: Promise.resolve(node.ignoreAnticipatorCycles),
|
|
127
|
+
);
|
|
128
|
+
//10
|
|
129
|
+
evaluations.push(
|
|
130
|
+
utils.requiresEvaluation(config.isHeatingType)
|
|
131
|
+
? utils.evaluateNodeProperty(config.isHeating, config.isHeatingType, node, msg)
|
|
132
|
+
.then(val => val === true)
|
|
133
|
+
: Promise.resolve(node.isHeating),
|
|
134
|
+
);
|
|
135
|
+
//11
|
|
136
|
+
evaluations.push(
|
|
137
|
+
utils.requiresEvaluation(config.algorithmType)
|
|
138
|
+
? utils.evaluateNodeProperty(config.algorithm, config.algorithmType, node, msg)
|
|
139
|
+
: Promise.resolve(node.algorithm),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const results = await Promise.all(evaluations);
|
|
143
|
+
|
|
144
|
+
// Update runtime with evaluated values
|
|
145
|
+
if (!isNaN(results[0])) node.setpoint = results[0];
|
|
146
|
+
if (!isNaN(results[1])) node.heatingSetpoint = results[1];
|
|
147
|
+
if (!isNaN(results[2])) node.coolingSetpoint = results[2];
|
|
148
|
+
if (!isNaN(results[3])) node.coolingOn = results[3];
|
|
149
|
+
if (!isNaN(results[4])) node.coolingOff = results[4];
|
|
150
|
+
if (!isNaN(results[5])) node.heatingOff = results[5];
|
|
151
|
+
if (!isNaN(results[6])) node.heatingOn = results[6];
|
|
152
|
+
if (!isNaN(results[7])) node.diff = results[7];
|
|
153
|
+
if (!isNaN(results[8])) node.anticipator = results[8];
|
|
154
|
+
if (!isNaN(results[9])) node.ignoreAnticipatorCycles = results[9];
|
|
155
|
+
if (results[10] !== null) node.isHeating = results[10];
|
|
156
|
+
if (results[11]) node.algorithm = results[11];
|
|
86
157
|
} catch (err) {
|
|
87
158
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
88
159
|
if (done) done();
|
|
89
160
|
return;
|
|
161
|
+
} finally {
|
|
162
|
+
// Release, all synchronous from here on
|
|
163
|
+
node.isBusy = false;
|
|
90
164
|
}
|
|
91
165
|
|
|
92
166
|
// Handle configuration messages
|
|
@@ -320,7 +394,7 @@ module.exports = function(RED) {
|
|
|
320
394
|
}
|
|
321
395
|
above = false;
|
|
322
396
|
} else {
|
|
323
|
-
if (input > coolingOn) {
|
|
397
|
+
if (input > node.coolingOn) {
|
|
324
398
|
above = true;
|
|
325
399
|
} else if (above && input < node.coolingOff + effectiveAnticipator) {
|
|
326
400
|
above = false;
|
package/nodes/utils.js
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
2
|
function requiresEvaluation(type) { return type === "flow" || type === "global" || type === "msg"; }
|
|
3
|
-
|
|
4
3
|
|
|
4
|
+
// Safe evaluation helper (promisified)
|
|
5
|
+
function evaluateNodeProperty(value, type, node, msg) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
if (!this.requiresEvaluation(type)) {
|
|
8
|
+
resolve(value); // Return raw value for static types
|
|
9
|
+
} else {
|
|
10
|
+
RED.util.evaluateNodeProperty(
|
|
11
|
+
value, type, node, msg,
|
|
12
|
+
(err, result) => err ? reject(err) : resolve(result)
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Usage:
|
|
5
19
|
// const utils = require('./utils')(RED);
|
|
6
20
|
|
|
7
21
|
return {
|
|
8
|
-
requiresEvaluation
|
|
22
|
+
requiresEvaluation,
|
|
23
|
+
evaluateNodeProperty
|
|
9
24
|
};
|
|
10
25
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bldgblocks/node-red-contrib-control",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Sedona-inspired control nodes for Node-RED",
|
|
5
5
|
"keywords": [ "node-red", "sedona", "control", "hvac" ],
|
|
6
6
|
"files": ["nodes/*.js", "nodes/*.html"],
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
69
|
"author": "buildingblocks",
|
|
70
|
-
"license": "
|
|
70
|
+
"license": "Apache-2.0",
|
|
71
71
|
"repository": {
|
|
72
72
|
"type": "git",
|
|
73
73
|
"url": "git+https://github.com/BldgBlocks/node-red-contrib-control.git"
|