├── package.json ├── boolean-logic ├── BDebug.js ├── Invert.js ├── BDebug.html ├── Invert.html ├── NodeHelper.js ├── BooleanLogic.js └── BooleanLogic.html ├── examples └── boolean or example.json ├── LICENSE └── README.md /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-boolean-logic", 3 | "version": "0.0.3", 4 | "description": "A set of Node-RED nodes for boolean logic", 5 | "author": "Per Malmberg (https://github.com/PerMalmberg)", 6 | "dependencies": { 7 | }, 8 | "keywords": [ "node-red", "boolean", "logic" ], 9 | "license": "MIT", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/PerMalmberg/node-red-contrib-boolean-logic" 13 | }, 14 | "node-red": { 15 | "nodes": { 16 | "BooleanLogic": "boolean-logic/BooleanLogic.js", 17 | "Invert": "boolean-logic/Invert.js", 18 | "BDebug": "boolean-logic/BDebug.js" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /boolean-logic/BDebug.js: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license, see LICENSE file. 2 | // Author: Per Malmberg (https://github.com/PerMalmberg) 3 | module.exports = function(RED) { 4 | function BDebug(config) { 5 | RED.nodes.createNode(this,config); 6 | this.config = config; 7 | var node = this; 8 | var NodeHelper = require('./NodeHelper.js'); 9 | var h = new NodeHelper( node ); 10 | 11 | this.on('input', function(msg) { 12 | var topic = msg.topic; 13 | var payload = msg.payload; 14 | 15 | if( topic !== undefined && payload !== undefined ) { 16 | h.DisplayStatus( h.ToBoolean( payload ) ); 17 | } 18 | }); 19 | 20 | h.DisplayUnkownStatus(); 21 | } 22 | 23 | RED.nodes.registerType("BDebug",BDebug); 24 | } -------------------------------------------------------------------------------- /boolean-logic/Invert.js: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license, see LICENSE file. 2 | // Author: Per Malmberg (https://github.com/PerMalmberg) 3 | module.exports = function(RED) { 4 | function Invert(config) { 5 | RED.nodes.createNode(this,config); 6 | this.config = config; 7 | var node = this; 8 | var NodeHelper = require('./NodeHelper.js'); 9 | var h = new NodeHelper( node ); 10 | 11 | this.on('input', function(msg) { 12 | var topic = msg.topic; 13 | var payload = msg.payload; 14 | 15 | if( topic !== undefined && payload !== undefined ) { 16 | h.SetResult( !h.ToBoolean( payload ), topic ); 17 | } 18 | }); 19 | 20 | h.DisplayUnkownStatus(); 21 | } 22 | 23 | 24 | RED.nodes.registerType("Invert",Invert); 25 | } -------------------------------------------------------------------------------- /examples/boolean or example.json: -------------------------------------------------------------------------------- 1 | [{"id":"1bb999a.5f6a566","type":"BooleanLogic","z":"62d12c11.50bd44","name":"","operation":"OR","inputCount":2,"topic":"result","x":430,"y":220,"wires":[["1ca74af5.9e7305"]]},{"id":"1a04e0f4.aae6af","type":"inject","z":"62d12c11.50bd44","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"value1","payload":"true","payloadType":"bool","x":200,"y":160,"wires":[["1bb999a.5f6a566"]]},{"id":"8c34e183.5b814","type":"inject","z":"62d12c11.50bd44","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"value2","payload":"true","payloadType":"bool","x":200,"y":260,"wires":[["1bb999a.5f6a566"]]},{"id":"1ca74af5.9e7305","type":"BDebug","z":"62d12c11.50bd44","name":"Debug","x":610,"y":220,"wires":[]}] -------------------------------------------------------------------------------- /boolean-logic/BDebug.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /boolean-logic/Invert.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Per Malmberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /boolean-logic/NodeHelper.js: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license, see LICENSE file. 2 | // Author: Per Malmberg (https://github.com/PerMalmberg) 3 | 4 | var NodeHelper = function( node ) { 5 | var myNode = node; 6 | var self = this; 7 | var decimal = /^\s*[+-]{0,1}\s*([\d]+(\.[\d]*)*)\s*$/ 8 | 9 | 10 | this.ToBoolean = function( value ) { 11 | var res = false; 12 | 13 | if (typeof value === 'boolean') { 14 | res = value; 15 | } 16 | else if( typeof value === 'number' || typeof value === 'string' ) { 17 | // Is it formated as a decimal number? 18 | if( decimal.test( value ) ) { 19 | var v = parseFloat( value ); 20 | res = v != 0; 21 | } 22 | else { 23 | res = value.toLowerCase() === "true"; 24 | } 25 | } 26 | 27 | return res; 28 | }; 29 | 30 | this.DisplayStatus = function( value ) { 31 | myNode.status( 32 | { 33 | fill: value ? "green" : "red", 34 | shape: value ? "dot" : "ring", 35 | text: value ? "true" : "false" 36 | } 37 | ); 38 | }; 39 | 40 | this.DisplayUnkownStatus = function() { 41 | myNode.status( 42 | { 43 | fill: "gray", 44 | shape: "dot", 45 | text: "Unknown" 46 | }); 47 | }; 48 | 49 | this.SetResult = function( value, optionalTopic ) { 50 | self.DisplayStatus( value ); 51 | 52 | var msg = { 53 | topic: optionalTopic === undefined ? "result" : optionalTopic, 54 | payload: value 55 | }; 56 | 57 | myNode.send(msg); 58 | }; 59 | }; 60 | module.exports = NodeHelper; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-red-contrib-boolean-logic 2 | [Node-RED](http://nodered.org/) nodes to easily perform boolean logic. 3 | 4 | ## The problem 5 | [Node-RED](http://nodered.org/) does not support multiple inputs on nodes, and it has been discussed at length in [this thread](https://groups.google.com/forum/#!searchin/node-red/multiple$20inputs%7Csort:relevance/node-red/Q0YLQYCUJ_E/JVNjznmx2e8J). The TL;DR - as I understand it - is that the developers of NR argue that multiple inputs makes it too complex for users without a background in electrical engineering and that it is [preferred](https://groups.google.com/d/msg/node-red/Q0YLQYCUJ_E/DTxHFcVfAwAJ) users of NR instead use other means to create the desired logic (i.e. write Javascript in function-nodes). 6 | 7 | ## A solution 8 | I really needed a simple and reusable way to perform boolean logic on multiple topics without the need to write the same code over and over. 9 | 10 | Could this be solved using a subflow? No, function-node within a subflow cannot be configured on an instance basis which is required as the logic must know how many inputs it is expecting when performing operations such as ```A || B``` or ```A && (B || C)```. Yes, that could be hard coded, but then it would not be reusable. Also, a subflow cannot use the status indicator which is a great help to the user. 11 | 12 | What I came up with are the following nodes. 13 | * BooleanLogic: Can perform AND, OR and XOR operations on ```msg.payload``` on any number of topics. 14 | * Invert: Inverts the ```msg.payload```, e.g. true -> false. 15 | * Debug: A debug node that displays the status direcly in the editor, making it easier to see the boolean value at a specific point. 16 | 17 | All nodes attempts to convert the incoming ```msg.payload``` to a boolean value according to these rules: 18 | * Boolean values are taken as-is. 19 | * For numbers, 0 evaluates to false, all other numbers evaluates to true. 20 | * Strings are converted to numbers if they match the format of a decimal value, then the same rule as for numbers are applied. If it does not match, it evaluates to false. Also, the string "true" evaluates to true. 21 | 22 | ### BooleanLogic 23 | This node must be configured with the expected number of topics. It will not output a value until it has received the configured number of topics. Also, if it receives more than the configured number of topics it will reset (but not output a value) and wait until it once again sees the configured number of topics. 24 | 25 | ## Example 26 | ![Example](http://i.imgur.com/m2s6JRl.png) 27 | 28 | ## Version history 29 | * 0.0.1 First release 30 | * 0.0.2 31 | * Changed status indicators from dot to rings for false-values. 32 | * Reworked the conversion of input values to be consistent between numbers and strings with numeric meaning. 33 | -------------------------------------------------------------------------------- /boolean-logic/BooleanLogic.js: -------------------------------------------------------------------------------- 1 | // Licensed under the MIT license, see LICENSE file. 2 | // Author: Per Malmberg (https://github.com/PerMalmberg) 3 | module.exports = function(RED) { 4 | function BooleanLogic(config) { 5 | RED.nodes.createNode(this,config); 6 | this.config = config; 7 | this.state = {}; 8 | var node = this; 9 | var NodeHelper = require('./NodeHelper.js'); 10 | var h = new NodeHelper( node ); 11 | 12 | this.on('input', function(msg) { 13 | var topic = msg.topic; 14 | var payload = msg.payload; 15 | 16 | if( topic !== undefined && payload !== undefined ) { 17 | var value = h.ToBoolean( payload ); 18 | var state = node.state; 19 | 20 | state[topic] = value; 21 | 22 | // Do we have as many inputs as we expect? 23 | var keyCount = Object.keys(state).length; 24 | 25 | if( keyCount == node.config.inputCount ) { 26 | var res = CalculateResult(); 27 | 28 | h.SetResult( res, node.config.topic ); 29 | } 30 | else if(keyCount > node.config.inputCount ) { 31 | node.warn( 32 | (node.config.name !== undefined && node.config.name.length > 0 33 | ? node.config.name : "BooleanLogic") 34 | + " [" + node.config.operation + "]: More than the specified " 35 | + node.config.inputCount + " topics received, resetting. Will not output new value until " + node.config.inputCount + " new topics have been received."); 36 | node.state = {}; 37 | h.DisplayUnkownStatus(); 38 | } else { 39 | node.status({ fill: "yellow", shape: "dot", text: keyCount + " of " + node.config.inputCount + " topics recieved." }); 40 | } 41 | } 42 | }); 43 | 44 | function CalculateResult() { 45 | var res; 46 | 47 | if( node.config.operation == "XOR") { 48 | res = PerformXOR(); 49 | } 50 | else { 51 | // We need a starting value to perform AND and OR operations. 52 | var keys = Object.keys(node.state); 53 | res = node.state[keys[0]]; 54 | 55 | for( var i = 1; i < keys.length; ++i ) { 56 | var key = keys[i]; 57 | res = PerformSimpleOperation( node.config.operation, res, node.state[key] ); 58 | } 59 | } 60 | 61 | return res; 62 | } 63 | 64 | function PerformXOR() 65 | { 66 | // XOR = exclusively one input is true. As such, we just count the number of true values and compare to 1. 67 | var trueCount = 0; 68 | 69 | for( var key in node.state ) { 70 | if( node.state[key] ) { 71 | trueCount++; 72 | } 73 | } 74 | 75 | return trueCount == 1; 76 | } 77 | 78 | function PerformSimpleOperation( operation, val1, val2 ) { 79 | var res; 80 | 81 | if( operation === "AND" ) { 82 | res = val1 && val2; 83 | } 84 | else if( operation === "OR" ) { 85 | res = val1 || val2; 86 | } 87 | else { 88 | node.error( "Unknown operation: " + operation ); 89 | } 90 | 91 | return res; 92 | } 93 | 94 | h.DisplayUnkownStatus(); 95 | } 96 | 97 | RED.nodes.registerType("BooleanLogic",BooleanLogic); 98 | } -------------------------------------------------------------------------------- /boolean-logic/BooleanLogic.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 55 | 56 | 78 | 79 | --------------------------------------------------------------------------------