@bldgblocks/node-red-contrib-control 0.1.4
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/README.md +43 -0
- package/nodes/accumulate-block.html +71 -0
- package/nodes/accumulate-block.js +104 -0
- package/nodes/add-block.html +67 -0
- package/nodes/add-block.js +97 -0
- package/nodes/analog-switch-block.html +65 -0
- package/nodes/analog-switch-block.js +129 -0
- package/nodes/and-block.html +64 -0
- package/nodes/and-block.js +73 -0
- package/nodes/average-block.html +97 -0
- package/nodes/average-block.js +137 -0
- package/nodes/boolean-switch-block.html +59 -0
- package/nodes/boolean-switch-block.js +88 -0
- package/nodes/boolean-to-number-block.html +59 -0
- package/nodes/boolean-to-number-block.js +45 -0
- package/nodes/cache-block.html +69 -0
- package/nodes/cache-block.js +106 -0
- package/nodes/call-status-block.html +111 -0
- package/nodes/call-status-block.js +274 -0
- package/nodes/changeover-block.html +234 -0
- package/nodes/changeover-block.js +392 -0
- package/nodes/comment-block.html +83 -0
- package/nodes/comment-block.js +53 -0
- package/nodes/compare-block.html +64 -0
- package/nodes/compare-block.js +84 -0
- package/nodes/contextual-label-block.html +67 -0
- package/nodes/contextual-label-block.js +52 -0
- package/nodes/convert-block.html +179 -0
- package/nodes/convert-block.js +289 -0
- package/nodes/count-block.html +57 -0
- package/nodes/count-block.js +92 -0
- package/nodes/debounce-block.html +64 -0
- package/nodes/debounce-block.js +140 -0
- package/nodes/delay-block.html +104 -0
- package/nodes/delay-block.js +180 -0
- package/nodes/divide-block.html +65 -0
- package/nodes/divide-block.js +123 -0
- package/nodes/edge-block.html +71 -0
- package/nodes/edge-block.js +120 -0
- package/nodes/frequency-block.html +55 -0
- package/nodes/frequency-block.js +140 -0
- package/nodes/hysteresis-block.html +131 -0
- package/nodes/hysteresis-block.js +142 -0
- package/nodes/interpolate-block.html +74 -0
- package/nodes/interpolate-block.js +141 -0
- package/nodes/load-sequence-block.html +134 -0
- package/nodes/load-sequence-block.js +272 -0
- package/nodes/max-block.html +76 -0
- package/nodes/max-block.js +103 -0
- package/nodes/memory-block.html +90 -0
- package/nodes/memory-block.js +241 -0
- package/nodes/min-block.html +77 -0
- package/nodes/min-block.js +106 -0
- package/nodes/minmax-block.html +89 -0
- package/nodes/minmax-block.js +119 -0
- package/nodes/modulo-block.html +73 -0
- package/nodes/modulo-block.js +126 -0
- package/nodes/multiply-block.html +63 -0
- package/nodes/multiply-block.js +115 -0
- package/nodes/negate-block.html +55 -0
- package/nodes/negate-block.js +91 -0
- package/nodes/nullify-block.html +111 -0
- package/nodes/nullify-block.js +78 -0
- package/nodes/on-change-block.html +79 -0
- package/nodes/on-change-block.js +191 -0
- package/nodes/oneshot-block.html +96 -0
- package/nodes/oneshot-block.js +169 -0
- package/nodes/or-block.html +64 -0
- package/nodes/or-block.js +73 -0
- package/nodes/pid-block.html +205 -0
- package/nodes/pid-block.js +407 -0
- package/nodes/priority-block.html +66 -0
- package/nodes/priority-block.js +239 -0
- package/nodes/rate-limit-block.html +99 -0
- package/nodes/rate-limit-block.js +221 -0
- package/nodes/round-block.html +73 -0
- package/nodes/round-block.js +89 -0
- package/nodes/saw-tooth-wave-block.html +87 -0
- package/nodes/saw-tooth-wave-block.js +161 -0
- package/nodes/scale-range-block.html +90 -0
- package/nodes/scale-range-block.js +137 -0
- package/nodes/sine-wave-block.html +88 -0
- package/nodes/sine-wave-block.js +142 -0
- package/nodes/subtract-block.html +64 -0
- package/nodes/subtract-block.js +103 -0
- package/nodes/thermistor-block.html +81 -0
- package/nodes/thermistor-block.js +146 -0
- package/nodes/tick-tock-block.html +66 -0
- package/nodes/tick-tock-block.js +110 -0
- package/nodes/time-sequence-block.html +67 -0
- package/nodes/time-sequence-block.js +144 -0
- package/nodes/triangle-wave-block.html +86 -0
- package/nodes/triangle-wave-block.js +154 -0
- package/nodes/tstat-block.html +311 -0
- package/nodes/tstat-block.js +499 -0
- package/nodes/units-block.html +150 -0
- package/nodes/units-block.js +106 -0
- package/package.json +73 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function CacheBlockNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
|
|
6
|
+
// Initialize runtime state
|
|
7
|
+
node.runtime = {
|
|
8
|
+
name: config.name,
|
|
9
|
+
operationMode: config.operationMode,
|
|
10
|
+
cachedMessage: null
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
node.on("input", function(msg, send, done) {
|
|
14
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
15
|
+
|
|
16
|
+
// Guard against invalid message
|
|
17
|
+
if (!msg) {
|
|
18
|
+
node.status({ fill: "red", shape: "ring", text: "invalid message" });
|
|
19
|
+
if (done) done();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Validate context
|
|
24
|
+
if (!msg.hasOwnProperty("context") || typeof msg.context !== "string") {
|
|
25
|
+
node.status({ fill: "red", shape: "ring", text: "missing context" });
|
|
26
|
+
if (done) done();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
switch (msg.context) {
|
|
31
|
+
case "update":
|
|
32
|
+
// Validate payload
|
|
33
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
34
|
+
node.status({ fill: "red", shape: "ring", text: "missing payload" });
|
|
35
|
+
if (done) done();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
node.runtime.cachedMessage = RED.util.cloneMessage(msg);
|
|
40
|
+
node.status({
|
|
41
|
+
fill: "green",
|
|
42
|
+
shape: "dot",
|
|
43
|
+
text: `update: ${typeof msg.payload === "number" ? msg.payload.toFixed(2) : JSON.stringify(msg.payload).slice(0, 20)}`
|
|
44
|
+
});
|
|
45
|
+
if (done) done();
|
|
46
|
+
return;
|
|
47
|
+
case "execute":
|
|
48
|
+
if (node.runtime.cachedMessage === null) {
|
|
49
|
+
node.status({
|
|
50
|
+
fill: "blue",
|
|
51
|
+
shape: "dot",
|
|
52
|
+
text: "execute: null"
|
|
53
|
+
});
|
|
54
|
+
send({ payload: null });
|
|
55
|
+
} else {
|
|
56
|
+
let outputMsg;
|
|
57
|
+
if (node.runtime.operationMode === "clone") {
|
|
58
|
+
outputMsg = RED.util.cloneMessage(node.runtime.cachedMessage);
|
|
59
|
+
} else {
|
|
60
|
+
outputMsg = { payload: node.runtime.cachedMessage.payload };
|
|
61
|
+
}
|
|
62
|
+
node.status({
|
|
63
|
+
fill: "blue",
|
|
64
|
+
shape: "dot",
|
|
65
|
+
text: `execute: ${typeof outputMsg.payload === "number" ? outputMsg.payload.toFixed(2) : JSON.stringify(outputMsg.payload).slice(0, 20)}`
|
|
66
|
+
});
|
|
67
|
+
send(outputMsg);
|
|
68
|
+
}
|
|
69
|
+
if (done) done();
|
|
70
|
+
return;
|
|
71
|
+
case "reset":
|
|
72
|
+
// Validate payload
|
|
73
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
74
|
+
node.status({ fill: "red", shape: "ring", text: "missing payload" });
|
|
75
|
+
if (done) done();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (typeof msg.payload !== "boolean" || !msg.payload) {
|
|
80
|
+
node.status({ fill: "red", shape: "ring", text: "invalid reset" });
|
|
81
|
+
if (done) done();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
node.runtime.cachedMessage = null;
|
|
86
|
+
node.status({
|
|
87
|
+
fill: "green",
|
|
88
|
+
shape: "dot",
|
|
89
|
+
text: "reset"
|
|
90
|
+
});
|
|
91
|
+
if (done) done();
|
|
92
|
+
return;
|
|
93
|
+
default:
|
|
94
|
+
node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
|
|
95
|
+
if (done) done("Unknown context");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
node.on("close", function(done) {
|
|
101
|
+
done();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
RED.nodes.registerType("cache-block", CacheBlockNode);
|
|
106
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<!-- UI Template Section: Defines the edit dialog -->
|
|
2
|
+
<script type="text/html" data-template-name="call-status-block">
|
|
3
|
+
<div class="form-row">
|
|
4
|
+
<label for="node-input-name" title="Display name shown on the canvas"><i class="fa fa-tag"></i> Name</label>
|
|
5
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
6
|
+
</div>
|
|
7
|
+
<div class="form-row">
|
|
8
|
+
<label for="node-input-statusTimeout" title="Time to wait for status response (seconds, positive number)"><i class="fa fa-clock-o"></i> Status Timeout</label>
|
|
9
|
+
<input type="number" id="node-input-statusTimeout" placeholder="30" min="0.01" step="any">
|
|
10
|
+
</div>
|
|
11
|
+
<div class="form-row">
|
|
12
|
+
<label for="node-input-clearDelay" title="Delay before clearing status and alarm after call ends (seconds, positive number)"><i class="fa fa-clock-o"></i> Clear Delay</label>
|
|
13
|
+
<input type="number" id="node-input-clearDelay" placeholder="10" min="0.01" step="any">
|
|
14
|
+
</div>
|
|
15
|
+
<div class="form-row">
|
|
16
|
+
<label for="node-input-normalOff" title="Expected status when call is off (boolean)"><i class="fa fa-power-off"></i> Normal Off</label>
|
|
17
|
+
<input type="checkbox" id="node-input-normalOff" style="width: auto; vertical-align: middle;">
|
|
18
|
+
</div>
|
|
19
|
+
<div class="form-row">
|
|
20
|
+
<label for="node-input-normalOn" title="Expected status when call is on (boolean)"><i class="fa fa-power-off"></i> Normal On</label>
|
|
21
|
+
<input type="checkbox" id="node-input-normalOn" style="width: auto; vertical-align: middle;">
|
|
22
|
+
</div>
|
|
23
|
+
<div class="form-row">
|
|
24
|
+
<label for="node-input-runLostStatus" title="Alarm if status doesn't match normalOn during call (boolean)"><i class="fa fa-exclamation-triangle"></i> Run Lost Status</label>
|
|
25
|
+
<input type="checkbox" id="node-input-runLostStatus" style="width: auto; vertical-align: middle;">
|
|
26
|
+
</div>
|
|
27
|
+
<div class="form-row">
|
|
28
|
+
<label for="node-input-noStatusOnRun" title="Alarm if no status received during call (boolean)"><i class="fa fa-exclamation-triangle"></i> No Status On Run</label>
|
|
29
|
+
<input type="checkbox" id="node-input-noStatusOnRun" style="width: auto; vertical-align: middle;">
|
|
30
|
+
</div>
|
|
31
|
+
<div class="form-row">
|
|
32
|
+
<label for="node-input-runLostStatusMessage" title="Message for run lost status alarm (string)"><i class="fa fa-comment"></i> Run Lost Message</label>
|
|
33
|
+
<input type="text" id="node-input-runLostStatusMessage" placeholder="Status lost during run">
|
|
34
|
+
</div>
|
|
35
|
+
<div class="form-row">
|
|
36
|
+
<label for="node-input-noStatusOnRunMessage" title="Message for no status on run alarm (string)"><i class="fa fa-comment"></i> No Status Message</label>
|
|
37
|
+
<input type="text" id="node-input-noStatusOnRunMessage" placeholder="No status received during run">
|
|
38
|
+
</div>
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<!-- JavaScript Section: Registers the node and handles editor logic -->
|
|
42
|
+
<script type="text/javascript">
|
|
43
|
+
RED.nodes.registerType("call-status-block", {
|
|
44
|
+
category: "control",
|
|
45
|
+
color: "#301934",
|
|
46
|
+
defaults: {
|
|
47
|
+
name: { value: "" },
|
|
48
|
+
statusTimeout: { value: 30, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && parseFloat(v) > 0; } },
|
|
49
|
+
clearDelay: { value: 10, required: true, validate: function(v) { return !isNaN(parseFloat(v)) && parseFloat(v) > 0; } },
|
|
50
|
+
normalOff: { value: false },
|
|
51
|
+
normalOn: { value: true },
|
|
52
|
+
runLostStatus: { value: false },
|
|
53
|
+
noStatusOnRun: { value: true },
|
|
54
|
+
runLostStatusMessage: { value: "Status lost during run" },
|
|
55
|
+
noStatusOnRunMessage: { value: "No status received during run" }
|
|
56
|
+
},
|
|
57
|
+
inputs: 1,
|
|
58
|
+
outputs: 2,
|
|
59
|
+
inputLabels: ["input"],
|
|
60
|
+
outputLabels: ["call", "diagnostics"],
|
|
61
|
+
icon: "font-awesome/fa-phone",
|
|
62
|
+
paletteLabel: "call status",
|
|
63
|
+
label: function() {
|
|
64
|
+
return this.name || "call status";
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<!-- Help Section -->
|
|
70
|
+
<script type="text/markdown" data-help-name="call-status-block">
|
|
71
|
+
Issues calls to equipment with status and alarm diagnostics.
|
|
72
|
+
|
|
73
|
+
### Inputs
|
|
74
|
+
: context (string) : Configuration commands - (`"statusTimeout"`, `"clearDelay"`, `"normalOff"`, `"normalOn"`, `"runLostStatus"`, `"noStatusOnRun"`, `"runLostStatusMessage"`, `"noStatusOnRunMessage"`) or sets status (`"status"`). Unmatched values trigger error.
|
|
75
|
+
: payload (boolean | number | string) : Boolean for call, status, or boolean configs; number for timeouts; string for messages.
|
|
76
|
+
|
|
77
|
+
### Outputs
|
|
78
|
+
: call (boolean) : Current call state.
|
|
79
|
+
: diagnostics (object) : `{ call; boolean, status; boolean, alarm; boolean, alarmMessage; string, timeout; boolean }`.
|
|
80
|
+
|
|
81
|
+
### Properties
|
|
82
|
+
: name (string) : Display name in editor.
|
|
83
|
+
: statusTimeout (number) : Time to wait for status response (seconds, > 0).
|
|
84
|
+
: clearDelay (number) : Delay before clearing status and alarm after call ends (seconds, > 0).
|
|
85
|
+
: normalOff (boolean) : Expected status when call is off.
|
|
86
|
+
: normalOn (boolean) : Expected status when call is on.
|
|
87
|
+
: runLostStatus (boolean) : Alarm if status doesn't match `normalOn` during call.
|
|
88
|
+
: noStatusOnRun (boolean) : Alarm if no status received during call.
|
|
89
|
+
: runLostStatusMessage (string) : Message for run lost status alarm.
|
|
90
|
+
: noStatusOnRunMessage (string) : Message for no status on run alarm.
|
|
91
|
+
|
|
92
|
+
### Details
|
|
93
|
+
Issues a call (`true`) to equipment via output 1 and expects a status response matching `normalOn` within `statusTimeout` seconds.
|
|
94
|
+
|
|
95
|
+
Clears status and alarm after `clearDelay` seconds when call ends (`false`), checking against `normalOff`.
|
|
96
|
+
|
|
97
|
+
Generates alarms for `runLostStatus` (status mismatch during call) or `noStatusOnRun` (no status within `statusTimeout`) with custom messages.
|
|
98
|
+
|
|
99
|
+
Ignores status inputs without an active call.
|
|
100
|
+
|
|
101
|
+
### Status
|
|
102
|
+
- Green (dot): Configuration
|
|
103
|
+
- Blue (dot): Output, no alarm
|
|
104
|
+
- Red (dot): Output with alarm
|
|
105
|
+
- Red (ring): Errors
|
|
106
|
+
- Yellow (ring): Unknown context
|
|
107
|
+
|
|
108
|
+
### References
|
|
109
|
+
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
110
|
+
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
|
111
|
+
</script>
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function CallStatusBlockNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
const node = this;
|
|
5
|
+
|
|
6
|
+
// Initialize runtime state
|
|
7
|
+
node.runtime = {
|
|
8
|
+
name: config.name || "",
|
|
9
|
+
statusTimeout: parseFloat(config.statusTimeout),
|
|
10
|
+
clearDelay: parseFloat(config.clearDelay),
|
|
11
|
+
normalOff: config.normalOff === true,
|
|
12
|
+
normalOn: config.normalOn === true,
|
|
13
|
+
runLostStatus: config.runLostStatus === true,
|
|
14
|
+
noStatusOnRun: config.noStatusOnRun === true,
|
|
15
|
+
runLostStatusMessage: config.runLostStatusMessage,
|
|
16
|
+
noStatusOnRunMessage: config.noStatusOnRunMessage,
|
|
17
|
+
call: false,
|
|
18
|
+
status: false,
|
|
19
|
+
alarm: false,
|
|
20
|
+
alarmMessage: "",
|
|
21
|
+
statusTimer: null,
|
|
22
|
+
clearTimer: null
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Validate initial config
|
|
26
|
+
if (isNaN(node.runtime.statusTimeout) || node.runtime.statusTimeout <= 0) {
|
|
27
|
+
node.runtime.statusTimeout = 30;
|
|
28
|
+
node.status({ fill: "red", shape: "ring", text: "invalid statusTimeout" });
|
|
29
|
+
}
|
|
30
|
+
if (isNaN(node.runtime.clearDelay) || node.runtime.clearDelay <= 0) {
|
|
31
|
+
node.runtime.clearDelay = 10;
|
|
32
|
+
node.status({ fill: "red", shape: "ring", text: "invalid clearDelay" });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
node.on("input", function(msg, send, done) {
|
|
36
|
+
send = send || function() { node.send.apply(node, arguments); };
|
|
37
|
+
|
|
38
|
+
// Guard against invalid message
|
|
39
|
+
if (!msg) {
|
|
40
|
+
node.status({ fill: "red", shape: "ring", text: "invalid message" });
|
|
41
|
+
if (done) done();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Handle context updates
|
|
46
|
+
if (msg.hasOwnProperty("context")) {
|
|
47
|
+
if (!msg.hasOwnProperty("payload")) {
|
|
48
|
+
node.status({ fill: "red", shape: "ring", text: `missing payload for ${msg.context}` });
|
|
49
|
+
if (done) done();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
switch (msg.context) {
|
|
54
|
+
case "statusTimeout":
|
|
55
|
+
case "clearDelay":
|
|
56
|
+
const value = parseFloat(msg.payload);
|
|
57
|
+
if (isNaN(value) || value <= 0) {
|
|
58
|
+
node.status({ fill: "red", shape: "ring", text: `invalid ${msg.context}` });
|
|
59
|
+
if (done) done();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
node.runtime[msg.context] = value;
|
|
63
|
+
if (node.runtime.statusTimer) {
|
|
64
|
+
clearTimeout(node.runtime.statusTimer);
|
|
65
|
+
node.runtime.statusTimer = null;
|
|
66
|
+
if (node.runtime.noStatusOnRun && node.runtime.call) {
|
|
67
|
+
node.runtime.statusTimer = setTimeout(() => {
|
|
68
|
+
if (!node.runtime.status) {
|
|
69
|
+
node.runtime.alarm = true;
|
|
70
|
+
node.runtime.alarmMessage = node.runtime.noStatusOnRunMessage;
|
|
71
|
+
node.status({ fill: "red", shape: "dot", text: `no status on run, alarm: true` });
|
|
72
|
+
send(sendOutputs());
|
|
73
|
+
}
|
|
74
|
+
node.runtime.statusTimer = null;
|
|
75
|
+
}, node.runtime.statusTimeout * 1000);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
node.status({
|
|
79
|
+
fill: "green",
|
|
80
|
+
shape: "dot",
|
|
81
|
+
text: `${msg.context}: ${value.toFixed(2)}`
|
|
82
|
+
});
|
|
83
|
+
if (done) done();
|
|
84
|
+
return;
|
|
85
|
+
case "normalOff":
|
|
86
|
+
case "normalOn":
|
|
87
|
+
case "runLostStatus":
|
|
88
|
+
case "noStatusOnRun":
|
|
89
|
+
if (typeof msg.payload !== "boolean") {
|
|
90
|
+
node.status({ fill: "red", shape: "ring", text: `invalid ${msg.context}` });
|
|
91
|
+
if (done) done();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
node.runtime[msg.context] = msg.payload;
|
|
95
|
+
if (msg.context === "noStatusOnRun" && !msg.payload && node.runtime.statusTimer) {
|
|
96
|
+
clearTimeout(node.runtime.statusTimer);
|
|
97
|
+
node.runtime.statusTimer = null;
|
|
98
|
+
} else if (msg.context === "noStatusOnRun" && msg.payload && node.runtime.call && !node.runtime.status) {
|
|
99
|
+
node.runtime.statusTimer = setTimeout(() => {
|
|
100
|
+
if (!node.runtime.status) {
|
|
101
|
+
node.runtime.alarm = true;
|
|
102
|
+
node.runtime.alarmMessage = node.runtime.noStatusOnRunMessage;
|
|
103
|
+
node.status({ fill: "red", shape: "dot", text: `no status on run, alarm: true` });
|
|
104
|
+
send(sendOutputs());
|
|
105
|
+
}
|
|
106
|
+
node.runtime.statusTimer = null;
|
|
107
|
+
}, node.runtime.statusTimeout * 1000);
|
|
108
|
+
}
|
|
109
|
+
node.status({
|
|
110
|
+
fill: "green",
|
|
111
|
+
shape: "dot",
|
|
112
|
+
text: `${msg.context}: ${msg.payload}`
|
|
113
|
+
});
|
|
114
|
+
send(checkStatusConditions());
|
|
115
|
+
if (done) done();
|
|
116
|
+
return;
|
|
117
|
+
case "runLostStatusMessage":
|
|
118
|
+
case "noStatusOnRunMessage":
|
|
119
|
+
if (typeof msg.payload !== "string") {
|
|
120
|
+
node.status({ fill: "red", shape: "ring", text: `invalid ${msg.context}` });
|
|
121
|
+
if (done) done();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
node.runtime[msg.context] = msg.payload;
|
|
125
|
+
node.status({
|
|
126
|
+
fill: "green",
|
|
127
|
+
shape: "dot",
|
|
128
|
+
text: `${msg.context} set`
|
|
129
|
+
});
|
|
130
|
+
if (node.runtime.alarm && node.runtime.alarmMessage === (msg.context === "runLostStatusMessage" ? node.runtime.noStatusOnRunMessage : node.runtime.runLostStatusMessage)) {
|
|
131
|
+
node.runtime.alarmMessage = msg.payload;
|
|
132
|
+
send(sendOutputs());
|
|
133
|
+
}
|
|
134
|
+
if (done) done();
|
|
135
|
+
return;
|
|
136
|
+
case "status":
|
|
137
|
+
if (typeof msg.payload !== "boolean") {
|
|
138
|
+
node.status({ fill: "red", shape: "ring", text: "invalid status" });
|
|
139
|
+
if (done) done();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!node.runtime.call) {
|
|
143
|
+
node.status({ fill: "red", shape: "ring", text: "status ignored" });
|
|
144
|
+
if (done) done();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
node.runtime.status = msg.payload;
|
|
148
|
+
if (node.runtime.status && node.runtime.statusTimer) {
|
|
149
|
+
clearTimeout(node.runtime.statusTimer);
|
|
150
|
+
node.runtime.statusTimer = null;
|
|
151
|
+
node.runtime.alarm = false;
|
|
152
|
+
node.runtime.alarmMessage = "";
|
|
153
|
+
}
|
|
154
|
+
send(checkStatusConditions());
|
|
155
|
+
if (done) done();
|
|
156
|
+
return;
|
|
157
|
+
default:
|
|
158
|
+
node.status({ fill: "yellow", shape: "ring", text: "unknown context" });
|
|
159
|
+
if (done) done("Unknown context");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Validate call input
|
|
165
|
+
if (typeof msg.payload !== "boolean") {
|
|
166
|
+
node.status({ fill: "red", shape: "ring", text: "invalid call" });
|
|
167
|
+
if (done) done();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Process call change
|
|
172
|
+
if (msg.payload !== node.runtime.call) {
|
|
173
|
+
node.runtime.call = msg.payload;
|
|
174
|
+
|
|
175
|
+
// Clear existing timers
|
|
176
|
+
if (node.runtime.statusTimer) {
|
|
177
|
+
clearTimeout(node.runtime.statusTimer);
|
|
178
|
+
node.runtime.statusTimer = null;
|
|
179
|
+
}
|
|
180
|
+
if (node.runtime.clearTimer) {
|
|
181
|
+
clearTimeout(node.runtime.clearTimer);
|
|
182
|
+
node.runtime.clearTimer = null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (node.runtime.call) {
|
|
186
|
+
// Start call: reset status and alarm, set timeout
|
|
187
|
+
node.runtime.status = false;
|
|
188
|
+
node.runtime.alarm = false;
|
|
189
|
+
node.runtime.alarmMessage = "";
|
|
190
|
+
if (node.runtime.noStatusOnRun) {
|
|
191
|
+
node.runtime.statusTimer = setTimeout(() => {
|
|
192
|
+
if (!node.runtime.status) {
|
|
193
|
+
node.runtime.alarm = true;
|
|
194
|
+
node.runtime.alarmMessage = node.runtime.noStatusOnRunMessage;
|
|
195
|
+
node.status({ fill: "red", shape: "dot", text: `no status on run, alarm: true` });
|
|
196
|
+
send(sendOutputs());
|
|
197
|
+
}
|
|
198
|
+
node.runtime.statusTimer = null;
|
|
199
|
+
}, node.runtime.statusTimeout * 1000);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
// Stop call: schedule status and alarm clear
|
|
203
|
+
node.runtime.clearTimer = setTimeout(() => {
|
|
204
|
+
node.runtime.status = false;
|
|
205
|
+
node.runtime.alarm = false;
|
|
206
|
+
node.runtime.alarmMessage = "";
|
|
207
|
+
send(sendOutputs());
|
|
208
|
+
updateStatus();
|
|
209
|
+
node.runtime.clearTimer = null;
|
|
210
|
+
}, node.runtime.clearDelay * 1000);
|
|
211
|
+
}
|
|
212
|
+
send(checkStatusConditions());
|
|
213
|
+
} else {
|
|
214
|
+
updateStatus();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (done) done();
|
|
218
|
+
|
|
219
|
+
function checkStatusConditions() {
|
|
220
|
+
if (node.runtime.call && node.runtime.runLostStatus) {
|
|
221
|
+
if (node.runtime.status !== node.runtime.normalOn) {
|
|
222
|
+
node.runtime.alarm = true;
|
|
223
|
+
node.runtime.alarmMessage = node.runtime.runLostStatusMessage;
|
|
224
|
+
node.status({ fill: "red", shape: "dot", text: `run lost, alarm: true` });
|
|
225
|
+
return sendOutputs();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (!node.runtime.call && node.runtime.status !== node.runtime.normalOff) {
|
|
229
|
+
node.runtime.alarm = true;
|
|
230
|
+
node.runtime.alarmMessage = "Status mismatch when off";
|
|
231
|
+
node.status({ fill: "red", shape: "dot", text: `off mismatch, alarm: true` });
|
|
232
|
+
return sendOutputs();
|
|
233
|
+
}
|
|
234
|
+
if (!node.runtime.alarm || (node.runtime.status === node.runtime.normalOn && node.runtime.call) || (node.runtime.status === node.runtime.normalOff && !node.runtime.call)) {
|
|
235
|
+
node.runtime.alarm = false;
|
|
236
|
+
node.runtime.alarmMessage = "";
|
|
237
|
+
}
|
|
238
|
+
updateStatus();
|
|
239
|
+
return sendOutputs();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function sendOutputs() {
|
|
243
|
+
return [
|
|
244
|
+
{ payload: node.runtime.call },
|
|
245
|
+
{
|
|
246
|
+
payload: {
|
|
247
|
+
call: node.runtime.call,
|
|
248
|
+
status: node.runtime.status,
|
|
249
|
+
alarm: node.runtime.alarm,
|
|
250
|
+
alarmMessage: node.runtime.alarmMessage,
|
|
251
|
+
timeout: !!node.runtime.statusTimer
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function updateStatus() {
|
|
258
|
+
node.status({
|
|
259
|
+
fill: node.runtime.alarm ? "red" : "blue",
|
|
260
|
+
shape: "dot",
|
|
261
|
+
text: `call: ${node.runtime.call}, status: ${node.runtime.status}, alarm: ${node.runtime.alarm}`
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
node.on("close", function(done) {
|
|
267
|
+
if (node.runtime.statusTimer) clearTimeout(node.runtime.statusTimer);
|
|
268
|
+
if (node.runtime.clearTimer) clearTimeout(node.runtime.clearTimer);
|
|
269
|
+
done();
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
RED.nodes.registerType("call-status-block", CallStatusBlockNode);
|
|
274
|
+
};
|