@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
package/nodes/delay-block.js
CHANGED
|
@@ -5,13 +5,12 @@ module.exports = function(RED) {
|
|
|
5
5
|
RED.nodes.createNode(this, config);
|
|
6
6
|
const node = this;
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
};
|
|
8
|
+
// Initialize state
|
|
9
|
+
node.name = config.name;
|
|
10
|
+
node.state = false;
|
|
11
|
+
node.desired = false;
|
|
12
|
+
node.delayOn = parseFloat(config.delayOn) * (config.delayOnUnits === "seconds" ? 1000 : config.delayOnUnits === "minutes" ? 60000 : 1);
|
|
13
|
+
node.delayOff = parseFloat(config.delayOff) * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1);
|
|
15
14
|
|
|
16
15
|
let timeoutId = null;
|
|
17
16
|
node.isBusy = false;
|
|
@@ -21,7 +20,7 @@ module.exports = function(RED) {
|
|
|
21
20
|
|
|
22
21
|
// Guard against invalid msg
|
|
23
22
|
if (!msg) {
|
|
24
|
-
|
|
23
|
+
utils.setStatusError(node, "invalid message");
|
|
25
24
|
if (done) done();
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
@@ -32,7 +31,7 @@ module.exports = function(RED) {
|
|
|
32
31
|
// Check busy lock
|
|
33
32
|
if (node.isBusy) {
|
|
34
33
|
// Update status to let user know they are pushing too fast
|
|
35
|
-
|
|
34
|
+
utils.setStatusBusy(node, "busy - dropped msg");
|
|
36
35
|
if (done) done();
|
|
37
36
|
return;
|
|
38
37
|
}
|
|
@@ -47,21 +46,21 @@ module.exports = function(RED) {
|
|
|
47
46
|
utils.requiresEvaluation(config.delayOnType)
|
|
48
47
|
? utils.evaluateNodeProperty(config.delayOn, config.delayOnType, node, msg)
|
|
49
48
|
.then(val => parseFloat(val))
|
|
50
|
-
: Promise.resolve(node.
|
|
49
|
+
: Promise.resolve(node.delayOn),
|
|
51
50
|
);
|
|
52
51
|
|
|
53
52
|
evaluations.push(
|
|
54
53
|
utils.requiresEvaluation(config.delayOffType)
|
|
55
54
|
? utils.evaluateNodeProperty(config.delayOff, config.delayOffType, node, msg)
|
|
56
55
|
.then(val => parseFloat(val))
|
|
57
|
-
: Promise.resolve(node.
|
|
56
|
+
: Promise.resolve(node.delayOff),
|
|
58
57
|
);
|
|
59
58
|
|
|
60
59
|
const results = await Promise.all(evaluations);
|
|
61
60
|
|
|
62
61
|
// Update runtime with evaluated values
|
|
63
|
-
if (!isNaN(results[0])) node.
|
|
64
|
-
if (!isNaN(results[1])) node.
|
|
62
|
+
if (!isNaN(results[0])) node.delayOn = results[0] * (config.delayOnUnits === "seconds" ? 1000 : config.delayOnUnits === "minutes" ? 60000 : 1);
|
|
63
|
+
if (!isNaN(results[1])) node.delayOff = results[1] * (config.delayOffUnits === "seconds" ? 1000 : config.delayOffUnits === "minutes" ? 60000 : 1);
|
|
65
64
|
} catch (err) {
|
|
66
65
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
67
66
|
if (done) done();
|
|
@@ -72,35 +71,41 @@ module.exports = function(RED) {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
// Acceptable fallbacks
|
|
75
|
-
if (isNaN(node.
|
|
76
|
-
node.
|
|
77
|
-
|
|
74
|
+
if (isNaN(node.delayOn) || node.delayOn < 0) {
|
|
75
|
+
node.delayOn = 1000;
|
|
76
|
+
utils.setStatusError(node, "invalid delayOn");
|
|
78
77
|
}
|
|
79
|
-
if (isNaN(node.
|
|
80
|
-
node.
|
|
81
|
-
|
|
78
|
+
if (isNaN(node.delayOff) || node.delayOff < 0) {
|
|
79
|
+
node.delayOff = 1000;
|
|
80
|
+
utils.setStatusError(node, "invalid delayOff");
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
if (msg.hasOwnProperty("context")) {
|
|
85
84
|
if (msg.context === "reset") {
|
|
86
|
-
if (!msg.hasOwnProperty("payload")
|
|
87
|
-
|
|
85
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
86
|
+
utils.setStatusError(node, "missing payload");
|
|
87
|
+
if (done) done();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const boolVal = utils.validateBoolean(msg.payload);
|
|
91
|
+
if (!boolVal.valid) {
|
|
92
|
+
utils.setStatusError(node, boolVal.error);
|
|
88
93
|
if (done) done();
|
|
89
94
|
return;
|
|
90
95
|
}
|
|
91
|
-
if (
|
|
96
|
+
if (boolVal.value === true) {
|
|
92
97
|
if (timeoutId) {
|
|
93
98
|
clearTimeout(timeoutId);
|
|
94
99
|
timeoutId = null;
|
|
95
100
|
}
|
|
96
|
-
node.
|
|
97
|
-
|
|
101
|
+
node.state = false;
|
|
102
|
+
utils.setStatusOK(node, "reset");
|
|
98
103
|
}
|
|
99
104
|
if (done) done();
|
|
100
105
|
return;
|
|
101
106
|
} else if (msg.context === "delayOn") {
|
|
102
107
|
if (!msg.hasOwnProperty("payload")) {
|
|
103
|
-
|
|
108
|
+
utils.setStatusError(node, "missing payload for delayOn");
|
|
104
109
|
if (done) done();
|
|
105
110
|
return;
|
|
106
111
|
}
|
|
@@ -108,17 +113,17 @@ module.exports = function(RED) {
|
|
|
108
113
|
const newDelayOnMultiplier = msg.units === "seconds" ? 1000 : msg.units === "minutes" ? 60000 : 1;
|
|
109
114
|
newDelayOn *= newDelayOnMultiplier;
|
|
110
115
|
if (isNaN(newDelayOn) || newDelayOn < 0) {
|
|
111
|
-
|
|
116
|
+
utils.setStatusError(node, "invalid delayOn");
|
|
112
117
|
if (done) done();
|
|
113
118
|
return;
|
|
114
119
|
}
|
|
115
|
-
node.
|
|
116
|
-
|
|
120
|
+
node.delayOn = newDelayOn;
|
|
121
|
+
utils.setStatusOK(node, `delayOn: ${newDelayOn.toFixed(0)} ms`);
|
|
117
122
|
if (done) done();
|
|
118
123
|
return;
|
|
119
124
|
} else if (msg.context === "delayOff") {
|
|
120
125
|
if (!msg.hasOwnProperty("payload")) {
|
|
121
|
-
|
|
126
|
+
utils.setStatusError(node, "missing payload for delayOff");
|
|
122
127
|
if (done) done();
|
|
123
128
|
return;
|
|
124
129
|
}
|
|
@@ -126,81 +131,81 @@ module.exports = function(RED) {
|
|
|
126
131
|
const newDelayOffMultiplier = msg.units === "seconds" ? 1000 : msg.units === "minutes" ? 60000 : 1;
|
|
127
132
|
newDelayOff *= newDelayOffMultiplier;
|
|
128
133
|
if (isNaN(newDelayOff) || newDelayOff < 0) {
|
|
129
|
-
|
|
134
|
+
utils.setStatusError(node, "invalid delayOff");
|
|
130
135
|
if (done) done();
|
|
131
136
|
return;
|
|
132
137
|
}
|
|
133
|
-
node.
|
|
134
|
-
|
|
138
|
+
node.delayOff = newDelayOff;
|
|
139
|
+
utils.setStatusOK(node, `delayOff: ${newDelayOff.toFixed(0)} ms`);
|
|
135
140
|
if (done) done();
|
|
136
141
|
return;
|
|
137
142
|
}
|
|
138
|
-
|
|
143
|
+
utils.setStatusWarn(node, "unknown context");
|
|
139
144
|
if (done) done();
|
|
140
145
|
return;
|
|
141
146
|
}
|
|
142
147
|
|
|
143
148
|
if (!msg.hasOwnProperty("payload")) {
|
|
144
|
-
|
|
149
|
+
utils.setStatusError(node, "missing payload");
|
|
145
150
|
if (done) done();
|
|
146
151
|
return;
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
const inputValue = msg.payload;
|
|
150
155
|
if (typeof inputValue !== "boolean") {
|
|
151
|
-
|
|
156
|
+
utils.setStatusError(node, "invalid payload");
|
|
152
157
|
if (done) done();
|
|
153
158
|
return;
|
|
154
159
|
}
|
|
155
160
|
|
|
156
|
-
if (!node.
|
|
157
|
-
if (node.
|
|
161
|
+
if (!node.state && inputValue === true) {
|
|
162
|
+
if (node.desired) {
|
|
158
163
|
if (done) done();
|
|
159
164
|
return;
|
|
160
165
|
}
|
|
161
166
|
if (timeoutId) {
|
|
162
167
|
clearTimeout(timeoutId);
|
|
163
168
|
}
|
|
164
|
-
|
|
165
|
-
node.
|
|
169
|
+
utils.setStatusUnchanged(node, "awaiting true");
|
|
170
|
+
node.desired = true;
|
|
166
171
|
timeoutId = setTimeout(() => {
|
|
167
|
-
node.
|
|
172
|
+
node.state = true;
|
|
168
173
|
msg.payload = true;
|
|
169
174
|
delete msg.context;
|
|
170
|
-
|
|
175
|
+
utils.setStatusChanged(node, "in: true, out: true");
|
|
171
176
|
send(msg);
|
|
172
177
|
timeoutId = null;
|
|
173
|
-
}, node.
|
|
174
|
-
} else if (node.
|
|
175
|
-
if (node.
|
|
178
|
+
}, node.delayOn);
|
|
179
|
+
} else if (node.state && inputValue === false) {
|
|
180
|
+
if (node.desired === false) {
|
|
176
181
|
if (done) done();
|
|
177
182
|
return;
|
|
178
183
|
}
|
|
179
184
|
if (timeoutId) {
|
|
180
185
|
clearTimeout(timeoutId);
|
|
181
186
|
}
|
|
182
|
-
|
|
183
|
-
node.
|
|
187
|
+
utils.setStatusUnchanged(node, "awaiting false");
|
|
188
|
+
node.desired = false;
|
|
184
189
|
timeoutId = setTimeout(() => {
|
|
185
|
-
node.
|
|
190
|
+
node.state = false;
|
|
186
191
|
msg.payload = false;
|
|
187
192
|
delete msg.context;
|
|
188
|
-
|
|
193
|
+
utils.setStatusChanged(node, "in: false, out: false");
|
|
189
194
|
send(msg);
|
|
190
195
|
timeoutId = null;
|
|
191
|
-
}, node.
|
|
196
|
+
}, node.delayOff);
|
|
192
197
|
} else {
|
|
193
198
|
if (timeoutId) {
|
|
194
199
|
clearTimeout(timeoutId);
|
|
195
200
|
timeoutId = null;
|
|
196
|
-
|
|
201
|
+
utils.setStatusUnchanged(node, `canceled awaiting ${node.state}`);
|
|
197
202
|
} else {
|
|
198
|
-
|
|
203
|
+
utils.setStatusUnchanged(node, "no change");
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
// No state change, pass the message through
|
|
202
|
-
node.
|
|
203
|
-
desired = inputValue;
|
|
207
|
+
node.state = inputValue;
|
|
208
|
+
node.desired = inputValue;
|
|
204
209
|
send(msg);
|
|
205
210
|
}
|
|
206
211
|
|
package/nodes/divide-block.js
CHANGED
|
@@ -1,114 +1,112 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function DivideBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
|
|
5
7
|
const node = this;
|
|
6
8
|
|
|
7
9
|
// Initialize runtime state
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
10
|
+
// Initialize state
|
|
11
|
+
node.name = config.name;
|
|
12
|
+
node.slots = parseInt(config.slots);
|
|
13
|
+
node.inputs = Array(config.slots).fill(1).map(x => parseFloat(x));
|
|
14
|
+
node.lastResult = null;
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
fill: "green",
|
|
17
|
-
shape: "dot",
|
|
18
|
-
text: `name: ${node.runtime.name}, slots: ${node.runtime.slots}`
|
|
19
|
-
});
|
|
16
|
+
utils.setStatusOK(node, `name: ${node.name}, slots: ${node.slots}`);
|
|
20
17
|
|
|
21
18
|
node.on("input", function(msg, send, done) {
|
|
22
19
|
send = send || function() { node.send.apply(node, arguments); };
|
|
23
20
|
|
|
24
21
|
// Guard against invalid msg
|
|
25
22
|
if (!msg) {
|
|
26
|
-
|
|
23
|
+
utils.setStatusError(node, "invalid message");
|
|
27
24
|
if (done) done();
|
|
28
25
|
return;
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
// Check for missing context or payload
|
|
32
29
|
if (!msg.hasOwnProperty("context")) {
|
|
33
|
-
|
|
30
|
+
utils.setStatusError(node, "missing context");
|
|
34
31
|
if (done) done();
|
|
35
32
|
return;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
if (!msg.hasOwnProperty("payload")) {
|
|
39
|
-
|
|
36
|
+
utils.setStatusError(node, "missing payload");
|
|
40
37
|
if (done) done();
|
|
41
38
|
return;
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
// Handle configuration messages
|
|
45
42
|
if (msg.context === "reset") {
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
const boolVal = utils.validateBoolean(msg.payload);
|
|
44
|
+
if (!boolVal.valid) {
|
|
45
|
+
utils.setStatusError(node, boolVal.error);
|
|
48
46
|
if (done) done();
|
|
49
47
|
return;
|
|
50
48
|
}
|
|
51
|
-
if (
|
|
52
|
-
node.
|
|
53
|
-
node.
|
|
54
|
-
|
|
49
|
+
if (boolVal.value === true) {
|
|
50
|
+
node.inputs = Array(node.slots).fill(1);
|
|
51
|
+
node.lastResult = null;
|
|
52
|
+
utils.setStatusOK(node, "state reset");
|
|
55
53
|
if (done) done();
|
|
56
54
|
return;
|
|
57
55
|
}
|
|
58
56
|
} else if (msg.context === "slots") {
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
57
|
+
const slotsVal = utils.validateIntRange(msg.payload, { min: 1 });
|
|
58
|
+
if (!slotsVal.valid) {
|
|
59
|
+
utils.setStatusError(node, slotsVal.error);
|
|
62
60
|
if (done) done();
|
|
63
61
|
return;
|
|
64
62
|
}
|
|
65
|
-
node.
|
|
66
|
-
node.
|
|
67
|
-
node.
|
|
68
|
-
|
|
63
|
+
node.slots = slotsVal.value;
|
|
64
|
+
node.inputs = Array(newSlots).fill(1);
|
|
65
|
+
node.lastResult = null;
|
|
66
|
+
utils.setStatusOK(node, `slots: ${node.slots}`);
|
|
69
67
|
if (done) done();
|
|
70
68
|
return;
|
|
71
69
|
} else if (msg.context.startsWith("in")) {
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
70
|
+
const slotVal = utils.validateSlotIndex(msg.context, node.slots);
|
|
71
|
+
if (!slotVal.valid) {
|
|
72
|
+
utils.setStatusError(node, slotVal.error);
|
|
75
73
|
if (done) done();
|
|
76
74
|
return;
|
|
77
75
|
}
|
|
76
|
+
const slotIndex = slotVal.index - 1;
|
|
78
77
|
let newValue = parseFloat(msg.payload);
|
|
79
78
|
if (isNaN(newValue)) {
|
|
80
|
-
|
|
79
|
+
utils.setStatusError(node, "invalid input");
|
|
81
80
|
if (done) done();
|
|
82
81
|
return;
|
|
83
82
|
}
|
|
84
83
|
if (slotIndex > 0 && newValue === 0) {
|
|
85
|
-
|
|
84
|
+
utils.setStatusError(node, "divide by zero");
|
|
86
85
|
if (done) done();
|
|
87
86
|
return;
|
|
88
87
|
}
|
|
89
88
|
// Handle division by very small numbers approaching zero
|
|
90
89
|
if (slotIndex > 0 && Math.abs(newValue) < 1e-10) { // Near-zero check
|
|
91
|
-
|
|
90
|
+
utils.setStatusError(node, "divide by near-zero");
|
|
92
91
|
if (done) done();
|
|
93
92
|
return;
|
|
94
93
|
}
|
|
95
|
-
node.
|
|
94
|
+
node.inputs[slotIndex] = newValue;
|
|
96
95
|
// Calculate division
|
|
97
|
-
const result = node.
|
|
98
|
-
const isUnchanged = result === node.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (!isUnchanged) {
|
|
105
|
-
node.runtime.lastResult = result;
|
|
106
|
-
send({ payload: result });
|
|
96
|
+
const result = node.inputs.reduce((acc, val, idx) => idx === 0 ? val : acc / val, 1);
|
|
97
|
+
const isUnchanged = result === node.lastResult;
|
|
98
|
+
const statusText = `in: ${msg.context}=${newValue.toFixed(2)}, out: ${result.toFixed(2)}`;
|
|
99
|
+
if (isUnchanged) {
|
|
100
|
+
utils.setStatusUnchanged(node, statusText);
|
|
101
|
+
} else {
|
|
102
|
+
utils.setStatusChanged(node, statusText);
|
|
107
103
|
}
|
|
104
|
+
node.lastResult = result;
|
|
105
|
+
send({ payload: result });
|
|
108
106
|
if (done) done();
|
|
109
107
|
return;
|
|
110
108
|
} else {
|
|
111
|
-
|
|
109
|
+
utils.setStatusWarn(node, "unknown context");
|
|
112
110
|
if (done) done();
|
|
113
111
|
return;
|
|
114
112
|
}
|
package/nodes/edge-block.html
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
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-algorithm" title="Transition to detect (true-to-false or false-to-true)"><i class="fa fa-exchange"></i> Algorithm</label>
|
|
9
13
|
<select id="node-input-algorithm">
|
|
10
14
|
<option value="true-to-false">True to False</option>
|
|
@@ -20,6 +24,7 @@
|
|
|
20
24
|
color: "#301934",
|
|
21
25
|
defaults: {
|
|
22
26
|
name: { value: "" },
|
|
27
|
+
inputProperty: { value: "payload" },
|
|
23
28
|
algorithm: { value: "true-to-false", required: true }
|
|
24
29
|
},
|
|
25
30
|
inputs: 1,
|
|
@@ -36,27 +41,29 @@
|
|
|
36
41
|
|
|
37
42
|
<!-- Help Section -->
|
|
38
43
|
<script type="text/markdown" data-help-name="edge-block">
|
|
39
|
-
Detects
|
|
44
|
+
Detects boolean state transitions (true-to-false or false-to-true).
|
|
40
45
|
|
|
41
46
|
### Inputs
|
|
42
|
-
:
|
|
47
|
+
: input-property (boolean) : Boolean value to monitor for transitions, read from the configured Input Property.
|
|
43
48
|
: context (string) : Action (`"algorithm"` to set transition type, `"reset"` to clear state). Unknown `msg.context` is ignored.
|
|
44
49
|
: payload (string | boolean) : Transition type (`"true-to-false"`, `"false-to-true"`) for `"algorithm"`, true for `"reset"`.
|
|
45
50
|
|
|
46
51
|
### Outputs
|
|
47
|
-
: payload (boolean) : `true` when the specified transition occurs
|
|
52
|
+
: payload (boolean) : `true` when the specified transition occurs; `false` otherwise.
|
|
48
53
|
|
|
49
54
|
### Properties
|
|
50
55
|
: name (string) : Display name in editor.
|
|
56
|
+
: inputProperty (string) : Message property to read input from (default: `payload`). Supports nested properties (e.g., `data.value`).
|
|
51
57
|
: algorithm (string) : Transition to detect (`"true-to-false"`, `"false-to-true"`).
|
|
52
58
|
|
|
53
59
|
### Details
|
|
54
|
-
Detects transitions in boolean
|
|
55
|
-
Outputs `msg.payload` as true only when the specified transition occurs (true-to-false or false-to-true).
|
|
56
|
-
No output on first input after reset, if no transition occurs, or for non-boolean inputs.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- `"
|
|
60
|
+
Detects transitions in boolean input (read from the configured **Input Property**, default: `msg.payload`) based on the configured `algorithm`.
|
|
61
|
+
Outputs `msg.payload` as true only when the specified transition occurs (true-to-false or false-to-true).
|
|
62
|
+
No output on first input after reset, if no transition occurs, or for non-boolean inputs.
|
|
63
|
+
|
|
64
|
+
Configuration via `msg.context`:
|
|
65
|
+
- `"algorithm"`: Sets transition type, no output.
|
|
66
|
+
- `"reset"`: Clears state, no output.
|
|
60
67
|
|
|
61
68
|
### Status
|
|
62
69
|
- Green (dot): Configuration
|
package/nodes/edge-block.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function EdgeBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
const node = this;
|
|
5
7
|
|
|
6
8
|
// Initialize runtime state
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
fill: "green",
|
|
15
|
-
shape: "dot",
|
|
16
|
-
text: `name: ${node.runtime.name || "edge"}, algorithm: ${node.runtime.algorithm}`
|
|
17
|
-
});
|
|
9
|
+
// Initialize state
|
|
10
|
+
node.name = config.name;
|
|
11
|
+
node.inputProperty = config.inputProperty || "payload";
|
|
12
|
+
node.algorithm = config.algorithm;
|
|
13
|
+
node.lastValue = null;
|
|
14
|
+
|
|
15
|
+
utils.setStatusOK(node, `name: ${node.name || "edge"}, algorithm: ${node.algorithm}`);
|
|
18
16
|
|
|
19
17
|
node.on("input", function(msg, send, done) {
|
|
20
18
|
send = send || function() { node.send.apply(node, arguments); };
|
|
@@ -23,7 +21,7 @@ module.exports = function(RED) {
|
|
|
23
21
|
|
|
24
22
|
// Guard against invalid message
|
|
25
23
|
if (!msg) {
|
|
26
|
-
|
|
24
|
+
utils.setStatusError(node, "invalid message");
|
|
27
25
|
if (done) done();
|
|
28
26
|
return;
|
|
29
27
|
}
|
|
@@ -32,31 +30,37 @@ module.exports = function(RED) {
|
|
|
32
30
|
if (msg.hasOwnProperty("context") && typeof msg.context === "string") {
|
|
33
31
|
if (msg.context === "algorithm") {
|
|
34
32
|
if (!msg.hasOwnProperty("payload")) {
|
|
35
|
-
|
|
33
|
+
utils.setStatusError(node, "missing payload");
|
|
36
34
|
if (done) done();
|
|
37
35
|
return;
|
|
38
36
|
}
|
|
39
37
|
const newAlgorithm = String(msg.payload);
|
|
40
38
|
if (!validAlgorithms.includes(newAlgorithm)) {
|
|
41
|
-
|
|
39
|
+
utils.setStatusError(node, "invalid algorithm");
|
|
42
40
|
if (done) done();
|
|
43
41
|
return;
|
|
44
42
|
}
|
|
45
|
-
node.
|
|
46
|
-
|
|
43
|
+
node.algorithm = newAlgorithm;
|
|
44
|
+
utils.setStatusOK(node, `algorithm: ${newAlgorithm}`);
|
|
47
45
|
if (done) done();
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
if (msg.context === "reset") {
|
|
52
|
-
if (!msg.hasOwnProperty("payload")
|
|
53
|
-
|
|
50
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
51
|
+
utils.setStatusError(node, "missing payload");
|
|
54
52
|
if (done) done();
|
|
55
53
|
return;
|
|
56
54
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const boolVal = utils.validateBoolean(msg.payload);
|
|
56
|
+
if (!boolVal.valid) {
|
|
57
|
+
utils.setStatusError(node, boolVal.error);
|
|
58
|
+
if (done) done();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (boolVal.value === true) {
|
|
62
|
+
node.lastValue = null;
|
|
63
|
+
utils.setStatusOK(node, "state reset");
|
|
60
64
|
if (done) done();
|
|
61
65
|
return;
|
|
62
66
|
}
|
|
@@ -66,48 +70,46 @@ module.exports = function(RED) {
|
|
|
66
70
|
// Ignore unknown context, process payload
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
// Get input from configured property
|
|
74
|
+
let inputValue;
|
|
75
|
+
try {
|
|
76
|
+
inputValue = RED.util.getMessageProperty(msg, node.inputProperty);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
inputValue = undefined;
|
|
79
|
+
}
|
|
80
|
+
if (inputValue === undefined) {
|
|
81
|
+
utils.setStatusError(node, "missing or invalid input property");
|
|
72
82
|
if (done) done();
|
|
73
83
|
return;
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
if (typeof
|
|
77
|
-
|
|
86
|
+
if (typeof inputValue !== "boolean") {
|
|
87
|
+
utils.setStatusError(node, "invalid input");
|
|
78
88
|
if (done) done();
|
|
79
89
|
return;
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
const currentValue =
|
|
83
|
-
const lastValue = node.
|
|
92
|
+
const currentValue = inputValue;
|
|
93
|
+
const lastValue = node.lastValue;
|
|
84
94
|
|
|
85
95
|
// Check for transition
|
|
86
96
|
let isTransition = false;
|
|
87
97
|
if (lastValue !== null && lastValue !== undefined) {
|
|
88
|
-
if (node.
|
|
98
|
+
if (node.algorithm === "true-to-false" && lastValue === true && currentValue === false) {
|
|
89
99
|
isTransition = true;
|
|
90
|
-
} else if (node.
|
|
100
|
+
} else if (node.algorithm === "false-to-true" && lastValue === false && currentValue === true) {
|
|
91
101
|
isTransition = true;
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
if (isTransition) {
|
|
96
|
-
|
|
97
|
-
fill: "blue",
|
|
98
|
-
shape: "dot",
|
|
99
|
-
text: `in: ${currentValue}, out: true`
|
|
100
|
-
});
|
|
106
|
+
utils.setStatusChanged(node, `in: ${currentValue}, out: true`);
|
|
101
107
|
send({ payload: true });
|
|
102
108
|
} else {
|
|
103
|
-
|
|
104
|
-
fill: "blue",
|
|
105
|
-
shape: "ring",
|
|
106
|
-
text: `in: ${currentValue}, out: none`
|
|
107
|
-
});
|
|
109
|
+
utils.setStatusUnchanged(node, `in: ${currentValue}, out: none`);
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
node.
|
|
112
|
+
node.lastValue = currentValue;
|
|
111
113
|
if (done) done();
|
|
112
114
|
});
|
|
113
115
|
|