@bldgblocks/node-red-contrib-control 0.1.34 → 0.1.37
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 +464 -0
- package/nodes/history-collector.html +29 -1
- package/nodes/history-collector.js +46 -16
- package/nodes/history-config.html +13 -1
- package/nodes/history-service.html +84 -0
- package/nodes/history-service.js +66 -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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
4
|
+
function AlarmServiceNode(config) {
|
|
5
|
+
RED.nodes.createNode(this, config);
|
|
6
|
+
const node = this;
|
|
7
|
+
|
|
8
|
+
// Initialize configuration
|
|
9
|
+
node.name = config.name || "alarm-service";
|
|
10
|
+
node.filterTopic = config.filterTopic || ""; // Optional: only listen to alarms with this topic
|
|
11
|
+
node.filterPriority = config.filterPriority || ""; // Optional: only listen to this priority
|
|
12
|
+
|
|
13
|
+
// Runtime state
|
|
14
|
+
node.activeAlarms = new Map(); // Map of topic → { state, data, timestamp }
|
|
15
|
+
node.alarmListener = null;
|
|
16
|
+
|
|
17
|
+
utils.setStatusOK(node, "listening");
|
|
18
|
+
|
|
19
|
+
// ====================================================================
|
|
20
|
+
// Handle alarm events from collectors
|
|
21
|
+
// ====================================================================
|
|
22
|
+
node.alarmListener = function(eventData) {
|
|
23
|
+
// Filter by topic if configured
|
|
24
|
+
if (node.filterTopic && eventData.topic !== node.filterTopic) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Filter by priority if configured
|
|
29
|
+
if (node.filterPriority && eventData.priority !== node.filterPriority) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Use topic as the key (allows multiple alarms with different topics)
|
|
34
|
+
const key = eventData.topic || `alarm_${eventData.nodeId}`;
|
|
35
|
+
|
|
36
|
+
// Update active alarms map
|
|
37
|
+
if (eventData.state === true) {
|
|
38
|
+
// Alarm triggered
|
|
39
|
+
node.activeAlarms.set(key, {
|
|
40
|
+
state: true,
|
|
41
|
+
data: eventData,
|
|
42
|
+
timestamp: new Date(eventData.timestamp)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Update status to show active alarm count
|
|
46
|
+
const activeCount = node.activeAlarms.size;
|
|
47
|
+
utils.setStatusError(node, `${activeCount} active alarm(s)`);
|
|
48
|
+
|
|
49
|
+
// Send alarm message with status
|
|
50
|
+
const msg = {
|
|
51
|
+
payload: eventData,
|
|
52
|
+
status: { state: "triggered", transition: eventData.transition },
|
|
53
|
+
activeAlarmCount: activeCount,
|
|
54
|
+
alarmKey: key
|
|
55
|
+
};
|
|
56
|
+
node.send(msg);
|
|
57
|
+
|
|
58
|
+
} else if (eventData.state === false) {
|
|
59
|
+
// Alarm cleared
|
|
60
|
+
if (node.activeAlarms.has(key)) {
|
|
61
|
+
const clearedAlarm = node.activeAlarms.get(key);
|
|
62
|
+
node.activeAlarms.delete(key);
|
|
63
|
+
|
|
64
|
+
// Update status
|
|
65
|
+
const activeCount = node.activeAlarms.size;
|
|
66
|
+
if (activeCount === 0) {
|
|
67
|
+
utils.setStatusOK(node, "listening (no active alarms)");
|
|
68
|
+
} else {
|
|
69
|
+
utils.setStatusWarn(node, `${activeCount} active alarm(s)`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Send clear message with status
|
|
73
|
+
const msg = {
|
|
74
|
+
payload: eventData,
|
|
75
|
+
status: { state: "cleared", transition: eventData.transition },
|
|
76
|
+
activeAlarmCount: activeCount,
|
|
77
|
+
alarmKey: key
|
|
78
|
+
};
|
|
79
|
+
node.send(msg);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Listen to the fixed alarm event
|
|
85
|
+
RED.events.on("bldgblocks:alarms:state-change", node.alarmListener);
|
|
86
|
+
|
|
87
|
+
// Handle wired input messages (optional - can relay or query status)
|
|
88
|
+
node.on("input", function(msg, send, done) {
|
|
89
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
90
|
+
|
|
91
|
+
if (!msg) {
|
|
92
|
+
utils.setStatusError(node, "invalid message");
|
|
93
|
+
if (done) done();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Handle control messages
|
|
98
|
+
if (msg.hasOwnProperty("context")) {
|
|
99
|
+
if (msg.context === "getStatus") {
|
|
100
|
+
// Query current alarm status
|
|
101
|
+
const alarmArray = Array.from(node.activeAlarms.values()).map(a => a.data);
|
|
102
|
+
const statusMsg = {
|
|
103
|
+
payload: alarmArray,
|
|
104
|
+
activeCount: node.activeAlarms.size,
|
|
105
|
+
timestamp: new Date().toISOString()
|
|
106
|
+
};
|
|
107
|
+
send(statusMsg);
|
|
108
|
+
utils.setStatusOK(node, `status: ${node.activeAlarms.size} alarms`);
|
|
109
|
+
if (done) done();
|
|
110
|
+
return;
|
|
111
|
+
} else if (msg.context === "clearAll") {
|
|
112
|
+
// Clear all active alarms from tracking (for reset scenarios)
|
|
113
|
+
if (msg.payload === true) {
|
|
114
|
+
node.activeAlarms.clear();
|
|
115
|
+
utils.setStatusOK(node, "listening (cleared)");
|
|
116
|
+
if (done) done();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Pass-through: relay incoming message downstream
|
|
123
|
+
send(msg);
|
|
124
|
+
if (done) done();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
node.on("close", function(done) {
|
|
128
|
+
// Remove alarm listener
|
|
129
|
+
if (node.alarmListener) {
|
|
130
|
+
RED.events.off("bldgblocks:alarms:state-change", node.alarmListener);
|
|
131
|
+
node.alarmListener = null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Clear active alarms map
|
|
135
|
+
node.activeAlarms.clear();
|
|
136
|
+
|
|
137
|
+
done();
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
RED.nodes.registerType("alarm-service", AlarmServiceNode);
|
|
142
|
+
};
|
|
@@ -1,82 +1,75 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function AnalogSwitchBlockNode(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
|
-
};
|
|
9
|
+
// Initialize state
|
|
10
|
+
node.name = config.name;
|
|
11
|
+
node.slots = parseInt(config.slots, 10);
|
|
12
|
+
node.inputs = Array(parseInt(config.slots, 10) || 2).fill(0);
|
|
13
|
+
node.switch = 1;
|
|
13
14
|
|
|
14
15
|
node.on("input", function(msg, send, done) {
|
|
15
16
|
send = send || function() { node.send.apply(node, arguments); };
|
|
16
17
|
|
|
17
18
|
// Guard against invalid message
|
|
18
19
|
if (!msg) {
|
|
19
|
-
|
|
20
|
+
utils.setStatusError(node, "invalid message");
|
|
20
21
|
if (done) done();
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// Validate context
|
|
25
26
|
if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
|
|
26
|
-
|
|
27
|
+
utils.setStatusError(node, "missing context");
|
|
27
28
|
if (done) done();
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
// Validate payload
|
|
32
33
|
if (!msg.hasOwnProperty("payload")) {
|
|
33
|
-
|
|
34
|
+
utils.setStatusError(node, "missing payload");
|
|
34
35
|
if (done) done();
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
let shouldOutput = false;
|
|
39
|
-
const prevSwitch = node.
|
|
40
|
+
const prevSwitch = node.switch;
|
|
40
41
|
|
|
41
42
|
switch (msg.context) {
|
|
42
43
|
case "switch":
|
|
43
44
|
const switchValue = parseInt(msg.payload, 10);
|
|
44
|
-
if (isNaN(switchValue) || switchValue < 1 || switchValue > node.
|
|
45
|
-
|
|
45
|
+
if (isNaN(switchValue) || switchValue < 1 || switchValue > node.slots) {
|
|
46
|
+
utils.setStatusError(node, "invalid switch");
|
|
46
47
|
if (done) done();
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
|
-
node.
|
|
50
|
-
shouldOutput = prevSwitch !== node.
|
|
51
|
-
|
|
52
|
-
fill: "green",
|
|
53
|
-
shape: "dot",
|
|
54
|
-
text: `switch: ${node.runtime.switch}`
|
|
55
|
-
});
|
|
50
|
+
node.switch = switchValue;
|
|
51
|
+
shouldOutput = prevSwitch !== node.switch;
|
|
52
|
+
utils.setStatusOK(node, `switch: ${node.switch}`);
|
|
56
53
|
break;
|
|
57
54
|
default:
|
|
58
55
|
if (msg.context.startsWith("in")) {
|
|
59
56
|
const index = parseInt(msg.context.slice(2), 10);
|
|
60
|
-
if (isNaN(index) || index < 1 || index > node.
|
|
61
|
-
|
|
57
|
+
if (isNaN(index) || index < 1 || index > node.slots) {
|
|
58
|
+
utils.setStatusError(node, `invalid input index ${index}`);
|
|
62
59
|
if (done) done();
|
|
63
60
|
return;
|
|
64
61
|
}
|
|
65
62
|
const value = parseFloat(msg.payload);
|
|
66
63
|
if (isNaN(value)) {
|
|
67
|
-
|
|
64
|
+
utils.setStatusError(node, `invalid in${index}`);
|
|
68
65
|
if (done) done();
|
|
69
66
|
return;
|
|
70
67
|
}
|
|
71
|
-
node.
|
|
72
|
-
shouldOutput = index === node.
|
|
73
|
-
node.
|
|
74
|
-
fill: "green",
|
|
75
|
-
shape: "dot",
|
|
76
|
-
text: `in${index}: ${value.toFixed(2)}`
|
|
77
|
-
});
|
|
68
|
+
node.inputs[index - 1] = value;
|
|
69
|
+
shouldOutput = index === node.switch;
|
|
70
|
+
utils.setStatusOK(node, `in${index}: ${value.toFixed(2)}`);
|
|
78
71
|
} else {
|
|
79
|
-
|
|
72
|
+
utils.setStatusWarn(node, "unknown context");
|
|
80
73
|
if (done) done("Unknown context");
|
|
81
74
|
return;
|
|
82
75
|
}
|
|
@@ -85,12 +78,8 @@ module.exports = function(RED) {
|
|
|
85
78
|
|
|
86
79
|
// Output new message if the active slot is updated or switch/slots change affects output
|
|
87
80
|
if (shouldOutput) {
|
|
88
|
-
const out = node.
|
|
89
|
-
node.
|
|
90
|
-
fill: "blue",
|
|
91
|
-
shape: "dot",
|
|
92
|
-
text: `slots: ${node.runtime.slots}, switch: ${node.runtime.switch}, out: ${out.toFixed(2)}`
|
|
93
|
-
});
|
|
81
|
+
const out = node.inputs[node.switch - 1] ?? node.inputs[0];
|
|
82
|
+
utils.setStatusChanged(node, `slots: ${node.slots}, switch: ${node.switch}, out: ${out.toFixed(2)}`);
|
|
94
83
|
send({ payload: out });
|
|
95
84
|
}
|
|
96
85
|
|
package/nodes/and-block.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function AndBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
const node = this;
|
|
@@ -7,60 +9,87 @@ module.exports = function(RED) {
|
|
|
7
9
|
node.inputs = Array(parseInt(config.slots) || 2).fill(false);
|
|
8
10
|
node.slots = parseInt(config.slots);
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
utils.setStatusOK(node, `slots: ${node.slots}`);
|
|
11
13
|
|
|
12
14
|
// Initialize fields
|
|
13
15
|
let lastResult = null;
|
|
14
16
|
let lastInputs = node.inputs.slice();
|
|
17
|
+
let lastOutputTime = 0; // Track last output timestamp for debounce
|
|
18
|
+
let lastOutputValue = undefined; // Track last output value for duplicate suppression
|
|
19
|
+
const DEBOUNCE_MS = 500; // Debounce period in milliseconds
|
|
15
20
|
|
|
16
21
|
node.on("input", function(msg, send, done) {
|
|
17
22
|
send = send || function() { node.send.apply(node, arguments); };
|
|
18
23
|
|
|
19
24
|
// Guard against invalid msg
|
|
20
25
|
if (!msg) {
|
|
21
|
-
|
|
26
|
+
utils.setStatusError(node, "invalid message");
|
|
22
27
|
if (done) done();
|
|
23
28
|
return;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
// Check required properties
|
|
27
32
|
if (!msg.hasOwnProperty("context")) {
|
|
28
|
-
|
|
33
|
+
utils.setStatusError(node, "missing context");
|
|
29
34
|
if (done) done();
|
|
30
35
|
return;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
if (!msg.hasOwnProperty("payload")) {
|
|
34
|
-
|
|
39
|
+
utils.setStatusError(node, "missing payload");
|
|
35
40
|
if (done) done();
|
|
36
41
|
return;
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
// Process input slot
|
|
40
45
|
if (msg.context.startsWith("in")) {
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
node.inputs[index - 1] = Boolean(msg.payload);
|
|
46
|
+
const slotVal = utils.validateSlotIndex(msg.context, node.slots);
|
|
47
|
+
if (slotVal.valid) {
|
|
48
|
+
node.inputs[slotVal.index - 1] = Boolean(msg.payload);
|
|
44
49
|
const result = node.inputs.every(v => v === true);
|
|
45
50
|
const isUnchanged = result === lastResult && node.inputs.every((v, i) => v === lastInputs[i]);
|
|
46
|
-
node.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
const statusText = `in: [${node.inputs.join(", ")}], out: ${result}`;
|
|
52
|
+
|
|
53
|
+
// ================================================================
|
|
54
|
+
// Debounce: Suppress consecutive same outputs within 500ms
|
|
55
|
+
// But always output if value is different or debounce time expired
|
|
56
|
+
// ================================================================
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
const timeSinceLastOutput = now - lastOutputTime;
|
|
59
|
+
const isSameOutput = result === lastOutputValue;
|
|
60
|
+
const shouldSuppress = isSameOutput && timeSinceLastOutput < DEBOUNCE_MS;
|
|
61
|
+
|
|
62
|
+
if (shouldSuppress) {
|
|
63
|
+
// Same output within debounce window - don't send, just update status
|
|
64
|
+
utils.setStatusUnchanged(node, statusText);
|
|
65
|
+
} else {
|
|
66
|
+
// Different output or debounce period expired - send it
|
|
67
|
+
if (isUnchanged) {
|
|
68
|
+
utils.setStatusUnchanged(node, statusText);
|
|
69
|
+
} else {
|
|
70
|
+
utils.setStatusChanged(node, statusText);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Record output for next debounce comparison
|
|
74
|
+
lastOutputTime = now;
|
|
75
|
+
lastOutputValue = result;
|
|
76
|
+
|
|
77
|
+
// Send output to allow all downstream branches to update
|
|
78
|
+
send({ payload: result });
|
|
79
|
+
}
|
|
80
|
+
|
|
51
81
|
lastResult = result;
|
|
52
82
|
lastInputs = node.inputs.slice();
|
|
53
|
-
send({ payload: result });
|
|
54
83
|
if (done) done();
|
|
55
84
|
return;
|
|
56
85
|
} else {
|
|
57
|
-
|
|
86
|
+
utils.setStatusError(node, slotVal.error);
|
|
58
87
|
if (done) done();
|
|
59
88
|
return;
|
|
60
89
|
}
|
|
61
90
|
}
|
|
62
91
|
|
|
63
|
-
|
|
92
|
+
utils.setStatusWarn(node, "unknown context");
|
|
64
93
|
if (done) done();
|
|
65
94
|
});
|
|
66
95
|
|
package/nodes/average-block.js
CHANGED
|
@@ -6,13 +6,12 @@ module.exports = function(RED) {
|
|
|
6
6
|
const node = this;
|
|
7
7
|
|
|
8
8
|
// Initialize runtime state
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
9
|
+
// Initialize state
|
|
10
|
+
node.maxValues = parseInt(config.sampleSize);
|
|
11
|
+
node.values = [], // Queue for rolling window;
|
|
12
|
+
node.lastAvg = null;
|
|
13
|
+
node.minValid = parseFloat(config.minValid);
|
|
14
|
+
node.maxValid = parseFloat(config.maxValid);
|
|
16
15
|
|
|
17
16
|
node.isBusy = false;
|
|
18
17
|
|
|
@@ -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.minValidType)
|
|
48
47
|
? utils.evaluateNodeProperty(config.minValid, config.minValidType, node, msg)
|
|
49
48
|
.then(val => parseFloat(val))
|
|
50
|
-
: Promise.resolve(node.
|
|
49
|
+
: Promise.resolve(node.minValid),
|
|
51
50
|
);
|
|
52
51
|
|
|
53
52
|
evaluations.push(
|
|
54
53
|
utils.requiresEvaluation(config.maxValidType)
|
|
55
54
|
? utils.evaluateNodeProperty(config.maxValid, config.maxValidType, node, msg)
|
|
56
55
|
.then(val => parseFloat(val))
|
|
57
|
-
: Promise.resolve(node.
|
|
56
|
+
: Promise.resolve(node.maxValid),
|
|
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.minValid = results[0];
|
|
63
|
+
if (!isNaN(results[1])) node.maxValid = results[1];
|
|
65
64
|
} catch (err) {
|
|
66
65
|
node.error(`Error evaluating properties: ${err.message}`);
|
|
67
66
|
if (done) done();
|
|
@@ -72,8 +71,8 @@ module.exports = function(RED) {
|
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
// Validate values
|
|
75
|
-
if (isNaN(node.
|
|
76
|
-
|
|
74
|
+
if (isNaN(node.maxValid) || isNaN(node.minValid) || node.maxValid <= node.minValid ) {
|
|
75
|
+
utils.setStatusError(node, `invalid evaluated values ${node.minValid}, ${node.maxValid}`);
|
|
77
76
|
if (done) done();
|
|
78
77
|
return;
|
|
79
78
|
}
|
|
@@ -81,42 +80,43 @@ module.exports = function(RED) {
|
|
|
81
80
|
// Handle configuration messages
|
|
82
81
|
if (msg.hasOwnProperty("context")) {
|
|
83
82
|
if (!msg.hasOwnProperty("payload")) {
|
|
84
|
-
|
|
83
|
+
utils.setStatusError(node, "missing payload");
|
|
85
84
|
if (done) done();
|
|
86
85
|
return;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
switch (msg.context) {
|
|
90
89
|
case "reset":
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const boolVal = utils.validateBoolean(msg.payload);
|
|
91
|
+
if (!boolVal.valid) {
|
|
92
|
+
utils.setStatusError(node, boolVal.error);
|
|
93
93
|
if (done) done();
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
|
-
if (
|
|
97
|
-
node.
|
|
98
|
-
node.
|
|
99
|
-
|
|
96
|
+
if (boolVal.value === true) {
|
|
97
|
+
node.values = [];
|
|
98
|
+
node.lastAvg = null;
|
|
99
|
+
utils.setStatusOK(node, "state reset");
|
|
100
100
|
}
|
|
101
101
|
break;
|
|
102
102
|
|
|
103
103
|
case "sampleSize":
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
104
|
+
const sizeVal = utils.validateIntRange(msg.payload, { min: 1 });
|
|
105
|
+
if (!sizeVal.valid) {
|
|
106
|
+
utils.setStatusError(node, sizeVal.error);
|
|
107
107
|
if (done) done();
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
node.
|
|
110
|
+
node.maxValues = sizeVal.value;
|
|
111
111
|
// Trim values if new window is smaller
|
|
112
|
-
if (node.
|
|
113
|
-
node.
|
|
112
|
+
if (node.values.length > sizeVal.value) {
|
|
113
|
+
node.values = node.values.slice(-sizeVal.value);
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
utils.setStatusOK(node, `window: ${sizeVal.value}`);
|
|
116
116
|
break;
|
|
117
117
|
|
|
118
118
|
default:
|
|
119
|
-
|
|
119
|
+
utils.setStatusWarn(node, "unknown context");
|
|
120
120
|
break;
|
|
121
121
|
}
|
|
122
122
|
if (done) done();
|
|
@@ -125,32 +125,37 @@ module.exports = function(RED) {
|
|
|
125
125
|
|
|
126
126
|
// Check for missing payload
|
|
127
127
|
if (!msg.hasOwnProperty("payload")) {
|
|
128
|
-
|
|
128
|
+
utils.setStatusError(node, "missing payload");
|
|
129
129
|
if (done) done();
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
// Process input
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
136
|
-
|
|
134
|
+
const numVal = utils.validateNumericPayload(msg.payload, { min: node.minValid, max: node.maxValid });
|
|
135
|
+
if (!numVal.valid) {
|
|
136
|
+
utils.setStatusWarn(node, "out of range");
|
|
137
137
|
if (done) done();
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
+
const inputValue = numVal.value;
|
|
140
141
|
|
|
141
142
|
// Update rolling window
|
|
142
|
-
node.
|
|
143
|
-
if (node.
|
|
144
|
-
node.
|
|
143
|
+
node.values.push(inputValue);
|
|
144
|
+
if (node.values.length > node.maxValues) {
|
|
145
|
+
node.values.shift();
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
// Calculate average
|
|
148
|
-
const avg = node.
|
|
149
|
-
const isUnchanged = avg === node.
|
|
149
|
+
const avg = node.values.length ? node.values.reduce((a, b) => a + b, 0) / node.values.length : null;
|
|
150
|
+
const isUnchanged = avg === node.lastAvg;
|
|
150
151
|
|
|
151
152
|
// Send new message
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
if (isUnchanged) {
|
|
154
|
+
utils.setStatusUnchanged(node, `out: ${avg !== null ? avg.toFixed(3) : "null"}`);
|
|
155
|
+
} else {
|
|
156
|
+
utils.setStatusChanged(node, `out: ${avg !== null ? avg.toFixed(3) : "null"}`);
|
|
157
|
+
}
|
|
158
|
+
node.lastAvg = avg;
|
|
154
159
|
send({ payload: avg });
|
|
155
160
|
|
|
156
161
|
if (done) done();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
module.exports = function(RED) {
|
|
2
|
+
const utils = require('./utils')(RED);
|
|
3
|
+
|
|
2
4
|
function BooleanSwitchBlockNode(config) {
|
|
3
5
|
RED.nodes.createNode(this, config);
|
|
4
6
|
const node = this;
|
|
@@ -7,25 +9,21 @@ module.exports = function(RED) {
|
|
|
7
9
|
node.state = config.state;
|
|
8
10
|
|
|
9
11
|
// Set initial status
|
|
10
|
-
|
|
11
|
-
fill: "green",
|
|
12
|
-
shape: "dot",
|
|
13
|
-
text: `state: ${node.state}`
|
|
14
|
-
});
|
|
12
|
+
utils.setStatusOK(node, `state: ${node.state}`);
|
|
15
13
|
|
|
16
14
|
node.on("input", function(msg, send, done) {
|
|
17
15
|
send = send || function() { node.send.apply(node, arguments); };
|
|
18
16
|
|
|
19
17
|
// Guard against invalid message
|
|
20
18
|
if (!msg) {
|
|
21
|
-
|
|
19
|
+
utils.setStatusError(node, "invalid message");
|
|
22
20
|
if (done) done();
|
|
23
21
|
return;
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
// Validate context
|
|
27
25
|
if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
|
|
28
|
-
|
|
26
|
+
utils.setStatusError(node, "missing or invalid context");
|
|
29
27
|
if (done) done();
|
|
30
28
|
return;
|
|
31
29
|
}
|
|
@@ -34,44 +32,28 @@ module.exports = function(RED) {
|
|
|
34
32
|
switch (msg.context) {
|
|
35
33
|
case "toggle":
|
|
36
34
|
node.state = !node.state;
|
|
37
|
-
|
|
38
|
-
fill: "green",
|
|
39
|
-
shape: "dot",
|
|
40
|
-
text: `state: ${node.state}`
|
|
41
|
-
});
|
|
35
|
+
utils.setStatusChanged(node, `state: ${node.state}`);
|
|
42
36
|
send([null, null, { payload: node.state }]);
|
|
43
37
|
break;
|
|
44
38
|
case "switch":
|
|
45
39
|
node.state = !!msg.payload;
|
|
46
|
-
|
|
47
|
-
fill: "green",
|
|
48
|
-
shape: "dot",
|
|
49
|
-
text: `state: ${node.state}`
|
|
50
|
-
});
|
|
40
|
+
utils.setStatusChanged(node, `state: ${node.state}`);
|
|
51
41
|
send([null, null, { payload: node.state }]);
|
|
52
42
|
break;
|
|
53
43
|
case "inTrue":
|
|
54
44
|
if (node.state) {
|
|
55
|
-
|
|
56
|
-
fill: "blue",
|
|
57
|
-
shape: "dot",
|
|
58
|
-
text: `out: ${msg.payload}`
|
|
59
|
-
});
|
|
45
|
+
utils.setStatusOK(node, `out: ${msg.payload}`);
|
|
60
46
|
send([msg, null, { payload: node.state }]);
|
|
61
47
|
}
|
|
62
48
|
break;
|
|
63
49
|
case "inFalse":
|
|
64
50
|
if (!node.state) {
|
|
65
|
-
|
|
66
|
-
fill: "blue",
|
|
67
|
-
shape: "dot",
|
|
68
|
-
text: `out: ${msg.payload}`
|
|
69
|
-
});
|
|
51
|
+
utils.setStatusOK(node, `out: ${msg.payload}`);
|
|
70
52
|
send([null, msg, { payload: node.state }]);
|
|
71
53
|
}
|
|
72
54
|
break;
|
|
73
55
|
default:
|
|
74
|
-
|
|
56
|
+
utils.setStatusWarn(node, "unknown context");
|
|
75
57
|
if (done) done("Unknown context");
|
|
76
58
|
return;
|
|
77
59
|
}
|