| @@ -1,5 +1,9 @@ | |||
| # node-red-contrib-boolean-logic-ultimate | |||
| <p> | |||
| <b>Version 1.0.4</b><br/> | |||
| - Added the option to initialize the undefined inputs with true or false. Thanks to this, the node is immediately operative (will not wait until all topis arrives).<br/> | |||
| </p> | |||
| <p> | |||
| <b>Version 1.0.3</b><br/> | |||
| - Node status: cosmetic adjustments<br/> | |||
| </p> | |||
| @@ -51,10 +51,14 @@ Changing the topic is usually only needed when chaining multiple boolean nodes a | |||
| <li>Single topic + eval other inputs: <u>only whenever the node receives a msg input with the <b>specified topic</b> (having payload = true)</u>, it starts the evaluation of all other inputs as well and outputs the evaluated payload. If the node receives a msg input other than the <b>specified topic</b>), it only retains it's value for the boolean evaluation.</li> | |||
| </ol> | |||
| Example of trigger mode = Single topic + eval other inputs | |||
| ```js | |||
| [{"id":"4d2e4d1.4a02034","type":"BooleanLogicUltimate","z":"5635003e.2d70f","name":"","filtertrue":"both","persist":true,"triggertopic":"MotionSensor","outputtriggeredby":"onlyonetopic","inputCount":"3","topic":"result","x":460,"y":580,"wires":[["a9e93fa0.99508"],[],[]]},{"id":"b3a4633e.ff06c","type":"inject","z":"5635003e.2d70f","name":"","topic":"MotionSensor","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":560,"wires":[["4d2e4d1.4a02034"]]},{"id":"150ff8fd.8110e7","type":"inject","z":"5635003e.2d70f","name":"","topic":"Dusk","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":760,"wires":[["4d2e4d1.4a02034"]]},{"id":"6ecd73e1.9ac75c","type":"inject","z":"5635003e.2d70f","name":"","topic":"Rain","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":660,"wires":[["4d2e4d1.4a02034"]]},{"id":"350fb477.b0d484","type":"inject","z":"5635003e.2d70f","name":"","topic":"Dusk","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":800,"wires":[["4d2e4d1.4a02034"]]},{"id":"1118f46a.b967ac","type":"inject","z":"5635003e.2d70f","name":"","topic":"MotionSensor","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":600,"wires":[["4d2e4d1.4a02034"]]},{"id":"4e793dec.646b4c","type":"inject","z":"5635003e.2d70f","name":"","topic":"Rain","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":240,"y":700,"wires":[["4d2e4d1.4a02034"]]},{"id":"a9e93fa0.99508","type":"debug","z":"5635003e.2d70f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":650,"y":580,"wires":[]},{"id":"8d44deef.e81d","type":"comment","z":"5635003e.2d70f","name":"Switch on the light only when it's raining and it's dusk. The trigger is someone entering in the Motion Sensor's area.","info":"Triggers only if someone enters \nin the motion sensor's area","x":410,"y":520,"wires":[]}] | |||
| ``` | |||
| <b>If input states are undefined</b><br /> | |||
| Every time you create a node or modify the node, all inputs are set to undefined. This means that the node will wait the arrive of all topics (for example 3 topics, if you've selected 3 topics in the option), before it can output a payload. This can be a problem if your logic must be operative as soon as you deploy the flow. To overcome this problem, you can "initialize" all the undefined inputs with True or False. | |||
| <ol> | |||
| <li>Leave undefined: Standard behaviour, the node will wait all the "undefined" topics to arrive, then starts a flow with the result.</li> | |||
| <li>True or False: The node is immediately operative, by force the initialization of the "undefined" inputs with "true" or "false".</li> | |||
| </ol> | |||
| <br/> | |||
| <br/><br/> | |||
| @@ -8,6 +8,7 @@ | |||
| }, | |||
| filtertrue: {value:"both"}, | |||
| persist: {value:true}, | |||
| sInitializeWith: {value:"WaitForPayload"}, | |||
| triggertopic: { | |||
| value: "trigger", | |||
| required: false | |||
| @@ -90,6 +91,14 @@ | |||
| $("#node-input-outputtriggeredby").val("all"); | |||
| this.outputtriggeredby="all"; | |||
| } | |||
| // default | |||
| if(typeof this.sInitializeWith === "undefined") | |||
| { | |||
| $("#node-input-sInitializeWith").val("WaitForPayload"); | |||
| this.sInitializeWith="WaitForPayload"; | |||
| } | |||
| }, | |||
| oneditsave: function () { | |||
| // Delete persistent state file | |||
| @@ -124,7 +133,7 @@ | |||
| <label for="node-input-outputtriggeredby"><i class="fa fa-filter"></i> Trigger mode</label> | |||
| <select type="text" id="node-input-outputtriggeredby" placeholder="Event"> | |||
| <option value="all">All topics</option> | |||
| <option value="onlyonetopic">Single topic + only eval others</option> | |||
| <option value="onlyonetopic">Single topic</option> | |||
| </select> | |||
| </div> | |||
| <div class="form-row" id="triggertopic"> | |||
| @@ -136,6 +145,14 @@ | |||
| <label style="width:auto" for="node-input-persist"> Remember latest input values after reboot</label> <input type="checkbox" id="node-input-persist" style="display:inline-block; width:auto; vertical-align:top;"> | |||
| <div id="helpallga"><i> If checked, the input values are retained <br /> after a node-red reboot.<br/>If you reboot your node-red, <br />you don't need to wait all inputs <br />to arrive and initialize the node, <br />before the node can output a payload.</div> | |||
| </div> | |||
| <div class="form-row"> | |||
| <label for="node-input-sInitializeWith"><i class="fa fa-home"></i> If input states are undefined</label> | |||
| <select type="text" id="node-input-sInitializeWith" placeholder=""> | |||
| <option value="WaitForPayload">Leave undefined</option> | |||
| <option value="false">Initialize all with False</option> | |||
| <option value="true">Initialize all with True</option> | |||
| </select> | |||
| </div> | |||
| </script> | |||
| @@ -184,7 +201,15 @@ | |||
| Every time you modify the node's config, <b>the retained values are cleared</b>.<br/> | |||
| <br/> | |||
| <b> If input states are undefined</b><br /> | |||
| Every time you create a node or modify the node, all inputs are set to undefined. This means that the node will wait the arrive of all topics (for example 3 topics, if you've selected 3 topics in the option), before it can output a payload. This can be a problem if your logic must be operative as soon as you deploy the flow. To overcome this problem, you can "initialize" all the undefined inputs with True or False. | |||
| <ol> | |||
| <li>Leave undefined: Standard behaviour, the node will wait all the "undefined" topics to arrive, then starts a flow with the result.</li> | |||
| <li>True or False: The node is immediately operative, by force the initialization of the "undefined" inputs with "true" or "false".</li> | |||
| </ol> | |||
| <br/> | |||
| <bi How inputs are handled:</i><br /> | |||
| All incoming msg.payloads are converted into a boolean value according to the following rules (this applies to all boolean logic nodes): | |||
| <ol> | |||
| <li>Boolean values are taken as-is.</li> | |||
| @@ -193,5 +218,6 @@ | |||
| </ol> | |||
| <br> | |||
| The XOR operation operates in a one, and only one mode, i.e. (A ^ B) ^ C ... ^ n | |||
| </p> | |||
| </script> | |||
| @@ -1,14 +1,13 @@ | |||
| module.exports = function(RED) { | |||
| function BooleanLogicUltimate(config) { | |||
| RED.nodes.createNode(this,config); | |||
| this.config = config; | |||
| this.state = {}; | |||
| RED.nodes.createNode(this, config); | |||
| var node = this; | |||
| node.config = config; | |||
| node.jSonStates = {}; // JSON object with elements. It's not an array! Format: {"Rain":true,"Dusk":true,"MotionSensor":true} | |||
| 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 }); | |||
| @@ -21,8 +20,8 @@ module.exports = function(RED) { | |||
| try { | |||
| var contents = fs.readFileSync("states/" + node.id.toString()).toString(); | |||
| if (typeof contents !== 'undefined') { | |||
| node.state = JSON.parse(contents); | |||
| node.status({fill: "blue",shape: "ring",text: "Loaded persistent states (" + Object.keys(node.state).length + " total)."}); | |||
| node.jSonStates = JSON.parse(contents); | |||
| node.status({fill: "blue",shape: "ring",text: "Loaded persistent states (" + Object.keys(node.jSonStates).length + " total)."}); | |||
| } | |||
| } catch (error) { | |||
| node.status({fill: "grey",shape: "ring",text: "No persistent states"}); | |||
| @@ -32,23 +31,45 @@ module.exports = function(RED) { | |||
| node.status({fill: "yellow",shape: "dot",text: "Waiting for input states"}); | |||
| } | |||
| // 14/08/2019 If the 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 state = node.state; | |||
| // 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") | |||
| { | |||
| // Search if the current topic is in the state array | |||
| if (typeof node.jSonStates[topic] === "undefined") | |||
| { | |||
| // Delete one dummy | |||
| for (let index = 0; index < node.config.inputCount; index++) { | |||
| if (node.jSonStates.hasOwnProperty("dummy" + index)) { | |||
| //RED.log.info(JSON.stringify(node.jSonStates)) | |||
| delete node.jSonStates["dummy" + index]; | |||
| //RED.log.info(JSON.stringify(node.jSonStates)) | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // Add current attribute | |||
| node.jSonStates[topic] = value; | |||
| state[topic] = value; | |||
| // Save the state array to a perisistent file | |||
| if (this.config.persist == true) { | |||
| if (node.config.persist == true) { | |||
| try { | |||
| if (!fs.existsSync("states")) fs.mkdirSync("states"); | |||
| fs.writeFileSync("states/" + node.id.toString(),JSON.stringify(state)); | |||
| fs.writeFileSync("states/" + node.id.toString(),JSON.stringify(node.jSonStates)); | |||
| } catch (error) { | |||
| node.status({fill: "red",shape: "dot",text: "Node cannot write to filesystem: " + error}); | |||
| @@ -56,7 +77,7 @@ module.exports = function(RED) { | |||
| } | |||
| // Do we have as many inputs as we expect? | |||
| var keyCount = Object.keys(state).length; | |||
| var keyCount = Object.keys(node.jSonStates).length; | |||
| if( keyCount == node.config.inputCount ) { | |||
| @@ -93,7 +114,7 @@ module.exports = function(RED) { | |||
| ? node.config.name : "BooleanLogicUltimate") | |||
| + " [Logic]: More than the specified " | |||
| + node.config.inputCount + " topics received, resetting. Will not output new value until " + node.config.inputCount + " new topics have been received."); | |||
| node.state = {}; | |||
| node.jSonStates = {}; | |||
| DeletePersistFile(node.id); | |||
| DisplayUnkownStatus(); | |||
| } else { | |||
| @@ -122,6 +143,23 @@ module.exports = function(RED) { | |||
| } catch (error) { | |||
| _node.status({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 | |||
| initUndefinedInputs(); | |||
| } | |||
| function initUndefinedInputs() { | |||
| if (node.sInitializeWith !== "WaitForPayload") | |||
| { | |||
| var nTotalDummyToCreate = Number(node.config.inputCount) - Object.keys(node.jSonStates).length; | |||
| 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; | |||
| } | |||
| if (nTotalDummyToCreate > 0) { | |||
| setTimeout(() => { node.status({fill: "green",shape: "ring",text: "Initialized " + nTotalDummyToCreate + " undefined inputs with " + node.sInitializeWith});}, 4000) | |||
| } | |||
| } | |||
| } | |||
| function CalculateResult(_operation) { | |||
| @@ -132,12 +170,12 @@ module.exports = function(RED) { | |||
| } | |||
| else { | |||
| // We need a starting value to perform AND and OR operations. | |||
| var keys = Object.keys(node.state); | |||
| res = node.state[keys[0]]; | |||
| var keys = Object.keys(node.jSonStates); | |||
| res = node.jSonStates[keys[0]]; | |||
| for( var i = 1; i < keys.length; ++i ) { | |||
| var key = keys[i]; | |||
| res = PerformSimpleOperation( _operation, res, node.state[key] ); | |||
| res = PerformSimpleOperation( _operation, res, node.jSonStates[key] ); | |||
| } | |||
| } | |||
| @@ -149,8 +187,8 @@ module.exports = function(RED) { | |||
| // 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.state ) { | |||
| if( node.state[key] ) { | |||
| for( var key in node.jSonStates ) { | |||
| if( node.jSonStates[key] ) { | |||
| trueCount++; | |||
| } | |||
| } | |||
| @@ -194,16 +232,7 @@ module.exports = function(RED) { | |||
| return res; | |||
| }; | |||
| function DisplayStatus ( value ) { | |||
| node.status( | |||
| { | |||
| fill: value ? "green" : "red", | |||
| shape: value ? "dot" : "ring", | |||
| text: value ? "true" : "false" | |||
| } | |||
| ); | |||
| }; | |||
| function DisplayUnkownStatus () { | |||
| node.status( | |||
| { | |||
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "name": "node-red-contrib-boolean-logic-ultimate", | |||
| "version": "1.0.3", | |||
| "version": "1.0.4", | |||
| "description": "A set of Node-RED enhanced boolean logic, with persisten values after reboot and more", | |||
| "author": "Supergiovane (https://github.com/Supergiovane)", | |||
| "dependencies": { | |||