@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.
Files changed (98) hide show
  1. package/README.md +43 -0
  2. package/nodes/accumulate-block.html +71 -0
  3. package/nodes/accumulate-block.js +104 -0
  4. package/nodes/add-block.html +67 -0
  5. package/nodes/add-block.js +97 -0
  6. package/nodes/analog-switch-block.html +65 -0
  7. package/nodes/analog-switch-block.js +129 -0
  8. package/nodes/and-block.html +64 -0
  9. package/nodes/and-block.js +73 -0
  10. package/nodes/average-block.html +97 -0
  11. package/nodes/average-block.js +137 -0
  12. package/nodes/boolean-switch-block.html +59 -0
  13. package/nodes/boolean-switch-block.js +88 -0
  14. package/nodes/boolean-to-number-block.html +59 -0
  15. package/nodes/boolean-to-number-block.js +45 -0
  16. package/nodes/cache-block.html +69 -0
  17. package/nodes/cache-block.js +106 -0
  18. package/nodes/call-status-block.html +111 -0
  19. package/nodes/call-status-block.js +274 -0
  20. package/nodes/changeover-block.html +234 -0
  21. package/nodes/changeover-block.js +392 -0
  22. package/nodes/comment-block.html +83 -0
  23. package/nodes/comment-block.js +53 -0
  24. package/nodes/compare-block.html +64 -0
  25. package/nodes/compare-block.js +84 -0
  26. package/nodes/contextual-label-block.html +67 -0
  27. package/nodes/contextual-label-block.js +52 -0
  28. package/nodes/convert-block.html +179 -0
  29. package/nodes/convert-block.js +289 -0
  30. package/nodes/count-block.html +57 -0
  31. package/nodes/count-block.js +92 -0
  32. package/nodes/debounce-block.html +64 -0
  33. package/nodes/debounce-block.js +140 -0
  34. package/nodes/delay-block.html +104 -0
  35. package/nodes/delay-block.js +180 -0
  36. package/nodes/divide-block.html +65 -0
  37. package/nodes/divide-block.js +123 -0
  38. package/nodes/edge-block.html +71 -0
  39. package/nodes/edge-block.js +120 -0
  40. package/nodes/frequency-block.html +55 -0
  41. package/nodes/frequency-block.js +140 -0
  42. package/nodes/hysteresis-block.html +131 -0
  43. package/nodes/hysteresis-block.js +142 -0
  44. package/nodes/interpolate-block.html +74 -0
  45. package/nodes/interpolate-block.js +141 -0
  46. package/nodes/load-sequence-block.html +134 -0
  47. package/nodes/load-sequence-block.js +272 -0
  48. package/nodes/max-block.html +76 -0
  49. package/nodes/max-block.js +103 -0
  50. package/nodes/memory-block.html +90 -0
  51. package/nodes/memory-block.js +241 -0
  52. package/nodes/min-block.html +77 -0
  53. package/nodes/min-block.js +106 -0
  54. package/nodes/minmax-block.html +89 -0
  55. package/nodes/minmax-block.js +119 -0
  56. package/nodes/modulo-block.html +73 -0
  57. package/nodes/modulo-block.js +126 -0
  58. package/nodes/multiply-block.html +63 -0
  59. package/nodes/multiply-block.js +115 -0
  60. package/nodes/negate-block.html +55 -0
  61. package/nodes/negate-block.js +91 -0
  62. package/nodes/nullify-block.html +111 -0
  63. package/nodes/nullify-block.js +78 -0
  64. package/nodes/on-change-block.html +79 -0
  65. package/nodes/on-change-block.js +191 -0
  66. package/nodes/oneshot-block.html +96 -0
  67. package/nodes/oneshot-block.js +169 -0
  68. package/nodes/or-block.html +64 -0
  69. package/nodes/or-block.js +73 -0
  70. package/nodes/pid-block.html +205 -0
  71. package/nodes/pid-block.js +407 -0
  72. package/nodes/priority-block.html +66 -0
  73. package/nodes/priority-block.js +239 -0
  74. package/nodes/rate-limit-block.html +99 -0
  75. package/nodes/rate-limit-block.js +221 -0
  76. package/nodes/round-block.html +73 -0
  77. package/nodes/round-block.js +89 -0
  78. package/nodes/saw-tooth-wave-block.html +87 -0
  79. package/nodes/saw-tooth-wave-block.js +161 -0
  80. package/nodes/scale-range-block.html +90 -0
  81. package/nodes/scale-range-block.js +137 -0
  82. package/nodes/sine-wave-block.html +88 -0
  83. package/nodes/sine-wave-block.js +142 -0
  84. package/nodes/subtract-block.html +64 -0
  85. package/nodes/subtract-block.js +103 -0
  86. package/nodes/thermistor-block.html +81 -0
  87. package/nodes/thermistor-block.js +146 -0
  88. package/nodes/tick-tock-block.html +66 -0
  89. package/nodes/tick-tock-block.js +110 -0
  90. package/nodes/time-sequence-block.html +67 -0
  91. package/nodes/time-sequence-block.js +144 -0
  92. package/nodes/triangle-wave-block.html +86 -0
  93. package/nodes/triangle-wave-block.js +154 -0
  94. package/nodes/tstat-block.html +311 -0
  95. package/nodes/tstat-block.js +499 -0
  96. package/nodes/units-block.html +150 -0
  97. package/nodes/units-block.js +106 -0
  98. 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
+ };