@bldgblocks/node-red-contrib-control 0.1.25 → 0.1.27
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/analog-switch-block.html +8 -9
- package/nodes/analog-switch-block.js +0 -23
- package/nodes/average-block.html +1 -1
- package/nodes/average-block.js +8 -8
- package/nodes/changeover-block.js +35 -22
- package/nodes/delay-block.js +8 -8
- package/nodes/enum-switch-block.html +157 -0
- package/nodes/enum-switch-block.js +101 -0
- package/nodes/hysteresis-block.html +1 -1
- package/nodes/hysteresis-block.js +16 -12
- package/nodes/max-block.js +2 -4
- package/nodes/memory-block.js +3 -6
- package/nodes/min-block.js +2 -4
- package/nodes/minmax-block.js +9 -9
- package/nodes/on-change-block.html +1 -1
- package/nodes/on-change-block.js +27 -54
- package/nodes/pid-block.js +1 -1
- package/nodes/tstat-block.js +40 -27
- package/nodes/utils.js +1 -22
- package/package.json +2 -1
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<input type="text" id="node-input-name" placeholder="Name">
|
|
6
6
|
</div>
|
|
7
7
|
<div class="form-row">
|
|
8
|
-
<label for="node-input-slots" title="Number of input slots (integer >=
|
|
9
|
-
<input type="number" id="node-input-slots" placeholder="2" min="
|
|
8
|
+
<label for="node-input-slots" title="Number of input slots (integer >= 2)"><i class="fa fa-th-list"></i> Slots</label>
|
|
9
|
+
<input type="number" id="node-input-slots" placeholder="2" min="2" step="1">
|
|
10
10
|
</div>
|
|
11
11
|
</script>
|
|
12
12
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
color: "#301934",
|
|
18
18
|
defaults: {
|
|
19
19
|
name: { value: "" },
|
|
20
|
-
slots: { value: 2, required: true, validate: function(v) { return Number.isInteger(Number(v)) && Number(v) >=
|
|
20
|
+
slots: { value: 2, required: true, validate: function(v) { return Number.isInteger(Number(v)) && Number(v) >= 2; } }
|
|
21
21
|
},
|
|
22
22
|
inputs: 1,
|
|
23
23
|
outputs: 1,
|
|
@@ -36,19 +36,18 @@
|
|
|
36
36
|
Selects one numeric input from multiple slots based on a switch value.
|
|
37
37
|
|
|
38
38
|
### Inputs
|
|
39
|
-
: context (string) :
|
|
40
|
-
: payload (
|
|
39
|
+
: context (string) : Switch (`"switch"`), or input slot (`"inX"`, e.g., `in1`). Unmatched values trigger error.
|
|
40
|
+
: payload (integer) : Switch configuration.
|
|
41
41
|
|
|
42
42
|
### Outputs
|
|
43
43
|
: payload (number) : Selected input value based on switch setting.
|
|
44
44
|
|
|
45
45
|
### Properties
|
|
46
|
-
: slots (integer) : Number of input slots (≥
|
|
46
|
+
: slots (integer) : Number of input slots (≥ 2).
|
|
47
47
|
|
|
48
48
|
### Details
|
|
49
|
-
Selects one numeric input from multiple slots (`in1`, `in2`, etc.) based on a switch value
|
|
50
|
-
set via `msg.context = "switch"` with integer `msg.payload`.
|
|
51
|
-
integer `msg.payload` (≥ 1). Inputs default to 0, updated via `msg.context = "inX"` with numeric `msg.payload`.
|
|
49
|
+
Selects one numeric input from multiple slots (`in1`, `in2`, etc.) based on a switch value
|
|
50
|
+
set via `msg.context = "switch"` with integer `msg.payload`. Inputs default to 0, updated via `msg.context = "inX"` with numeric `msg.payload`.
|
|
52
51
|
|
|
53
52
|
Outputs a new `msg.payload` when the active slot is updated, switch changes to a valid slot, or slots change affects the output.
|
|
54
53
|
|
|
@@ -39,29 +39,6 @@ module.exports = function(RED) {
|
|
|
39
39
|
const prevSwitch = node.runtime.switch;
|
|
40
40
|
|
|
41
41
|
switch (msg.context) {
|
|
42
|
-
case "slots":
|
|
43
|
-
const slotValue = parseInt(msg.payload, 10);
|
|
44
|
-
if (isNaN(slotValue) || slotValue < 1) {
|
|
45
|
-
node.status({ fill: "red", shape: "ring", text: "invalid slots" });
|
|
46
|
-
if (done) done();
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
node.runtime.slots = slotValue;
|
|
50
|
-
const newInputs = Array(node.runtime.slots).fill(0);
|
|
51
|
-
for (let i = 0; i < Math.min(node.runtime.inputs.length, node.runtime.slots); i++) {
|
|
52
|
-
newInputs[i] = node.runtime.inputs[i];
|
|
53
|
-
}
|
|
54
|
-
node.runtime.inputs = newInputs;
|
|
55
|
-
if (node.runtime.switch > node.runtime.slots) {
|
|
56
|
-
node.runtime.switch = 1;
|
|
57
|
-
shouldOutput = true;
|
|
58
|
-
}
|
|
59
|
-
node.status({
|
|
60
|
-
fill: "green",
|
|
61
|
-
shape: "dot",
|
|
62
|
-
text: `slots: ${node.runtime.slots}`
|
|
63
|
-
});
|
|
64
|
-
break;
|
|
65
42
|
case "switch":
|
|
66
43
|
const switchValue = parseInt(msg.payload, 10);
|
|
67
44
|
if (isNaN(switchValue) || switchValue < 1 || switchValue > node.runtime.slots) {
|
package/nodes/average-block.html
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
typeField: "#node-input-maxValidType"
|
|
62
62
|
}).typedInput("type", node.maxValidType || "num").typedInput("value", node.maxValid || "150");
|
|
63
63
|
} catch (err) {
|
|
64
|
-
console.error("Error in
|
|
64
|
+
console.error("Error in oneditprepare:", err);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
});
|
package/nodes/average-block.js
CHANGED
|
@@ -12,13 +12,10 @@ module.exports = function(RED) {
|
|
|
12
12
|
lastAvg: null
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const typedProperties = ['minValid', 'maxValid'];
|
|
16
|
-
|
|
17
15
|
// Evaluate typed-input properties
|
|
18
16
|
try {
|
|
19
|
-
|
|
20
|
-
node.runtime.
|
|
21
|
-
node.runtime.maxValid = parseFloat(evaluatedValues.maxValid);
|
|
17
|
+
node.runtime.minValid = parseFloat(RED.util.evaluateNodeProperty( config.minValid, config.minValidType, node ));
|
|
18
|
+
node.runtime.maxValid = parseFloat(RED.util.evaluateNodeProperty( config.maxValid, config.maxValidType, node ));
|
|
22
19
|
} catch (err) {
|
|
23
20
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
24
21
|
}
|
|
@@ -35,9 +32,12 @@ module.exports = function(RED) {
|
|
|
35
32
|
|
|
36
33
|
// Update typed-input properties if needed
|
|
37
34
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
if (utils.requiresEvaluation(config.minValidType)) {
|
|
36
|
+
node.runtime.minValid = parseFloat(RED.util.evaluateNodeProperty( config.minValid, config.minValidType, node, msg ));
|
|
37
|
+
}
|
|
38
|
+
if (utils.requiresEvaluation(config.maxValidType)) {
|
|
39
|
+
node.runtime.maxValid = parseFloat(RED.util.evaluateNodeProperty( config.maxValid, config.maxValidType, node, msg ));
|
|
40
|
+
}
|
|
41
41
|
} catch (err) {
|
|
42
42
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
43
43
|
if (done) done();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
//const { parse } = require('echarts/types/src/export/api/time.js');
|
|
2
|
+
|
|
1
3
|
module.exports = function(RED) {
|
|
2
4
|
const utils = require('./utils')(RED);
|
|
3
5
|
|
|
@@ -16,20 +18,16 @@ module.exports = function(RED) {
|
|
|
16
18
|
lastModeChange: 0
|
|
17
19
|
};
|
|
18
20
|
|
|
19
|
-
const typedProperties = ['setpoint', 'heatingSetpoint', 'coolingSetpoint', 'swapTime', 'deadband',
|
|
20
|
-
'extent', 'minTempSetpoint', 'maxTempSetpoint'];
|
|
21
|
-
|
|
22
21
|
// Evaluate typed-input properties
|
|
23
|
-
try {
|
|
24
|
-
|
|
25
|
-
node.runtime.
|
|
26
|
-
node.runtime.
|
|
27
|
-
node.runtime.
|
|
28
|
-
node.runtime.
|
|
29
|
-
node.runtime.
|
|
30
|
-
node.runtime.
|
|
31
|
-
node.runtime.
|
|
32
|
-
node.runtime.maxTempSetpoint = parseFloat(evaluatedValues.maxTempSetpoint);
|
|
22
|
+
try {
|
|
23
|
+
node.runtime.setpoint = parseFloat(RED.util.evaluateNodeProperty( config.setpoint, config.setpointType, node ));
|
|
24
|
+
node.runtime.heatingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.heatingSetpoint, config.heatingSetpointType, node ));
|
|
25
|
+
node.runtime.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node ));
|
|
26
|
+
node.runtime.swapTime = parseFloat(RED.util.evaluateNodeProperty( config.swapTime, config.swapTimeType, node ));
|
|
27
|
+
node.runtime.deadband = parseFloat(RED.util.evaluateNodeProperty( config.deadband, config.deadbandType, node ));
|
|
28
|
+
node.runtime.extent = parseFloat(RED.util.evaluateNodeProperty( config.extent, config.extentType, node ));
|
|
29
|
+
node.runtime.minTempSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.minTempSetpoint, config.minTempSetpointType, node ));
|
|
30
|
+
node.runtime.maxTempSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.maxTempSetpoint, config.maxTempSetpointType, node ));
|
|
33
31
|
} catch (err) {
|
|
34
32
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
35
33
|
if (done) done();
|
|
@@ -53,15 +51,30 @@ module.exports = function(RED) {
|
|
|
53
51
|
|
|
54
52
|
// Update typed-input properties if needed
|
|
55
53
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
if (utils.requiresEvaluation(config.setpointType)) {
|
|
55
|
+
node.runtime.setpoint = parseFloat(RED.util.evaluateNodeProperty( config.setpoint, config.setpointType, node, msg ));
|
|
56
|
+
}
|
|
57
|
+
if (utils.requiresEvaluation(config.heatingSetpointType)) {
|
|
58
|
+
node.runtime.heatingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.heatingSetpoint, config.heatingSetpointType, node, msg ));
|
|
59
|
+
}
|
|
60
|
+
if (utils.requiresEvaluation(config.coolingSetpointType)) {
|
|
61
|
+
node.runtime.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node, msg ));
|
|
62
|
+
}
|
|
63
|
+
if (utils.requiresEvaluation(config.swapTimeType)) {
|
|
64
|
+
node.runtime.swapTime = parseFloat(RED.util.evaluateNodeProperty( config.swapTime, config.swapTimeType, node, msg ));
|
|
65
|
+
}
|
|
66
|
+
if (utils.requiresEvaluation(config.deadbandType)) {
|
|
67
|
+
node.runtime.deadband = parseFloat(RED.util.evaluateNodeProperty( config.deadband, config.deadbandType, node, msg ));
|
|
68
|
+
}
|
|
69
|
+
if (utils.requiresEvaluation(config.extentType)) {
|
|
70
|
+
node.runtime.extent = parseFloat(RED.util.evaluateNodeProperty( config.extent, config.extentType, node, msg ));
|
|
71
|
+
}
|
|
72
|
+
if (utils.requiresEvaluation(config.minTempSetpointType)) {
|
|
73
|
+
node.runtime.minTempSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.minTempSetpoint, config.minTempSetpointType, node, msg ));
|
|
74
|
+
}
|
|
75
|
+
if (utils.requiresEvaluation(config.maxTempSetpointType)) {
|
|
76
|
+
node.runtime.maxTempSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.maxTempSetpoint, config.maxTempSetpointType, node, msg ));
|
|
77
|
+
}
|
|
65
78
|
} catch (err) {
|
|
66
79
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
67
80
|
if (done) done();
|
package/nodes/delay-block.js
CHANGED
|
@@ -11,13 +11,10 @@ module.exports = function(RED) {
|
|
|
11
11
|
desired: false
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const typedProperties = ['delayOn', 'delayOff'];
|
|
15
|
-
|
|
16
14
|
// Evaluate typed-input properties
|
|
17
15
|
try {
|
|
18
|
-
|
|
19
|
-
node.runtime.
|
|
20
|
-
node.runtime.delayOff = (parseFloat(evaluatedValues.delayOff)) * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1);
|
|
16
|
+
node.runtime.delayOn = parseFloat(RED.util.evaluateNodeProperty( config.delayOn, config.delayOnType, node )) * (config.delayOnUnits === "seconds" ? 1000 : config.delayOnUnits === "minutes" ? 60000 : 1);
|
|
17
|
+
node.runtime.delayOff = parseFloat(RED.util.evaluateNodeProperty( config.delayOff, config.delayOffType, node )) * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1);
|
|
21
18
|
} catch (err) {
|
|
22
19
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
23
20
|
}
|
|
@@ -33,9 +30,12 @@ module.exports = function(RED) {
|
|
|
33
30
|
|
|
34
31
|
// Update typed-input properties if needed
|
|
35
32
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
if (utils.requiresEvaluation(config.delayOnType)) {
|
|
34
|
+
node.runtime.delayOn = parseFloat(RED.util.evaluateNodeProperty( config.delayOn, config.delayOnType, node, msg )) * (config.delayOnUnits === "seconds" ? 1000 : config.delayOnUnits === "minutes" ? 60000 : 1);
|
|
35
|
+
}
|
|
36
|
+
if (utils.requiresEvaluation(config.delayOffType)) {
|
|
37
|
+
node.runtime.delayOff = parseFloat(RED.util.evaluateNodeProperty( config.delayOff, config.delayOffType, node, msg )) * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1);
|
|
38
|
+
}
|
|
39
39
|
} catch (err) {
|
|
40
40
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
41
41
|
if (done) done();
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="enum-switch-block">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
5
|
+
</div>
|
|
6
|
+
<div class="form-row">
|
|
7
|
+
<label for="node-input-property" title="Property to evaluate"><i class="fa fa-code"></i> Property</label>
|
|
8
|
+
<input type="text" id="node-input-property" placeholder="property">
|
|
9
|
+
<input type="hidden" id="node-input-propertyType">
|
|
10
|
+
<input type="hidden" id="node-input-outputs">
|
|
11
|
+
</div>
|
|
12
|
+
<div class="form-row">
|
|
13
|
+
<label><i class="fa fa-cogs"></i> Rules</label>
|
|
14
|
+
<div id="node-input-rules-container" style="border: 1px solid #999; padding: 5px; margin: 5px 0; max-height: 200px; overflow-y: auto;">
|
|
15
|
+
<input type="hidden" id="node-input-rules">
|
|
16
|
+
<div class="rule-template" style="display: none;">
|
|
17
|
+
<div class="form-row rule-item" style="margin-bottom: 5px; padding: 5px; border-bottom: 1px solid #eee; display: flex; align-items: center;">
|
|
18
|
+
<input type="text" class="node-input-rule-value" placeholder="value to match" style="flex: 1; margin-right: 5px;">
|
|
19
|
+
<button class="red-ui-button delete-rule" style="width: auto; padding: 0 8px;">
|
|
20
|
+
<i class="fa fa-times"></i>
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div id="node-input-rules-list"></div>
|
|
25
|
+
<button id="node-input-add-rule" class="red-ui-button" style="width: 100%; margin-top: 5px;">
|
|
26
|
+
<i class="fa fa-plus"></i> Add Rule
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<script type="text/javascript">
|
|
33
|
+
RED.nodes.registerType("enum-switch-block", {
|
|
34
|
+
category: "control",
|
|
35
|
+
color: "#301934",
|
|
36
|
+
defaults: {
|
|
37
|
+
name: { value: "" },
|
|
38
|
+
property: { value: "payload" },
|
|
39
|
+
propertyType: { value: "msg" },
|
|
40
|
+
rules: { value: "[]" },
|
|
41
|
+
outputs: { value: 1 }
|
|
42
|
+
},
|
|
43
|
+
inputs: 1,
|
|
44
|
+
outputs: 1,
|
|
45
|
+
inputLabels: ["input"],
|
|
46
|
+
outputLabels: function(index) {
|
|
47
|
+
try {
|
|
48
|
+
const rules = JSON.parse(this.rules || "[]");
|
|
49
|
+
if (rules[index]) {
|
|
50
|
+
return rules[index].value || `rule ${index + 1}`;
|
|
51
|
+
}
|
|
52
|
+
return "";
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
icon: "font-awesome/fa-exchange",
|
|
58
|
+
paletteLabel: "enum switch",
|
|
59
|
+
label: function() {
|
|
60
|
+
const rules = JSON.parse(this.rules || "[]");
|
|
61
|
+
return this.name ? this.name : `enum switch (${rules.length} rules)`;
|
|
62
|
+
},
|
|
63
|
+
oneditprepare: function() {
|
|
64
|
+
const node = this;
|
|
65
|
+
const rulesContainer = $("#node-input-rules-list");
|
|
66
|
+
const template = $(".rule-template").clone().removeClass("rule-template").show();
|
|
67
|
+
|
|
68
|
+
// Initialize typed inputs
|
|
69
|
+
$("#node-input-property").typedInput({
|
|
70
|
+
default: "msg",
|
|
71
|
+
types: ["msg", "flow", "global"],
|
|
72
|
+
typeField: "#node-input-propertyType"
|
|
73
|
+
}).typedInput("type", node.propertyType || "msg").typedInput("value", node.property);
|
|
74
|
+
|
|
75
|
+
// Parse existing rules
|
|
76
|
+
let rules = [];
|
|
77
|
+
try {
|
|
78
|
+
rules = JSON.parse(node.rules || "[]");
|
|
79
|
+
} catch (e) {
|
|
80
|
+
rules = [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Render existing rules
|
|
84
|
+
rules.forEach(function(rule, index) {
|
|
85
|
+
const ruleItem = template.clone();
|
|
86
|
+
ruleItem.find(".node-input-rule-value").val(rule.value || "");
|
|
87
|
+
rulesContainer.append(ruleItem);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Add new rule button click
|
|
91
|
+
$("#node-input-add-rule").on("click", function() {
|
|
92
|
+
const ruleItem = template.clone();
|
|
93
|
+
rulesContainer.append(ruleItem);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Delete rule button click
|
|
97
|
+
rulesContainer.on("click", ".delete-rule", function() {
|
|
98
|
+
$(this).closest(".rule-item").remove();
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
oneditsave: function() {
|
|
103
|
+
const rules = [];
|
|
104
|
+
$("#node-input-rules-list .rule-item").each(function() {
|
|
105
|
+
const value = $(this).find(".node-input-rule-value").val().trim();
|
|
106
|
+
if (value) {
|
|
107
|
+
rules.push({ value: value });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
$("#node-input-rules").val(JSON.stringify(rules));
|
|
112
|
+
$("#node-input-outputs").val(rules.length);
|
|
113
|
+
},
|
|
114
|
+
oneditresize: function(size) {
|
|
115
|
+
$("#node-input-rules-container").height(size.height - 200);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
<!-- Help Section -->
|
|
122
|
+
<script type="text/markdown" data-help-name="enum-switch-block">
|
|
123
|
+
Route input to the appropriate output based on switch values.
|
|
124
|
+
|
|
125
|
+
### Inputs
|
|
126
|
+
: payload (string) : `string` for comparison.
|
|
127
|
+
|
|
128
|
+
### Outputs
|
|
129
|
+
: payload (boolean) : Boolean output based on matching switch value.
|
|
130
|
+
|
|
131
|
+
### Properties
|
|
132
|
+
: slots (integer) : Number of input slots (≥ 2).
|
|
133
|
+
|
|
134
|
+
### Details
|
|
135
|
+
Similar to the NodeRED switch node, but single purpose, with the important difference:
|
|
136
|
+
1. Outputting boolean based on matching string input.
|
|
137
|
+
2. Updating all outputs `true`/`false` on each input message. (updating false being the key difference)
|
|
138
|
+
|
|
139
|
+
NodeRED `switch` node only outputs on matching cases, and does not update non-matching outputs unless you create opposing rules for each case,
|
|
140
|
+
which can be messy. Use case is to drive multiple boolean outputs based on a single string input, e.g., controlling visibility of multiple UI elements,
|
|
141
|
+
or routing logic based on a given mode.
|
|
142
|
+
|
|
143
|
+
Supports string, number, and boolean comparisons. Input type is inferred from the incoming message payload type.
|
|
144
|
+
|
|
145
|
+
Preserves original message structure, only modifying the payload to `true` or `false`.
|
|
146
|
+
|
|
147
|
+
### Status
|
|
148
|
+
- Green (dot): Configuration update
|
|
149
|
+
- Blue (dot): State changed
|
|
150
|
+
- Blue (ring): State unchanged
|
|
151
|
+
- Red (ring): Error
|
|
152
|
+
- Yellow (ring): Warning
|
|
153
|
+
|
|
154
|
+
### References
|
|
155
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
156
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
157
|
+
</script>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
4
|
+
function EnumSwitchBlockNode(config) {
|
|
5
|
+
RED.nodes.createNode(this, config);
|
|
6
|
+
const node = this;
|
|
7
|
+
|
|
8
|
+
// Parse rules from config
|
|
9
|
+
let rules = [];
|
|
10
|
+
try {
|
|
11
|
+
rules = JSON.parse(config.rules || "[]");
|
|
12
|
+
} catch (e) {
|
|
13
|
+
node.error("Invalid rules configuration");
|
|
14
|
+
rules = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
node.on("input", function(msg, send, done) {
|
|
18
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
19
|
+
|
|
20
|
+
// Guard against invalid msg
|
|
21
|
+
if (!msg) {
|
|
22
|
+
node.status({ fill: "red", shape: "ring", text: "invalid message" });
|
|
23
|
+
if (done) done();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let matchAgainst;
|
|
28
|
+
|
|
29
|
+
// Evaluate typed-input properties
|
|
30
|
+
try {
|
|
31
|
+
matchAgainst = RED.util.evaluateNodeProperty( config.property, config.propertyType, node, msg );
|
|
32
|
+
|
|
33
|
+
if (matchAgainst === undefined) {
|
|
34
|
+
node.status({ fill: "red", shape: "ring", text: "property evaluation failed" });
|
|
35
|
+
if (done) done();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
} catch (err) {
|
|
39
|
+
node.status({ fill: "red", shape: "ring", text: `Error: ${err.message}` });
|
|
40
|
+
if (done) done(err);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const outputs = [];
|
|
45
|
+
let matched = false;
|
|
46
|
+
|
|
47
|
+
// Evaluate each rule and set outputs
|
|
48
|
+
for (let i = 0; i < rules.length; i++) {
|
|
49
|
+
const rule = rules[i];
|
|
50
|
+
let match = false;
|
|
51
|
+
|
|
52
|
+
// Handle different types for comparison
|
|
53
|
+
if (matchAgainst === null || matchAgainst === undefined) {
|
|
54
|
+
match = (rule.value === null || rule.value === undefined || rule.value === "");
|
|
55
|
+
} else if (typeof matchAgainst === 'string' && typeof rule.value === 'string') {
|
|
56
|
+
match = matchAgainst === rule.value;
|
|
57
|
+
} else if (typeof matchAgainst === 'number') {
|
|
58
|
+
const numericRuleValue = parseFloat(rule.value);
|
|
59
|
+
match = !isNaN(numericRuleValue) && matchAgainst === numericRuleValue;
|
|
60
|
+
} else if (typeof matchAgainst === 'boolean') {
|
|
61
|
+
const boolRuleValue = rule.value.toLowerCase() === 'true';
|
|
62
|
+
match = matchAgainst === boolRuleValue;
|
|
63
|
+
} else {
|
|
64
|
+
match = String(matchAgainst) === String(rule.value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
outputs[i] = match;
|
|
68
|
+
|
|
69
|
+
if (match) {
|
|
70
|
+
matched = true;
|
|
71
|
+
node.status({ fill: "blue", shape: "dot", text: `Matched: ${rule.value}` });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Send output messages (all outputs as booleans)
|
|
76
|
+
const messages = outputs.map(isMatch => {
|
|
77
|
+
return {
|
|
78
|
+
...msg,
|
|
79
|
+
payload: isMatch,
|
|
80
|
+
topic: msg.topic
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
send(messages);
|
|
85
|
+
|
|
86
|
+
if (!matched && rules.length > 0) {
|
|
87
|
+
node.status({ fill: "blue", shape: "ring", text: "No match" });
|
|
88
|
+
} else if (rules.length === 0) {
|
|
89
|
+
node.status({ fill: "yellow", shape: "ring", text: "No rules configured" });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (done) done();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
node.on("close", function(done) {
|
|
96
|
+
if (done) done();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
RED.nodes.registerType("enum-switch-block", EnumSwitchBlockNode);
|
|
101
|
+
};
|
|
@@ -94,7 +94,7 @@ Hysteresis controller with separate turn-on limits and turn-off differentials.
|
|
|
94
94
|
|
|
95
95
|
### Inputs
|
|
96
96
|
: payload (number) : Input value to evaluate
|
|
97
|
-
: context (string) : Configure `
|
|
97
|
+
: context (string) : Configure `upperLimitThreshold`, `lowerLimitThreshold`
|
|
98
98
|
|
|
99
99
|
### Outputs
|
|
100
100
|
: above (boolean) : Input > upperLimit
|
|
@@ -7,15 +7,12 @@ module.exports = function(RED) {
|
|
|
7
7
|
node.name = config.name;
|
|
8
8
|
node.state = "within";
|
|
9
9
|
|
|
10
|
-
const typedProperties = ['upperLimit', 'lowerLimit', 'upperLimitThreshold', 'lowerLimitThreshold'];
|
|
11
|
-
|
|
12
10
|
// Evaluate typed-input properties
|
|
13
11
|
try {
|
|
14
|
-
|
|
15
|
-
node.
|
|
16
|
-
node.
|
|
17
|
-
node.
|
|
18
|
-
node.lowerLimitThreshold = parseFloat(evaluatedValues.lowerLimitThreshold);
|
|
12
|
+
node.upperLimit = parseFloat(RED.util.evaluateNodeProperty( config.upperLimit, config.upperLimitType, node ));
|
|
13
|
+
node.lowerLimit = parseFloat(RED.util.evaluateNodeProperty( config.lowerLimit, config.lowerLimitType, node ));
|
|
14
|
+
node.upperLimitThreshold = parseFloat(RED.util.evaluateNodeProperty( config.upperLimitThreshold, config.upperLimitThresholdType, node ));
|
|
15
|
+
node.lowerLimitThreshold = parseFloat(RED.util.evaluateNodeProperty( config.lowerLimitThreshold, config.lowerLimitThresholdType, node ));
|
|
19
16
|
} catch (err) {
|
|
20
17
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
21
18
|
}
|
|
@@ -31,11 +28,18 @@ module.exports = function(RED) {
|
|
|
31
28
|
|
|
32
29
|
// Update typed-input properties if needed
|
|
33
30
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
if (utils.requiresEvaluation(config.upperLimitType)) {
|
|
32
|
+
node.upperLimit = parseFloat(RED.util.evaluateNodeProperty( config.upperLimit, config.upperLimitType, node, msg ));
|
|
33
|
+
}
|
|
34
|
+
if (utils.requiresEvaluation(config.lowerLimitType)) {
|
|
35
|
+
node.lowerLimit = parseFloat(RED.util.evaluateNodeProperty( config.lowerLimit, config.lowerLimitType, node, msg ));
|
|
36
|
+
}
|
|
37
|
+
if (utils.requiresEvaluation(config.upperLimitThresholdType)) {
|
|
38
|
+
node.upperLimitThreshold = parseFloat(RED.util.evaluateNodeProperty( config.upperLimitThreshold, config.upperLimitThresholdType, node, msg ));
|
|
39
|
+
}
|
|
40
|
+
if (utils.requiresEvaluation(config.lowerLimitThresholdType)) {
|
|
41
|
+
node.lowerLimitThreshold = parseFloat(RED.util.evaluateNodeProperty( config.lowerLimitThreshold, config.lowerLimitThresholdType, node, msg ));
|
|
42
|
+
}
|
|
39
43
|
} catch (err) {
|
|
40
44
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
41
45
|
if (done) done();
|
package/nodes/max-block.js
CHANGED
|
@@ -12,8 +12,7 @@ module.exports = function(RED) {
|
|
|
12
12
|
|
|
13
13
|
// Evaluate typed-inputs
|
|
14
14
|
try {
|
|
15
|
-
node.runtime.max = RED.util.evaluateNodeProperty( config.max, config.maxType, node );
|
|
16
|
-
node.runtime.max = parseFloat(node.runtime.max);
|
|
15
|
+
node.runtime.max = parseFloat( RED.util.evaluateNodeProperty( config.max, config.maxType, node ));
|
|
17
16
|
} catch(err) {
|
|
18
17
|
node.status({ fill: "red", shape: "ring", text: "error evaluating properties" });
|
|
19
18
|
}
|
|
@@ -34,8 +33,7 @@ module.exports = function(RED) {
|
|
|
34
33
|
// Evaluate typed-inputs if needed
|
|
35
34
|
try {
|
|
36
35
|
if (utils.requiresEvaluation(config.maxType)) {
|
|
37
|
-
node.runtime.max = RED.util.evaluateNodeProperty( config.max, config.maxType, node, msg );
|
|
38
|
-
node.runtime.max = parseFloat(node.runtime.max);
|
|
36
|
+
node.runtime.max = parseFloat( RED.util.evaluateNodeProperty( config.max, config.maxType, node, msg ));
|
|
39
37
|
}
|
|
40
38
|
} catch(err) {
|
|
41
39
|
node.status({ fill: "red", shape: "ring", text: "error evaluating properties" });
|
package/nodes/memory-block.js
CHANGED
|
@@ -13,15 +13,13 @@ module.exports = function(RED) {
|
|
|
13
13
|
node.runtime = {
|
|
14
14
|
name: config.name,
|
|
15
15
|
writePeriod: config.writePeriod,
|
|
16
|
-
writePeriodType: config.writePeriodType,
|
|
17
16
|
transferProperty: config.transferProperty,
|
|
18
17
|
writeOnUpdate: config.writeOnUpdate === true,
|
|
19
18
|
storedMsg: null
|
|
20
19
|
};
|
|
21
20
|
|
|
22
21
|
// Resolve typed inputs
|
|
23
|
-
node.runtime.writePeriod = RED.util.evaluateNodeProperty( config.writePeriod, config.writePeriodType, node );
|
|
24
|
-
node.runtime.writePeriod = parseFloat(node.runtime.writePeriod);
|
|
22
|
+
node.runtime.writePeriod = parseFloat(RED.util.evaluateNodeProperty( config.writePeriod, config.writePeriodType, node ));
|
|
25
23
|
|
|
26
24
|
// File path for persistent storage
|
|
27
25
|
const filePath = path.join(RED.settings.userDir, `memory-${node.id}.json`);
|
|
@@ -88,9 +86,8 @@ module.exports = function(RED) {
|
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
// Evaluate typed-inputs if needed
|
|
91
|
-
if (utils.requiresEvaluation(
|
|
92
|
-
node.runtime.writePeriod = RED.util.evaluateNodeProperty( config.writePeriod, config.writePeriodType, node, msg );
|
|
93
|
-
node.runtime.writePeriod = parseFloat(node.runtime.writePeriod);
|
|
89
|
+
if (utils.requiresEvaluation(config.writePeriodType)) {
|
|
90
|
+
node.runtime.writePeriod = parseFloat(RED.util.evaluateNodeProperty( config.writePeriod, config.writePeriodType, node, msg ));
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
// Initialize output array: [Output 1, Output 2]
|
package/nodes/min-block.js
CHANGED
|
@@ -12,8 +12,7 @@ module.exports = function(RED) {
|
|
|
12
12
|
|
|
13
13
|
// Evaluate typed-inputs
|
|
14
14
|
try {
|
|
15
|
-
node.runtime.min = RED.util.evaluateNodeProperty( config.min, config.minType, node );
|
|
16
|
-
node.runtime.min = parseFloat(node.runtime.min);
|
|
15
|
+
node.runtime.min = parseFloat(RED.util.evaluateNodeProperty( config.min, config.minType, node ));
|
|
17
16
|
} catch(err) {
|
|
18
17
|
node.status({ fill: "red", shape: "ring", text: "error evaluating properties" });
|
|
19
18
|
}
|
|
@@ -34,8 +33,7 @@ module.exports = function(RED) {
|
|
|
34
33
|
// Evaluate typed-inputs if needed
|
|
35
34
|
try {
|
|
36
35
|
if (utils.requiresEvaluation(config.minType)) {
|
|
37
|
-
node.runtime.min = RED.util.evaluateNodeProperty( config.min, config.minType, node, msg );
|
|
38
|
-
node.runtime.min = parseFloat(node.runtime.min);
|
|
36
|
+
node.runtime.min = parseFloat(RED.util.evaluateNodeProperty( config.min, config.minType, node, msg ));
|
|
39
37
|
}
|
|
40
38
|
} catch(err) {
|
|
41
39
|
node.status({ fill: "red", shape: "ring", text: "error evaluating properties" });
|
package/nodes/minmax-block.js
CHANGED
|
@@ -9,14 +9,11 @@ module.exports = function(RED) {
|
|
|
9
9
|
node.runtime = {
|
|
10
10
|
name: config.name,
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
const typedProperties = ['min', 'max'];
|
|
14
12
|
|
|
15
13
|
// Evaluate typed-input properties
|
|
16
14
|
try {
|
|
17
|
-
|
|
18
|
-
node.runtime.
|
|
19
|
-
node.runtime.max = parseFloat(evaluatedValues.max);
|
|
15
|
+
node.runtime.min = parseFloat(RED.util.evaluateNodeProperty( config.min, config.minType, node ));
|
|
16
|
+
node.runtime.max = parseFloat(RED.util.evaluateNodeProperty( config.max, config.maxType, node ));
|
|
20
17
|
} catch (err) {
|
|
21
18
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
22
19
|
}
|
|
@@ -35,10 +32,13 @@ module.exports = function(RED) {
|
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
// Update typed-input properties if needed
|
|
38
|
-
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
try {
|
|
36
|
+
if (utils.requiresEvaluation(config.minType)) {
|
|
37
|
+
node.runtime.min = parseFloat(RED.util.evaluateNodeProperty( config.min, config.minType, node, msg ));
|
|
38
|
+
}
|
|
39
|
+
if (utils.requiresEvaluation(config.maxType)) {
|
|
40
|
+
node.runtime.max = parseFloat(RED.util.evaluateNodeProperty( config.max, config.maxType, node, msg ));
|
|
41
|
+
}
|
|
42
42
|
} catch (err) {
|
|
43
43
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
44
44
|
if (done) done();
|
|
@@ -46,7 +46,7 @@ Filters redundant messages based on value changes and a configurable period.
|
|
|
46
46
|
|
|
47
47
|
### Inputs
|
|
48
48
|
: payload (any) : Input value to compare.
|
|
49
|
-
: context (string, optional) : Configures period (`"period"`) or queries state (`"status"`).
|
|
49
|
+
: context (string, optional) : Configures period (`"period"`) or queries state (`"status"`).
|
|
50
50
|
: payload (number | any, for `context`) : Non-negative number for `"period"` (ms), any for `"status"`.
|
|
51
51
|
|
|
52
52
|
### Outputs
|
package/nodes/on-change-block.js
CHANGED
|
@@ -9,14 +9,12 @@ module.exports = function(RED) {
|
|
|
9
9
|
node.runtime = {
|
|
10
10
|
name: config.name,
|
|
11
11
|
lastValue: null,
|
|
12
|
-
blockTimer: null
|
|
13
|
-
pendingMsg: null
|
|
12
|
+
blockTimer: null
|
|
14
13
|
};
|
|
15
14
|
|
|
16
15
|
// Evaluate typed-input properties
|
|
17
16
|
try {
|
|
18
|
-
node.runtime.period = RED.util.evaluateNodeProperty(
|
|
19
|
-
node.runtime.period = parseFloat(node.runtime.period);
|
|
17
|
+
node.runtime.period = parseFloat(RED.util.evaluateNodeProperty( config.period, config.periodType, node ));
|
|
20
18
|
} catch (err) {
|
|
21
19
|
node.status({ fill: "red", shape: "ring", text: "error evaluating properties" });
|
|
22
20
|
}
|
|
@@ -34,8 +32,7 @@ module.exports = function(RED) {
|
|
|
34
32
|
// Evaluate typed-input properties if needed
|
|
35
33
|
try {
|
|
36
34
|
if (utils.requiresEvaluation(node.runtime.periodType)) {
|
|
37
|
-
node.runtime.period = RED.util.evaluateNodeProperty(
|
|
38
|
-
node.runtime.period = parseFloat(node.runtime.period);
|
|
35
|
+
node.runtime.period = parseFloat(RED.util.evaluateNodeProperty( config.period, config.periodType, node, msg ));
|
|
39
36
|
}
|
|
40
37
|
} catch (err) {
|
|
41
38
|
node.status({ fill: "red", shape: "ring", text: "error evaluating properties" });
|
|
@@ -45,14 +42,13 @@ module.exports = function(RED) {
|
|
|
45
42
|
|
|
46
43
|
// Acceptable fallbacks
|
|
47
44
|
if (isNaN(node.runtime.period) || node.runtime.period < 0) {
|
|
48
|
-
node.runtime.period =
|
|
45
|
+
node.runtime.period = config.period;
|
|
49
46
|
node.status({ fill: "red", shape: "ring", text: "invalid period, using 0" });
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
// Handle context updates
|
|
53
50
|
if (msg.hasOwnProperty("context") && typeof msg.context === "string") {
|
|
54
|
-
|
|
55
|
-
if (contextLower === "period") {
|
|
51
|
+
if (msg.context === "period") {
|
|
56
52
|
if (!msg.hasOwnProperty("payload")) {
|
|
57
53
|
node.status({ fill: "red", shape: "ring", text: "missing payload for period" });
|
|
58
54
|
if (done) done();
|
|
@@ -74,7 +70,7 @@ module.exports = function(RED) {
|
|
|
74
70
|
if (done) done();
|
|
75
71
|
return;
|
|
76
72
|
}
|
|
77
|
-
if (
|
|
73
|
+
if (msg.context === "status") {
|
|
78
74
|
send({
|
|
79
75
|
payload: {
|
|
80
76
|
period: node.runtime.period,
|
|
@@ -114,9 +110,8 @@ module.exports = function(RED) {
|
|
|
114
110
|
return false;
|
|
115
111
|
}
|
|
116
112
|
|
|
117
|
-
//
|
|
113
|
+
// Block if in filter period
|
|
118
114
|
if (node.runtime.blockTimer) {
|
|
119
|
-
node.runtime.pendingMsg = RED.util.cloneMessage(msg);
|
|
120
115
|
node.status({
|
|
121
116
|
fill: "blue",
|
|
122
117
|
shape: "ring",
|
|
@@ -126,49 +121,27 @@ module.exports = function(RED) {
|
|
|
126
121
|
return;
|
|
127
122
|
}
|
|
128
123
|
|
|
129
|
-
//
|
|
130
|
-
if (
|
|
131
|
-
node.runtime.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
shape: "dot",
|
|
135
|
-
text: `out: ${JSON.stringify(currentValue).slice(0, 20)}`
|
|
136
|
-
});
|
|
137
|
-
send(msg);
|
|
138
|
-
|
|
139
|
-
// Start filter period if applicable
|
|
140
|
-
if (node.runtime.period > 0) {
|
|
141
|
-
node.runtime.blockTimer = setTimeout(() => {
|
|
142
|
-
node.runtime.blockTimer = null;
|
|
143
|
-
if (node.runtime.pendingMsg) {
|
|
144
|
-
const pendingValue = node.runtime.pendingMsg.payload;
|
|
145
|
-
if (!isEqual(pendingValue, node.runtime.lastValue)) {
|
|
146
|
-
node.runtime.lastValue = RED.util.cloneMessage(pendingValue);
|
|
147
|
-
node.status({
|
|
148
|
-
fill: "blue",
|
|
149
|
-
shape: "dot",
|
|
150
|
-
text: `out: ${JSON.stringify(pendingValue).slice(0, 20)}`
|
|
151
|
-
});
|
|
152
|
-
send(node.runtime.pendingMsg);
|
|
153
|
-
} else {
|
|
154
|
-
node.status({
|
|
155
|
-
fill: "blue",
|
|
156
|
-
shape: "ring",
|
|
157
|
-
text: `Filter period expired`
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
node.runtime.pendingMsg = null;
|
|
161
|
-
} else {
|
|
162
|
-
node.status({});
|
|
163
|
-
}
|
|
164
|
-
}, node.runtime.period);
|
|
124
|
+
// period === 0 means only ever on change, not equal outside of filter period sends an update message
|
|
125
|
+
if (isEqual(currentValue, node.runtime.lastValue)) {
|
|
126
|
+
if (node.runtime.period === 0) {
|
|
127
|
+
if (done) done();
|
|
128
|
+
return;
|
|
165
129
|
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
node.runtime.lastValue = currentValue;
|
|
133
|
+
send(msg);
|
|
134
|
+
|
|
135
|
+
// Start filter period if applicable
|
|
136
|
+
if (node.runtime.period > 0) {
|
|
137
|
+
node.runtime.blockTimer = setTimeout(() => {
|
|
138
|
+
node.runtime.blockTimer = null;
|
|
139
|
+
node.status({
|
|
140
|
+
fill: "blue",
|
|
141
|
+
shape: "ring",
|
|
142
|
+
text: `Filter period expired`
|
|
143
|
+
});
|
|
144
|
+
}, node.runtime.period);
|
|
172
145
|
}
|
|
173
146
|
|
|
174
147
|
if (done) done();
|
package/nodes/pid-block.js
CHANGED
|
@@ -301,7 +301,7 @@ module.exports = function(RED) {
|
|
|
301
301
|
|
|
302
302
|
// Output calculation
|
|
303
303
|
let pv = pGain + intGain + dGain;
|
|
304
|
-
if (node.runtime.directAction) pv = -pv;
|
|
304
|
+
//if (node.runtime.directAction) pv = -pv;
|
|
305
305
|
pv = Math.min(Math.max(pv, node.runtime.outMin || -Infinity), node.runtime.outMax || Infinity);
|
|
306
306
|
|
|
307
307
|
// Rate of change limit
|
package/nodes/tstat-block.js
CHANGED
|
@@ -10,24 +10,18 @@ module.exports = function(RED) {
|
|
|
10
10
|
node.algorithm = config.algorithm;
|
|
11
11
|
node.name = config.name;
|
|
12
12
|
|
|
13
|
-
function shouldEvaluate(type) { return type === "flow" || type === "global" || type === "msg"; }
|
|
14
|
-
|
|
15
|
-
const typedProperties = ['setpoint', 'heatingSetpoint', 'coolingSetpoint', 'coolingOn', 'coolingOff',
|
|
16
|
-
'heatingOff', 'heatingOn', 'diff', 'anticipator', 'ignoreAnticipatorCycles'];
|
|
17
|
-
|
|
18
13
|
// Evaluate typed-input properties
|
|
19
14
|
try {
|
|
20
|
-
|
|
21
|
-
node.
|
|
22
|
-
node.
|
|
23
|
-
node.
|
|
24
|
-
node.
|
|
25
|
-
node.
|
|
26
|
-
node.
|
|
27
|
-
node.
|
|
28
|
-
node.
|
|
29
|
-
node.
|
|
30
|
-
node.ignoreAnticipatorCycles = Math.floor(evaluatedValues.ignoreAnticipatorCycles);
|
|
15
|
+
node.setpoint = parseFloat(RED.util.evaluateNodeProperty( config.setpoint, config.setpointType, node ));
|
|
16
|
+
node.heatingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.heatingSetpoint, config.heatingSetpointType, node ));
|
|
17
|
+
node.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node ));
|
|
18
|
+
node.coolingOn = parseFloat(RED.util.evaluateNodeProperty( config.coolingOn, config.coolingOnType, node ));
|
|
19
|
+
node.coolingOff = parseFloat(RED.util.evaluateNodeProperty( config.coolingOff, config.coolingOffType, node ));
|
|
20
|
+
node.heatingOff = parseFloat(RED.util.evaluateNodeProperty( config.heatingOff, config.heatingOffType, node ));
|
|
21
|
+
node.heatingOn = parseFloat(RED.util.evaluateNodeProperty( config.heatingOn, config.heatingOnType, node ));
|
|
22
|
+
node.diff = parseFloat(RED.util.evaluateNodeProperty( config.diff, config.diffType, node ));
|
|
23
|
+
node.anticipator = parseFloat(RED.util.evaluateNodeProperty( config.anticipator, config.anticipatorType, node ));
|
|
24
|
+
node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node ));
|
|
31
25
|
} catch (err) {
|
|
32
26
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
33
27
|
}
|
|
@@ -51,17 +45,36 @@ module.exports = function(RED) {
|
|
|
51
45
|
|
|
52
46
|
// Update typed-input properties if needed
|
|
53
47
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
48
|
+
if (utils.requiresEvaluation(config.setpointType)) {
|
|
49
|
+
node.setpoint = parseFloat(RED.util.evaluateNodeProperty( config.setpoint, config.setpointType, node, msg ));
|
|
50
|
+
}
|
|
51
|
+
if (utils.requiresEvaluation(config.heatingSetpointType)) {
|
|
52
|
+
node.heatingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.heatingSetpoint, config.heatingSetpointType, node, msg ));
|
|
53
|
+
}
|
|
54
|
+
if (utils.requiresEvaluation(config.coolingSetpointType)) {
|
|
55
|
+
node.coolingSetpoint = parseFloat(RED.util.evaluateNodeProperty( config.coolingSetpoint, config.coolingSetpointType, node, msg ));
|
|
56
|
+
}
|
|
57
|
+
if (utils.requiresEvaluation(config.coolingOnType)) {
|
|
58
|
+
node.coolingOn = parseFloat(RED.util.evaluateNodeProperty( config.coolingOn, config.coolingOnType, node, msg ));
|
|
59
|
+
}
|
|
60
|
+
if (utils.requiresEvaluation(config.coolingOffType)) {
|
|
61
|
+
node.coolingOff = parseFloat(RED.util.evaluateNodeProperty( config.coolingOff, config.coolingOffType, node, msg ));
|
|
62
|
+
}
|
|
63
|
+
if (utils.requiresEvaluation(config.heatingOffType)) {
|
|
64
|
+
node.heatingOff = parseFloat(RED.util.evaluateNodeProperty( config.heatingOff, config.heatingOffType, node, msg ));
|
|
65
|
+
}
|
|
66
|
+
if (utils.requiresEvaluation(config.heatingOnType)) {
|
|
67
|
+
node.heatingOn = parseFloat(RED.util.evaluateNodeProperty( config.heatingOn, config.heatingOnType, node, msg ));
|
|
68
|
+
}
|
|
69
|
+
if (utils.requiresEvaluation(config.diffType)) {
|
|
70
|
+
node.diff = parseFloat(RED.util.evaluateNodeProperty( config.diff, config.diffType, node, msg ));
|
|
71
|
+
}
|
|
72
|
+
if (utils.requiresEvaluation(config.anticipatorType)) {
|
|
73
|
+
node.anticipator = parseFloat(RED.util.evaluateNodeProperty( config.anticipator, config.anticipatorType, node, msg ));
|
|
74
|
+
}
|
|
75
|
+
if (utils.requiresEvaluation(config.ignoreAnticipatorCyclesType)) {
|
|
76
|
+
node.ignoreAnticipatorCycles = Math.floor(RED.util.evaluateNodeProperty( config.ignoreAnticipatorCycles, config.ignoreAnticipatorCyclesType, node, msg ));
|
|
77
|
+
}
|
|
65
78
|
} catch (err) {
|
|
66
79
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
67
80
|
if (done) done();
|
package/nodes/utils.js
CHANGED
|
@@ -2,30 +2,9 @@ module.exports = function(RED) {
|
|
|
2
2
|
function requiresEvaluation(type) { return type === "flow" || type === "global" || type === "msg"; }
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
function evaluateProperties(node, config, properties, msg = null, initialize = true) {
|
|
6
|
-
const results = {};
|
|
7
|
-
|
|
8
|
-
properties.forEach(prop => {
|
|
9
|
-
const type = config[`${prop}Type`];
|
|
10
|
-
let value;
|
|
11
|
-
|
|
12
|
-
if (type === "msg" && msg === null) {
|
|
13
|
-
value = null;
|
|
14
|
-
} else if (initialize || requiresEvaluation(type)) {
|
|
15
|
-
value = RED.util.evaluateNodeProperty(config[prop], type, node, msg);
|
|
16
|
-
} else {
|
|
17
|
-
value = config[prop];
|
|
18
|
-
}
|
|
19
|
-
results[prop] = value;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return results;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
5
|
// const utils = require('./utils')(RED);
|
|
26
6
|
|
|
27
7
|
return {
|
|
28
|
-
requiresEvaluation
|
|
29
|
-
evaluateProperties
|
|
8
|
+
requiresEvaluation
|
|
30
9
|
};
|
|
31
10
|
}
|
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.27",
|
|
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"],
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"delay-block": "nodes/delay-block.js",
|
|
30
30
|
"divide-block": "nodes/divide-block.js",
|
|
31
31
|
"edge-block": "nodes/edge-block.js",
|
|
32
|
+
"enum-switch-block": "nodes/enum-switch-block.js",
|
|
32
33
|
"frequency-block": "nodes/frequency-block.js",
|
|
33
34
|
"hysteresis-block": "nodes/hysteresis-block.js",
|
|
34
35
|
"interpolate-block": "nodes/interpolate-block.js",
|