node-red-contrib-boolean-logic-ultimate 1.0.63 → 1.1.0

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.
@@ -1,339 +1,489 @@
1
1
  module.exports = function (RED) {
2
- function BooleanLogicUltimate(config) {
3
- RED.nodes.createNode(this, config);
4
- var node = this;
5
- var fs = require("fs");
6
- var path = require("path");
7
-
8
- node.config = config;
9
- node.jSonStates = {}; // JSON object containing the states.
10
- node.sInitializeWith = typeof node.config.sInitializeWith === "undefined" ? "WaitForPayload" : node.config.sInitializeWith;
11
- node.persistPath = path.join(RED.settings.userDir, "booleanlogicultimatepersist"); // 26/10/2020 Contains the path for the states dir.
12
- node.restrictinputevaluation = config.restrictinputevaluation === undefined ? false : config.restrictinputevaluation;
13
- node.delayEvaluation = config.delayEvaluation === undefined ? 0 : config.delayEvaluation; // 26/01/2022 Starts evaluating the inputs only after this amount of time is elapsed, after the last msg input or trigger
14
- if (isNaN(parseInt(node.delayEvaluation)) || parseInt(node.delayEvaluation) < 0) node.delayEvaluation = 0;
15
- if (typeof node.delayEvaluation === "string") node.delayEvaluation = parseInt(node.delayEvaluation);
16
- node.timerDelayEvaluation = null;
17
- node.inputMessage = {}; // 26/01/2022 input message is stored here.
18
-
19
- function setNodeStatus({ fill, shape, text }) {
20
- let dDate = new Date();
21
- node.status({ fill: fill, shape: shape, text: text + " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" })
22
- }
23
-
24
- // Helper for the config html, to be able to delete the peristent states file
25
- RED.httpAdmin.get("/stateoperation_delete", RED.auth.needsPermission('BooleanLogicUltimate.read'), function (req, res) {
26
- //node.send({ req: req });
27
- // Detele the persist file
28
- //var _node = RED.nodes.getNode(req.query.nodeid); // Gets node object from nodeit, because when called from the config html, the node object is not defined
29
- var _nodeid = req.query.nodeid;
30
- try {
31
- if (fs.existsSync(path.join(node.persistPath, _nodeid.toString()))) fs.unlinkSync(path.join(node.persistPath, _nodeid.toString()));
32
- } catch (error) {
33
- }
34
- res.json({ status: 220 });
35
- });
36
-
37
- // 26/10/2020 Check for path and create it if doens't exists
38
- if (!fs.existsSync(node.persistPath)) {
39
- // Create the path
40
- try {
41
- fs.mkdirSync(node.persistPath);
42
- // Backward compatibility: Copy old states dir into the new folder
43
- if (fs.existsSync("states")) {
44
- var filenames = fs.readdirSync("states");
45
- filenames.forEach(file => {
46
- RED.log.info("BooleanLogicUltimate: migrating from old states path to the new persist " + file);
47
- fs.copyFileSync("states/" + file, path.join(node.persistPath, path.basename(file)));
48
- });
49
- }
50
- } catch (error) {
51
- RED.log.error("BooleanLogicUltimate: error creating persistent folder. Check user permission to write to the filesystem " + error.message);
52
- }
53
- }
54
-
55
- // Populate the state array with the persisten file
56
- if (node.config.persist == true) {
57
- try {
58
- var contents = fs.readFileSync(path.join(node.persistPath, node.id.toString())).toString();
59
- if (typeof contents !== 'undefined') {
60
- node.jSonStates = JSON.parse(contents);
61
- setNodeStatus({ fill: "blue", shape: "ring", text: "Loaded persistent states (" + Object.keys(node.jSonStates).length + " total)." });
62
- }
63
- } catch (error) {
64
- setNodeStatus({ fill: "grey", shape: "ring", text: "No persistent states" });
65
- }
66
-
67
- } else {
68
- setNodeStatus({ fill: "yellow", shape: "dot", text: "Waiting for input states" });
69
- }
70
-
71
- // Starts the evaluation delay timer, if needed
72
- node.startTimerDelayEvaluation = () => {
73
- if (node.timerDelayEvaluation !== null) clearTimeout(node.timerDelayEvaluation);
74
- node.timerDelayEvaluation = setTimeout(() => {
75
- outputResult();
76
- }, node.delayEvaluation);
77
- }
78
-
79
- // 14/08/2019 If some inputs are to be initialized, create a dummy items in the array
80
- initUndefinedInputs();
81
-
82
-
83
- this.on('input', function (msg) {
84
-
85
- // 21/04/2021 Msg to reset all inputs
86
- if (msg.hasOwnProperty("reset")) {
87
- setNodeStatus({ fill: "blue", shape: "ring", text: "All inputs have been reset." });
88
- node.jSonStates = [];
89
- return;
90
- }
91
-
92
- // 26/01/2023 you can change the input count from msg
93
- if (msg.hasOwnProperty("inputcount")) {
94
- setTimeout(() => {
95
- setNodeStatus({ fill: "grey", shape: "dot", text: "Input count changed to " + msg.inputcount });
96
- }, 500);
97
- try {
98
- node.config.inputCount = Number(msg.inputcount);
99
- } catch (error) {
100
- }
101
-
102
- }
103
-
104
- // 15/11/2021 inform user about undefined topic or payload
105
- if (!msg.hasOwnProperty("topic") || msg.topic === undefined || msg.topic === null) {
106
- setNodeStatus({ fill: "red", shape: "dot", text: "Received invalid topic!" });
107
- return;
108
- }
109
-
110
- var topic = msg.topic;
111
- const utils = require("./utils.js");
112
- let sPayload = utils.fetchFromObject(msg, config.payloadPropName || "payload");
113
-
114
- // 12/08/2021 Restrict only to true/false
115
- if (node.restrictinputevaluation) {
116
- if (sPayload !== true && sPayload !== false) {
117
- setNodeStatus({ fill: "red", shape: "dot", text: "Received non boolean value from " + msg.topic });
118
- return;
119
- }
120
- }
121
-
122
- var value = utils.ToBoolean(sPayload);
123
-
124
- // 15/11/2021 inform user about undefined topic or payload
125
- if (sPayload === undefined) {
126
- setNodeStatus({ fill: "red", shape: "dot", text: "Received invalid payload from " + msg.topic || "" });
127
- return;
128
- }
129
-
130
- // 14/08/2019 if inputs are initialized, remove a "dummy" item from the state's array, as soon as new topic arrives
131
- if (node.sInitializeWith !== "WaitForPayload") {
132
- // Search if the current topic is in the state array
133
- if (typeof node.jSonStates[topic] === "undefined") {
134
- // Delete one dummy
135
- for (let index = 0; index < node.config.inputCount; index++) {
136
- if (node.jSonStates.hasOwnProperty("dummy" + index)) {
137
- //RED.log.info(JSON.stringify(node.jSonStates))
138
- delete node.jSonStates["dummy" + index];
139
- //RED.log.info(JSON.stringify(node.jSonStates))
140
- break;
141
- }
142
- }
143
- }
144
- }
145
-
146
- // Add current attribute
147
- node.jSonStates[topic] = value;
148
-
149
- // Save the state array to a perisistent file
150
- if (node.config.persist == true) {
151
- try {
152
- fs.writeFileSync(path.join(node.persistPath, node.id.toString()), JSON.stringify(node.jSonStates));
153
- } catch (error) {
154
- setNodeStatus({ fill: "red", shape: "dot", text: "Node cannot write to filesystem: " + error.message });
155
- RED.log.error("BooleanLogicUltimate: unable to write to the filesystem. Check wether the user running node-red, has write permission to the filesysten. " + error.message);
156
- }
157
- }
158
- node.inputMessage = msg; // 26/01/2022 Store MSG to be used in the outputResult function.
159
-
160
- // Do we have as many inputs as we expect?
161
- var keyCount = Object.keys(node.jSonStates).length;
162
- if (keyCount == node.config.inputCount) {
163
-
164
- // var resAND = CalculateResult("AND");
165
- // var resOR = CalculateResult("OR");
166
- // var resXOR = CalculateResult("XOR");
167
-
168
- // if (node.config.filtertrue == "onlytrue") {
169
- // if (!resAND) { resAND = null };
170
- // if (!resOR) { resOR = null };
171
- // if (!resXOR) { resXOR = null };
172
- // }
173
-
174
- // Operation mode evaluation
175
- if (node.config.outputtriggeredby == "onlyonetopic") {
176
- if (typeof node.config.triggertopic !== "undefined"
177
- && node.config.triggertopic !== ""
178
- && msg.hasOwnProperty("topic") && msg.topic !== ""
179
- && node.config.triggertopic === msg.topic) {
180
- if (node.delayEvaluation > 0) {
181
- node.startTimerDelayEvaluation();
182
- setNodeStatus({ fill: "blue", shape: "ring", text: "Delay Eval " + node.delayEvaluation + "ms" });
183
- } else {
184
- outputResult();
185
- }
186
- } else {
187
- setNodeStatus({ fill: "grey", shape: "ring", text: "Saved (" + (msg.hasOwnProperty("topic") ? msg.topic : "empty input topic") + ") " + value });
188
- }
189
- } else {
190
- if (node.delayEvaluation > 0) {
191
- node.startTimerDelayEvaluation();
192
- setNodeStatus({ fill: "blue", shape: "ring", text: "Delay Eval " + node.delayEvaluation + "ms" });
193
- } else {
194
- outputResult();
195
- }
196
- }
197
- }
198
- else if (keyCount > node.config.inputCount) {
199
- setNodeStatus({ fill: "gray", shape: "ring", text: "Reset due to unexpected new topic" });
200
- DeletePersistFile();
201
- } else {
202
- setNodeStatus({ fill: "green", shape: "ring", text: "Arrived topic " + keyCount + " of " + node.config.inputCount });
203
- }
204
-
205
- });
206
-
207
- this.on('close', function (removed, done) {
208
- if (removed) {
209
- // This node has been deleted
210
- // Delete persistent states on change/deploy
211
- DeletePersistFile();
212
- } else {
213
- // This node is being restarted
214
- }
215
- done();
216
- });
217
-
218
- function DeletePersistFile() {
219
- // Detele the persist file
220
- try {
221
- if (fs.existsSync(path.join(node.persistPath, node.id.toString()))) fs.unlinkSync(path.join(node.persistPath, node.id.toString()));
222
- setNodeStatus({ fill: "red", shape: "ring", text: "Persistent states deleted (" + node.id.toString() + ")." });
223
- } catch (error) {
224
- setNodeStatus({ fill: "red", shape: "ring", text: "Error deleting persistent file: " + error.toString() });
225
- }
226
- node.jSonStates = {}; // Resets inputs
227
- // 14/08/2019 If the inputs are to be initialized, create a dummy items in the array
228
- initUndefinedInputs();
229
- }
230
-
231
- function initUndefinedInputs() {
232
- if (node.sInitializeWith !== "WaitForPayload") {
233
- var nTotalDummyToCreate = Number(node.config.inputCount) - Object.keys(node.jSonStates).length;
234
- if (nTotalDummyToCreate > 0) {
235
- RED.log.info("BooleanLogicUltimate: Will create " + nTotalDummyToCreate + " dummy (" + node.sInitializeWith + ") values")
236
- for (let index = 0; index < nTotalDummyToCreate; index++) {
237
- node.jSonStates["dummy" + index] = node.sInitializeWith === "false" ? false : true;
238
- }
239
- let t = setTimeout(() => { setNodeStatus({ fill: "green", shape: "ring", text: "Initialized " + nTotalDummyToCreate + " undefined inputs with " + node.sInitializeWith }); }, 4000)
240
- }
241
- }
242
- }
243
-
244
-
245
-
246
- function CalculateResult(_operation) {
247
- var res;
248
-
249
- if (_operation == "XOR") {
250
- res = PerformXOR();
251
- }
252
- else {
253
- // We need a starting value to perform AND and OR operations.
254
- var keys = Object.keys(node.jSonStates);
255
- res = node.jSonStates[keys[0]];
256
-
257
- for (var i = 1; i < keys.length; ++i) {
258
- var key = keys[i];
259
- res = PerformSimpleOperation(_operation, res, node.jSonStates[key]);
260
- }
261
- }
262
-
263
- return res;
264
- }
265
-
266
- function PerformXOR() {
267
- // XOR = exclusively one input is true. As such, we just count the number of true values and compare to 1.
268
- var trueCount = 0;
269
-
270
- for (var key in node.jSonStates) {
271
- if (node.jSonStates[key]) {
272
- trueCount++;
273
- }
274
- }
275
-
276
- return trueCount == 1;
277
- }
278
-
279
- function PerformSimpleOperation(operation, val1, val2) {
280
- var res;
281
-
282
- if (operation === "AND") {
283
- res = val1 && val2;
284
- }
285
- else if (operation === "OR") {
286
- res = val1 || val2;
287
- }
288
- else {
289
- node.error("Unknown operation: " + operation);
290
- }
291
-
292
- return res;
293
- }
294
-
295
-
296
-
297
- function outputResult() {
298
- let optionalTopic = node.config.topic;
299
- let calculatedValueAND = CalculateResult("AND");
300
- let calculatedValueOR = CalculateResult("OR");
301
- let calculatedValueXOR = CalculateResult("XOR");
302
-
303
- if (node.config.filtertrue == "onlytrue") {
304
- if (!calculatedValueAND) { calculatedValueAND = null };
305
- if (!calculatedValueOR) { calculatedValueOR = null };
306
- if (!calculatedValueXOR) { calculatedValueXOR = null };
307
- }
308
-
309
- setNodeStatus({ fill: "green", shape: "dot", text: "(AND)" + (calculatedValueAND !== null ? calculatedValueAND : "---") + " (OR)" + (calculatedValueOR !== null ? calculatedValueOR : "---") + " (XOR)" + (calculatedValueXOR !== null ? calculatedValueXOR : "---") });
310
-
311
- var msgAND = null;
312
- if (calculatedValueAND != null) {
313
- msgAND = RED.util.cloneMessage(node.inputMessage);
314
- msgAND.topic = optionalTopic === undefined ? "result" : optionalTopic;
315
- msgAND.operation = "AND";
316
- msgAND.payload = calculatedValueAND;
317
-
318
- }
319
- var msgOR = null;
320
- if (calculatedValueOR != null) {
321
- msgOR = RED.util.cloneMessage(node.inputMessage);
322
- msgOR.topic = optionalTopic === undefined ? "result" : optionalTopic;
323
- msgOR.operation = "OR";
324
- msgOR.payload = calculatedValueOR;
325
- }
326
- var msgXOR = null;
327
- if (calculatedValueXOR != null) {
328
- msgXOR = RED.util.cloneMessage(node.inputMessage);
329
- msgXOR.topic = optionalTopic === undefined ? "result" : optionalTopic;
330
- msgXOR.operation = "XOR";
331
- msgXOR.payload = calculatedValueXOR;
332
- }
333
- node.send([msgAND, msgOR, msgXOR]);
334
-
335
- };
336
- }
337
-
338
- RED.nodes.registerType("BooleanLogicUltimate", BooleanLogicUltimate);
339
- }
2
+ function BooleanLogicUltimate(config) {
3
+ RED.nodes.createNode(this, config);
4
+ var node = this;
5
+ var fs = require("fs");
6
+ var path = require("path");
7
+
8
+ node.config = config;
9
+ node.jSonStates = {}; // JSON object containing the states.
10
+ node.sInitializeWith =
11
+ typeof node.config.sInitializeWith === "undefined"
12
+ ? "WaitForPayload"
13
+ : node.config.sInitializeWith;
14
+ node.persistPath = path.join(
15
+ RED.settings.userDir,
16
+ "booleanlogicultimatepersist"
17
+ ); // 26/10/2020 Contains the path for the states dir.
18
+ node.restrictinputevaluation =
19
+ config.restrictinputevaluation === undefined
20
+ ? false
21
+ : config.restrictinputevaluation;
22
+ node.delayEvaluation =
23
+ config.delayEvaluation === undefined ? 0 : config.delayEvaluation; // 26/01/2022 Starts evaluating the inputs only after this amount of time is elapsed, after the last msg input or trigger
24
+ if (
25
+ isNaN(parseInt(node.delayEvaluation)) ||
26
+ parseInt(node.delayEvaluation) < 0
27
+ )
28
+ node.delayEvaluation = 0;
29
+ if (typeof node.delayEvaluation === "string")
30
+ node.delayEvaluation = parseInt(node.delayEvaluation);
31
+ node.timerDelayEvaluation = null;
32
+ node.inputMessage = {}; // 26/01/2022 input message is stored here.
33
+
34
+ function setNodeStatus({ fill, shape, text }) {
35
+ let dDate = new Date();
36
+ node.status({
37
+ fill: fill,
38
+ shape: shape,
39
+ text:
40
+ text +
41
+ " (" +
42
+ dDate.getDate() +
43
+ ", " +
44
+ dDate.toLocaleTimeString() +
45
+ ")",
46
+ });
47
+ }
48
+
49
+ // Helper for the config html, to be able to delete the peristent states file
50
+ RED.httpAdmin.get(
51
+ "/stateoperation_delete",
52
+ RED.auth.needsPermission("BooleanLogicUltimate.read"),
53
+ function (req, res) {
54
+ //node.send({ req: req });
55
+ // Detele the persist file
56
+ //var _node = RED.nodes.getNode(req.query.nodeid); // Gets node object from nodeit, because when called from the config html, the node object is not defined
57
+ var _nodeid = req.query.nodeid;
58
+ try {
59
+ if (fs.existsSync(path.join(node.persistPath, _nodeid.toString())))
60
+ fs.unlinkSync(path.join(node.persistPath, _nodeid.toString()));
61
+ } catch (error) {}
62
+ res.json({ status: 220 });
63
+ }
64
+ );
65
+
66
+ // 26/10/2020 Check for path and create it if doens't exists
67
+ if (!fs.existsSync(node.persistPath)) {
68
+ // Create the path
69
+ try {
70
+ fs.mkdirSync(node.persistPath);
71
+ // Backward compatibility: Copy old states dir into the new folder
72
+ if (fs.existsSync("states")) {
73
+ var filenames = fs.readdirSync("states");
74
+ filenames.forEach((file) => {
75
+ RED.log.info(
76
+ "BooleanLogicUltimate: migrating from old states path to the new persist " +
77
+ file
78
+ );
79
+ fs.copyFileSync(
80
+ "states/" + file,
81
+ path.join(node.persistPath, path.basename(file))
82
+ );
83
+ });
84
+ }
85
+ } catch (error) {
86
+ RED.log.error(
87
+ "BooleanLogicUltimate: error creating persistent folder. Check user permission to write to the filesystem " +
88
+ error.message
89
+ );
90
+ }
91
+ }
92
+
93
+ // Populate the state array with the persisten file
94
+ if (node.config.persist == true) {
95
+ try {
96
+ var contents = fs
97
+ .readFileSync(path.join(node.persistPath, node.id.toString()))
98
+ .toString();
99
+ if (typeof contents !== "undefined") {
100
+ node.jSonStates = JSON.parse(contents);
101
+ setNodeStatus({
102
+ fill: "blue",
103
+ shape: "ring",
104
+ text:
105
+ "Loaded persistent states (" +
106
+ Object.keys(node.jSonStates).length +
107
+ " total).",
108
+ });
109
+ }
110
+ } catch (error) {
111
+ setNodeStatus({
112
+ fill: "grey",
113
+ shape: "ring",
114
+ text: "No persistent states",
115
+ });
116
+ }
117
+ } else {
118
+ setNodeStatus({
119
+ fill: "yellow",
120
+ shape: "dot",
121
+ text: "Waiting for input states",
122
+ });
123
+ }
124
+
125
+ // Starts the evaluation delay timer, if needed
126
+ node.startTimerDelayEvaluation = () => {
127
+ if (node.timerDelayEvaluation !== null)
128
+ clearTimeout(node.timerDelayEvaluation);
129
+ node.timerDelayEvaluation = setTimeout(() => {
130
+ outputResult();
131
+ }, node.delayEvaluation);
132
+ };
133
+
134
+ // 14/08/2019 If some inputs are to be initialized, create a dummy items in the array
135
+ initUndefinedInputs();
136
+
137
+ this.on("input", function (msg) {
138
+ // 21/04/2021 Msg to reset all inputs
139
+ if (msg.hasOwnProperty("reset")) {
140
+ setNodeStatus({
141
+ fill: "blue",
142
+ shape: "ring",
143
+ text: "All inputs have been reset.",
144
+ });
145
+ node.jSonStates = [];
146
+ return;
147
+ }
148
+
149
+ // 26/01/2023 you can change the input count from msg
150
+ if (msg.hasOwnProperty("inputcount")) {
151
+ setTimeout(() => {
152
+ setNodeStatus({
153
+ fill: "grey",
154
+ shape: "dot",
155
+ text: "Input count changed to " + msg.inputcount,
156
+ });
157
+ }, 500);
158
+ try {
159
+ node.config.inputCount = Number(msg.inputcount);
160
+ } catch (error) {}
161
+ }
162
+
163
+ // 15/11/2021 inform user about undefined topic or payload
164
+ if (
165
+ !msg.hasOwnProperty("topic") ||
166
+ msg.topic === undefined ||
167
+ msg.topic === null
168
+ ) {
169
+ setNodeStatus({
170
+ fill: "red",
171
+ shape: "dot",
172
+ text: "Received invalid topic!",
173
+ });
174
+ return;
175
+ }
176
+
177
+ var topic = msg.topic;
178
+ const utils = require("./utils.js");
179
+ let sPayload = utils.fetchFromObject(
180
+ msg,
181
+ config.payloadPropName || "payload"
182
+ );
183
+
184
+ // 12/08/2021 Restrict only to true/false
185
+ if (node.restrictinputevaluation) {
186
+ if (sPayload !== true && sPayload !== false) {
187
+ setNodeStatus({
188
+ fill: "red",
189
+ shape: "dot",
190
+ text: "Received non boolean value from " + msg.topic,
191
+ });
192
+ return;
193
+ }
194
+ }
195
+
196
+ var value = utils.ToBoolean(
197
+ sPayload,
198
+ RED.nodes.getNode(config.translatorConfig)// Retrieve the config node. It can be null, but it's handled in utils.js);
199
+ );
200
+ if (sPayload === undefined || value === undefined) {
201
+ // 15/11/2021 inform user about undefined topic or payload
202
+ setNodeStatus({
203
+ fill: "red",
204
+ shape: "dot",
205
+ text: "Received invalid payload from " + msg.topic || "",
206
+ });
207
+ return;
208
+ }
209
+
210
+ // 14/08/2019 if inputs are initialized, remove a "dummy" item from the state's array, as soon as new topic arrives
211
+ if (node.sInitializeWith !== "WaitForPayload") {
212
+ // Search if the current topic is in the state array
213
+ if (typeof node.jSonStates[topic] === "undefined") {
214
+ // Delete one dummy
215
+ for (let index = 0; index < node.config.inputCount; index++) {
216
+ if (node.jSonStates.hasOwnProperty("dummy" + index)) {
217
+ //RED.log.info(JSON.stringify(node.jSonStates))
218
+ delete node.jSonStates["dummy" + index];
219
+ //RED.log.info(JSON.stringify(node.jSonStates))
220
+ break;
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ // Add current attribute
227
+ node.jSonStates[topic] = value;
228
+
229
+ // Save the state array to a perisistent file
230
+ if (node.config.persist == true) {
231
+ try {
232
+ fs.writeFileSync(
233
+ path.join(node.persistPath, node.id.toString()),
234
+ JSON.stringify(node.jSonStates)
235
+ );
236
+ } catch (error) {
237
+ setNodeStatus({
238
+ fill: "red",
239
+ shape: "dot",
240
+ text: "Node cannot write to filesystem: " + error.message,
241
+ });
242
+ RED.log.error(
243
+ "BooleanLogicUltimate: unable to write to the filesystem. Check wether the user running node-red, has write permission to the filesysten. " +
244
+ error.message
245
+ );
246
+ }
247
+ }
248
+ node.inputMessage = msg; // 26/01/2022 Store MSG to be used in the outputResult function.
249
+
250
+ // Do we have as many inputs as we expect?
251
+ var keyCount = Object.keys(node.jSonStates).length;
252
+ if (keyCount == node.config.inputCount) {
253
+ // var resAND = CalculateResult("AND");
254
+ // var resOR = CalculateResult("OR");
255
+ // var resXOR = CalculateResult("XOR");
256
+
257
+ // if (node.config.filtertrue == "onlytrue") {
258
+ // if (!resAND) { resAND = null };
259
+ // if (!resOR) { resOR = null };
260
+ // if (!resXOR) { resXOR = null };
261
+ // }
262
+
263
+ // Operation mode evaluation
264
+ if (node.config.outputtriggeredby == "onlyonetopic") {
265
+ if (
266
+ typeof node.config.triggertopic !== "undefined" &&
267
+ node.config.triggertopic !== "" &&
268
+ msg.hasOwnProperty("topic") &&
269
+ msg.topic !== "" &&
270
+ node.config.triggertopic === msg.topic
271
+ ) {
272
+ if (node.delayEvaluation > 0) {
273
+ node.startTimerDelayEvaluation();
274
+ setNodeStatus({
275
+ fill: "blue",
276
+ shape: "ring",
277
+ text: "Delay Eval " + node.delayEvaluation + "ms",
278
+ });
279
+ } else {
280
+ outputResult();
281
+ }
282
+ } else {
283
+ setNodeStatus({
284
+ fill: "grey",
285
+ shape: "ring",
286
+ text:
287
+ "Saved (" +
288
+ (msg.hasOwnProperty("topic")
289
+ ? msg.topic
290
+ : "empty input topic") +
291
+ ") " +
292
+ value,
293
+ });
294
+ }
295
+ } else {
296
+ if (node.delayEvaluation > 0) {
297
+ node.startTimerDelayEvaluation();
298
+ setNodeStatus({
299
+ fill: "blue",
300
+ shape: "ring",
301
+ text: "Delay Eval " + node.delayEvaluation + "ms",
302
+ });
303
+ } else {
304
+ outputResult();
305
+ }
306
+ }
307
+ } else if (keyCount > node.config.inputCount) {
308
+ setNodeStatus({
309
+ fill: "gray",
310
+ shape: "ring",
311
+ text: "Reset due to unexpected new topic",
312
+ });
313
+ DeletePersistFile();
314
+ } else {
315
+ setNodeStatus({
316
+ fill: "green",
317
+ shape: "ring",
318
+ text: "Arrived topic " + keyCount + " of " + node.config.inputCount,
319
+ });
320
+ }
321
+ });
322
+
323
+ this.on("close", function (removed, done) {
324
+ if (removed) {
325
+ // This node has been deleted
326
+ // Delete persistent states on change/deploy
327
+ DeletePersistFile();
328
+ } else {
329
+ // This node is being restarted
330
+ }
331
+ done();
332
+ });
333
+
334
+ function DeletePersistFile() {
335
+ // Detele the persist file
336
+ try {
337
+ if (fs.existsSync(path.join(node.persistPath, node.id.toString())))
338
+ fs.unlinkSync(path.join(node.persistPath, node.id.toString()));
339
+ setNodeStatus({
340
+ fill: "red",
341
+ shape: "ring",
342
+ text: "Persistent states deleted (" + node.id.toString() + ").",
343
+ });
344
+ } catch (error) {
345
+ setNodeStatus({
346
+ fill: "red",
347
+ shape: "ring",
348
+ text: "Error deleting persistent file: " + error.toString(),
349
+ });
350
+ }
351
+ node.jSonStates = {}; // Resets inputs
352
+ // 14/08/2019 If the inputs are to be initialized, create a dummy items in the array
353
+ initUndefinedInputs();
354
+ }
355
+
356
+ function initUndefinedInputs() {
357
+ if (node.sInitializeWith !== "WaitForPayload") {
358
+ var nTotalDummyToCreate =
359
+ Number(node.config.inputCount) - Object.keys(node.jSonStates).length;
360
+ if (nTotalDummyToCreate > 0) {
361
+ RED.log.info(
362
+ "BooleanLogicUltimate: Will create " +
363
+ nTotalDummyToCreate +
364
+ " dummy (" +
365
+ node.sInitializeWith +
366
+ ") values"
367
+ );
368
+ for (let index = 0; index < nTotalDummyToCreate; index++) {
369
+ node.jSonStates["dummy" + index] =
370
+ node.sInitializeWith === "false" ? false : true;
371
+ }
372
+ let t = setTimeout(() => {
373
+ setNodeStatus({
374
+ fill: "green",
375
+ shape: "ring",
376
+ text:
377
+ "Initialized " +
378
+ nTotalDummyToCreate +
379
+ " undefined inputs with " +
380
+ node.sInitializeWith,
381
+ });
382
+ }, 4000);
383
+ }
384
+ }
385
+ }
386
+
387
+ function CalculateResult(_operation) {
388
+ var res;
389
+
390
+ if (_operation == "XOR") {
391
+ res = PerformXOR();
392
+ } else {
393
+ // We need a starting value to perform AND and OR operations.
394
+ var keys = Object.keys(node.jSonStates);
395
+ res = node.jSonStates[keys[0]];
396
+
397
+ for (var i = 1; i < keys.length; ++i) {
398
+ var key = keys[i];
399
+ res = PerformSimpleOperation(_operation, res, node.jSonStates[key]);
400
+ }
401
+ }
402
+
403
+ return res;
404
+ }
405
+
406
+ function PerformXOR() {
407
+ // XOR = exclusively one input is true. As such, we just count the number of true values and compare to 1.
408
+ var trueCount = 0;
409
+
410
+ for (var key in node.jSonStates) {
411
+ if (node.jSonStates[key]) {
412
+ trueCount++;
413
+ }
414
+ }
415
+
416
+ return trueCount == 1;
417
+ }
418
+
419
+ function PerformSimpleOperation(operation, val1, val2) {
420
+ var res;
421
+
422
+ if (operation === "AND") {
423
+ res = val1 && val2;
424
+ } else if (operation === "OR") {
425
+ res = val1 || val2;
426
+ } else {
427
+ node.error("Unknown operation: " + operation);
428
+ }
429
+
430
+ return res;
431
+ }
432
+
433
+ function outputResult() {
434
+ let optionalTopic = node.config.topic;
435
+ let calculatedValueAND = CalculateResult("AND");
436
+ let calculatedValueOR = CalculateResult("OR");
437
+ let calculatedValueXOR = CalculateResult("XOR");
438
+
439
+ if (node.config.filtertrue == "onlytrue") {
440
+ if (!calculatedValueAND) {
441
+ calculatedValueAND = null;
442
+ }
443
+ if (!calculatedValueOR) {
444
+ calculatedValueOR = null;
445
+ }
446
+ if (!calculatedValueXOR) {
447
+ calculatedValueXOR = null;
448
+ }
449
+ }
450
+
451
+ setNodeStatus({
452
+ fill: "green",
453
+ shape: "dot",
454
+ text:
455
+ "(AND)" +
456
+ (calculatedValueAND !== null ? calculatedValueAND : "---") +
457
+ " (OR)" +
458
+ (calculatedValueOR !== null ? calculatedValueOR : "---") +
459
+ " (XOR)" +
460
+ (calculatedValueXOR !== null ? calculatedValueXOR : "---"),
461
+ });
462
+
463
+ var msgAND = null;
464
+ if (calculatedValueAND != null) {
465
+ msgAND = RED.util.cloneMessage(node.inputMessage);
466
+ msgAND.topic = optionalTopic === undefined ? "result" : optionalTopic;
467
+ msgAND.operation = "AND";
468
+ msgAND.payload = calculatedValueAND;
469
+ }
470
+ var msgOR = null;
471
+ if (calculatedValueOR != null) {
472
+ msgOR = RED.util.cloneMessage(node.inputMessage);
473
+ msgOR.topic = optionalTopic === undefined ? "result" : optionalTopic;
474
+ msgOR.operation = "OR";
475
+ msgOR.payload = calculatedValueOR;
476
+ }
477
+ var msgXOR = null;
478
+ if (calculatedValueXOR != null) {
479
+ msgXOR = RED.util.cloneMessage(node.inputMessage);
480
+ msgXOR.topic = optionalTopic === undefined ? "result" : optionalTopic;
481
+ msgXOR.operation = "XOR";
482
+ msgXOR.payload = calculatedValueXOR;
483
+ }
484
+ node.send([msgAND, msgOR, msgXOR]);
485
+ }
486
+ }
487
+
488
+ RED.nodes.registerType("BooleanLogicUltimate", BooleanLogicUltimate);
489
+ };