├── 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 | 
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 |
--------------------------------------------------------------------------------