|
|
@@ -1,5 +1,5 @@ |
|
|
|
module.exports = function(RED) { |
|
|
|
function BooleanLogicUltimate(config) { |
|
|
|
module.exports = function (RED) { |
|
|
|
function BooleanLogicUltimate(config) { |
|
|
|
RED.nodes.createNode(this, config); |
|
|
|
var node = this; |
|
|
|
node.config = config; |
|
|
@@ -7,7 +7,7 @@ module.exports = function(RED) { |
|
|
|
node.sInitializeWith = typeof node.config.sInitializeWith === "undefined" ? "WaitForPayload" : node.config.sInitializeWith; |
|
|
|
var fs = require('fs'); |
|
|
|
var decimal = /^\s*[+-]{0,1}\s*([\d]+(\.[\d]*)*)\s*$/ |
|
|
|
|
|
|
|
|
|
|
|
// Helper for the config html, to be able to delete the peristent states file |
|
|
|
RED.httpAdmin.get("/stateoperation_delete", RED.auth.needsPermission('BooleanLogicUltimate.read'), function (req, res) { |
|
|
|
//node.send({ req: req }); |
|
|
@@ -27,35 +27,33 @@ module.exports = function(RED) { |
|
|
|
var contents = fs.readFileSync("states/" + node.id.toString()).toString(); |
|
|
|
if (typeof contents !== 'undefined') { |
|
|
|
node.jSonStates = JSON.parse(contents); |
|
|
|
setNodeStatus({fill: "blue",shape: "ring",text: "Loaded persistent states (" + Object.keys(node.jSonStates).length + " total)."}); |
|
|
|
setNodeStatus({ fill: "blue", shape: "ring", text: "Loaded persistent states (" + Object.keys(node.jSonStates).length + " total)." }); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
setNodeStatus({fill: "grey",shape: "ring",text: "No persistent states"}); |
|
|
|
setNodeStatus({ fill: "grey", shape: "ring", text: "No persistent states" }); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
setNodeStatus({fill: "yellow",shape: "dot",text: "Waiting for input states"}); |
|
|
|
setNodeStatus({ fill: "yellow", shape: "dot", text: "Waiting for input states" }); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 14/08/2019 If some inputs are to be initialized, create a dummy items in the array |
|
|
|
initUndefinedInputs(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.on('input', function (msg) { |
|
|
|
|
|
|
|
|
|
|
|
var topic = msg.topic; |
|
|
|
var payload = msg.payload; |
|
|
|
|
|
|
|
|
|
|
|
if (topic !== undefined && payload !== undefined) { |
|
|
|
var value = ToBoolean( payload ); |
|
|
|
|
|
|
|
var value = ToBoolean(payload); |
|
|
|
|
|
|
|
// 14/08/2019 if inputs are initialized, remove a "dummy" item from the state's array, as soon as new topic arrives |
|
|
|
if(node.sInitializeWith !== "WaitForPayload") |
|
|
|
{ |
|
|
|
if (node.sInitializeWith !== "WaitForPayload") { |
|
|
|
// Search if the current topic is in the state array |
|
|
|
if (typeof node.jSonStates[topic] === "undefined") |
|
|
|
{ |
|
|
|
if (typeof node.jSonStates[topic] === "undefined") { |
|
|
|
// Delete one dummy |
|
|
|
for (let index = 0; index < node.config.inputCount; index++) { |
|
|
|
if (node.jSonStates.hasOwnProperty("dummy" + index)) { |
|
|
@@ -63,30 +61,30 @@ module.exports = function(RED) { |
|
|
|
delete node.jSonStates["dummy" + index]; |
|
|
|
//RED.log.info(JSON.stringify(node.jSonStates)) |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Add current attribute |
|
|
|
node.jSonStates[topic] = value; |
|
|
|
|
|
|
|
|
|
|
|
// Save the state array to a perisistent file |
|
|
|
if (node.config.persist == true) { |
|
|
|
if (node.config.persist == true) { |
|
|
|
try { |
|
|
|
if (!fs.existsSync("states")) fs.mkdirSync("states"); |
|
|
|
fs.writeFileSync("states/" + node.id.toString(),JSON.stringify(node.jSonStates)); |
|
|
|
|
|
|
|
fs.writeFileSync("states/" + node.id.toString(), JSON.stringify(node.jSonStates)); |
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
setNodeStatus({fill: "red",shape: "dot",text: "Node cannot write to filesystem: " + error}); |
|
|
|
setNodeStatus({ fill: "red", shape: "dot", text: "Node cannot write to filesystem: " + error }); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Do we have as many inputs as we expect? |
|
|
|
var keyCount = Object.keys(node.jSonStates).length; |
|
|
|
|
|
|
|
if( keyCount == node.config.inputCount ) { |
|
|
|
|
|
|
|
if (keyCount == node.config.inputCount) { |
|
|
|
|
|
|
|
var resAND = CalculateResult("AND"); |
|
|
|
var resOR = CalculateResult("OR"); |
|
|
|
var resXOR = CalculateResult("XOR"); |
|
|
@@ -101,29 +99,26 @@ module.exports = function(RED) { |
|
|
|
if (node.config.outputtriggeredby == "onlyonetopic") { |
|
|
|
if (typeof node.config.triggertopic !== "undefined" |
|
|
|
&& node.config.triggertopic !== "" |
|
|
|
&& msg.hasOwnProperty("topic") && msg.topic !=="" |
|
|
|
&& node.config.triggertopic === msg.topic) |
|
|
|
{ |
|
|
|
SetResult(resAND, resOR, resXOR, node.config.topic,msg); |
|
|
|
} else |
|
|
|
{ |
|
|
|
setNodeStatus({ fill: "grey", shape: "ring", text: "Saved (" + (msg.hasOwnProperty("topic") ? msg.topic : "empty input topic") + ") " + value}); |
|
|
|
&& msg.hasOwnProperty("topic") && msg.topic !== "" |
|
|
|
&& node.config.triggertopic === msg.topic) { |
|
|
|
SetResult(resAND, resOR, resXOR, node.config.topic, msg); |
|
|
|
} else { |
|
|
|
setNodeStatus({ fill: "grey", shape: "ring", text: "Saved (" + (msg.hasOwnProperty("topic") ? msg.topic : "empty input topic") + ") " + value }); |
|
|
|
} |
|
|
|
} else |
|
|
|
{ |
|
|
|
SetResult(resAND, resOR, resXOR, node.config.topic,msg); |
|
|
|
} else { |
|
|
|
SetResult(resAND, resOR, resXOR, node.config.topic, msg); |
|
|
|
} |
|
|
|
} |
|
|
|
else if(keyCount > node.config.inputCount ) { |
|
|
|
setNodeStatus({ fill: "gray", shape: "ring", text: "Reset due to unexpected new topic"}); |
|
|
|
else if (keyCount > node.config.inputCount) { |
|
|
|
setNodeStatus({ fill: "gray", shape: "ring", text: "Reset due to unexpected new topic" }); |
|
|
|
DeletePersistFile(); |
|
|
|
} else { |
|
|
|
setNodeStatus({ fill: "green", shape: "ring", text: "Arrived topic " + keyCount + " of " + node.config.inputCount}); |
|
|
|
setNodeStatus({ fill: "green", shape: "ring", text: "Arrived topic " + keyCount + " of " + node.config.inputCount }); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
this.on('close', function(removed, done) { |
|
|
|
|
|
|
|
this.on('close', function (removed, done) { |
|
|
|
if (removed) { |
|
|
|
// This node has been deleted |
|
|
|
// Delete persistent states on change/deploy |
|
|
@@ -134,13 +129,13 @@ module.exports = function(RED) { |
|
|
|
done(); |
|
|
|
}); |
|
|
|
|
|
|
|
function DeletePersistFile (){ |
|
|
|
function DeletePersistFile() { |
|
|
|
// Detele the persist file |
|
|
|
try { |
|
|
|
if (fs.existsSync("states/" + node.id.toString())) fs.unlinkSync("states/" + node.id.toString()); |
|
|
|
setNodeStatus({fill: "red",shape: "ring",text: "Persistent states deleted ("+node.id.toString()+")."}); |
|
|
|
setNodeStatus({ fill: "red", shape: "ring", text: "Persistent states deleted (" + node.id.toString() + ")." }); |
|
|
|
} catch (error) { |
|
|
|
setNodeStatus({fill: "red",shape: "ring",text: "Error deleting persistent file: " + error.toString()}); |
|
|
|
setNodeStatus({ fill: "red", shape: "ring", text: "Error deleting persistent file: " + error.toString() }); |
|
|
|
} |
|
|
|
node.jSonStates = {}; // Resets inputs |
|
|
|
// 14/08/2019 If the inputs are to be initialized, create a dummy items in the array |
|
|
@@ -148,144 +143,121 @@ module.exports = function(RED) { |
|
|
|
} |
|
|
|
|
|
|
|
function initUndefinedInputs() { |
|
|
|
if (node.sInitializeWith !== "WaitForPayload") |
|
|
|
{ |
|
|
|
if (node.sInitializeWith !== "WaitForPayload") { |
|
|
|
var nTotalDummyToCreate = Number(node.config.inputCount) - Object.keys(node.jSonStates).length; |
|
|
|
if (nTotalDummyToCreate > 0) { |
|
|
|
RED.log.info("BooleanLogicUltimate: Will create " + nTotalDummyToCreate + " dummy (" + node.sInitializeWith + ") values") |
|
|
|
for (let index = 0; index < nTotalDummyToCreate; index++) { |
|
|
|
node.jSonStates["dummy" + index] = node.sInitializeWith === "false" ? false : true; |
|
|
|
} |
|
|
|
setTimeout(() => { setNodeStatus({fill: "green",shape: "ring",text: "Initialized " + nTotalDummyToCreate + " undefined inputs with " + node.sInitializeWith});}, 4000) |
|
|
|
} |
|
|
|
setTimeout(() => { setNodeStatus({ fill: "green", shape: "ring", text: "Initialized " + nTotalDummyToCreate + " undefined inputs with " + node.sInitializeWith }); }, 4000) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function setNodeStatus({fill, shape, text}) |
|
|
|
{ |
|
|
|
function setNodeStatus({ fill, shape, text }) { |
|
|
|
var dDate = new Date(); |
|
|
|
node.status({fill: fill,shape: shape,text: text + " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")"}) |
|
|
|
node.status({ fill: fill, shape: shape, text: text + " (" + dDate.getDate() + ", " + dDate.toLocaleTimeString() + ")" }) |
|
|
|
} |
|
|
|
|
|
|
|
function CalculateResult(_operation) { |
|
|
|
var res; |
|
|
|
|
|
|
|
if( _operation == "XOR") { |
|
|
|
|
|
|
|
if (_operation == "XOR") { |
|
|
|
res = PerformXOR(); |
|
|
|
} |
|
|
|
else { |
|
|
|
// We need a starting value to perform AND and OR operations. |
|
|
|
var keys = Object.keys(node.jSonStates); |
|
|
|
res = node.jSonStates[keys[0]]; |
|
|
|
|
|
|
|
for( var i = 1; i < keys.length; ++i ) { |
|
|
|
|
|
|
|
for (var i = 1; i < keys.length; ++i) { |
|
|
|
var key = keys[i]; |
|
|
|
res = PerformSimpleOperation( _operation, res, node.jSonStates[key] ); |
|
|
|
res = PerformSimpleOperation(_operation, res, node.jSonStates[key]); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return res; |
|
|
|
} |
|
|
|
|
|
|
|
function PerformXOR() |
|
|
|
{ |
|
|
|
|
|
|
|
return res; |
|
|
|
} |
|
|
|
|
|
|
|
function PerformXOR() { |
|
|
|
// XOR = exclusively one input is true. As such, we just count the number of true values and compare to 1. |
|
|
|
var trueCount = 0; |
|
|
|
|
|
|
|
for( var key in node.jSonStates ) { |
|
|
|
if( node.jSonStates[key] ) { |
|
|
|
|
|
|
|
for (var key in node.jSonStates) { |
|
|
|
if (node.jSonStates[key]) { |
|
|
|
trueCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return trueCount == 1; |
|
|
|
} |
|
|
|
|
|
|
|
function PerformSimpleOperation( operation, val1, val2 ) { |
|
|
|
|
|
|
|
function PerformSimpleOperation(operation, val1, val2) { |
|
|
|
var res; |
|
|
|
|
|
|
|
if( operation === "AND" ) { |
|
|
|
|
|
|
|
if (operation === "AND") { |
|
|
|
res = val1 && val2; |
|
|
|
} |
|
|
|
else if( operation === "OR" ) { |
|
|
|
else if (operation === "OR") { |
|
|
|
res = val1 || val2; |
|
|
|
} |
|
|
|
else { |
|
|
|
node.error( "Unknown operation: " + operation ); |
|
|
|
node.error("Unknown operation: " + operation); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return res; |
|
|
|
} |
|
|
|
|
|
|
|
function ToBoolean( value ) { |
|
|
|
|
|
|
|
function ToBoolean(value) { |
|
|
|
var res = false; |
|
|
|
|
|
|
|
|
|
|
|
if (typeof value === 'boolean') { |
|
|
|
res = value; |
|
|
|
} |
|
|
|
else if( typeof value === 'number' || typeof value === 'string' ) { |
|
|
|
} |
|
|
|
else if (typeof value === 'number' || typeof value === 'string') { |
|
|
|
// Is it formated as a decimal number? |
|
|
|
if( decimal.test( value ) ) { |
|
|
|
var v = parseFloat( value ); |
|
|
|
if (decimal.test(value)) { |
|
|
|
var v = parseFloat(value); |
|
|
|
res = v != 0; |
|
|
|
} |
|
|
|
else { |
|
|
|
res = value.toLowerCase() === "true"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return res; |
|
|
|
}; |
|
|
|
|
|
|
|
function SetResult(_valueAND, _valueOR, _valueXOR, optionalTopic,_msg) { |
|
|
|
setNodeStatus({fill: "green",shape: "dot",text: "(AND)" + _valueAND + " (OR)" +_valueOR + " (XOR)" +_valueXOR}); |
|
|
|
|
|
|
|
// 24/01/2020 Output the entire input msg by duplicating the input and replacing only relevant fields. |
|
|
|
|
|
|
|
function SetResult(_valueAND, _valueOR, _valueXOR, optionalTopic, _msg) { |
|
|
|
setNodeStatus({ fill: "green", shape: "dot", text: "(AND)" + (_valueAND !== null ? _valueAND : "---") + " (OR)" + (_valueOR !== null ? _valueOR : "---") + " (XOR)" + (_valueXOR !== null ? _valueXOR : "---") }); |
|
|
|
|
|
|
|
var msgAND = null; |
|
|
|
if (_valueAND != null) { |
|
|
|
var msgAND = RED.util.cloneMessage(_msg); |
|
|
|
msgAND = RED.util.cloneMessage(_msg); |
|
|
|
msgAND.topic = optionalTopic === undefined ? "result" : optionalTopic; |
|
|
|
msgAND.operation = "AND"; |
|
|
|
msgAND.payload = _valueAND; |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
if (_valueOR!=null){ |
|
|
|
var msgOR = RED.util.cloneMessage(_msg); |
|
|
|
var msgOR = null; |
|
|
|
if (_valueOR != null) { |
|
|
|
msgOR = RED.util.cloneMessage(_msg); |
|
|
|
msgOR.topic = optionalTopic === undefined ? "result" : optionalTopic; |
|
|
|
msgOR.operation = "OR"; |
|
|
|
msgOR.payload= _valueOR; |
|
|
|
msgOR.payload = _valueOR; |
|
|
|
} |
|
|
|
var msgXOR = null; |
|
|
|
if (_valueXOR != null) { |
|
|
|
var msgXOR = RED.util.cloneMessage(_msg); |
|
|
|
msgXOR = RED.util.cloneMessage(_msg); |
|
|
|
msgXOR.topic = optionalTopic === undefined ? "result" : optionalTopic; |
|
|
|
msgXOR.operation = "XOR"; |
|
|
|
msgXOR.payload= _valueXOR; |
|
|
|
msgXOR.payload = _valueXOR; |
|
|
|
} |
|
|
|
node.send([msgAND, msgOR, msgXOR]); |
|
|
|
|
|
|
|
// if (_valueAND!=null){ |
|
|
|
// var msgAND = { |
|
|
|
// topic: optionalTopic === undefined ? "result" : optionalTopic, |
|
|
|
// operation:"AND", |
|
|
|
// payload: _valueAND |
|
|
|
// }; |
|
|
|
// } |
|
|
|
// if (_valueOR!=null){ |
|
|
|
// var msgOR = { |
|
|
|
// topic: optionalTopic === undefined ? "result" : optionalTopic, |
|
|
|
// operation:"OR", |
|
|
|
// payload: _valueOR |
|
|
|
// }; |
|
|
|
// } |
|
|
|
// if (_valueXOR!=null){ |
|
|
|
// var msgXOR = { |
|
|
|
// topic: optionalTopic === undefined ? "result" : optionalTopic, |
|
|
|
// operation:"XOR", |
|
|
|
// payload: _valueXOR |
|
|
|
// }; |
|
|
|
// } |
|
|
|
// node.send([msgAND,msgOR,msgXOR]); |
|
|
|
|
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
RED.nodes.registerType("BooleanLogicUltimate",BooleanLogicUltimate); |
|
|
|
} |
|
|
|
|
|
|
|
RED.nodes.registerType("BooleanLogicUltimate", BooleanLogicUltimate); |
|
|
|
} |