浏览代码

Initial commit

master
Massimo 5 年前
当前提交
ce0105944c
共有 11 个文件被更改,包括 463 次插入0 次删除
  1. +2
    -0
      .gitattributes
  2. +21
    -0
      LICENSE
  3. +32
    -0
      README.md
  4. +37
    -0
      boolean-logic/BDebug.html
  5. +24
    -0
      boolean-logic/BDebug.js
  6. +106
    -0
      boolean-logic/BooleanLogic.html
  7. +96
    -0
      boolean-logic/BooleanLogic.js
  8. +39
    -0
      boolean-logic/Invert.html
  9. +25
    -0
      boolean-logic/Invert.js
  10. +60
    -0
      boolean-logic/NodeHelper.js
  11. +21
    -0
      package.json

+ 2
- 0
.gitattributes 查看文件

@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

+ 21
- 0
LICENSE 查看文件

@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Massimo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

+ 32
- 0
README.md 查看文件

@@ -0,0 +1,32 @@
# node-red-contrib-boolean-logic
[Node-RED](http://nodered.org/) nodes to easily perform boolean logic.

## The problem
[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).

## A solution
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.

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.

What I came up with are the following nodes.
* BooleanLogic: Can perform AND, OR and XOR operations on ```msg.payload``` on any number of topics.
* Invert: Inverts the ```msg.payload```, e.g. true -> false.
* Debug: A debug node that displays the status direcly in the editor, making it easier to see the boolean value at a specific point.

All nodes attempts to convert the incoming ```msg.payload``` to a boolean value according to these rules:
* Boolean values are taken as-is.
* For numbers, 0 evaluates to false, all other numbers evaluates to true.
* 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.

### BooleanLogic
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.

## Example
![Example](http://i.imgur.com/m2s6JRl.png)

## Version history
* 0.0.1 First release
* 0.0.2
* Changed status indicators from dot to rings for false-values.
* Reworked the conversion of input values to be consistent between numbers and strings with numeric meaning.

+ 37
- 0
boolean-logic/BDebug.html 查看文件

@@ -0,0 +1,37 @@
<!--
Licensed under the MIT license, see LICENSE file.
Author: Per Malmberg (https://github.com/PerMalmberg)
-->

<script type="text/javascript">
RED.nodes.registerType('BDebug',{
category: 'boolean logic',
color: '#C0DEED',
defaults: {
name: {
value: "Debug"
}
},
inputs:1,
outputs:0,
icon: "debug.png",
label:
function() {
return this.name||"Debug";
},
paletteLabel: function() {
return "Debug";
}
});
</script>

<script type="text/x-red" data-template-name="BDebug">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>

<script type="text/x-red" data-help-name="BDebug">
<p>A debug node specifically targeting boolean logic.</p>
</script>

+ 24
- 0
boolean-logic/BDebug.js 查看文件

@@ -0,0 +1,24 @@
// Licensed under the MIT license, see LICENSE file.
// Author: Per Malmberg (https://github.com/PerMalmberg)
module.exports = function(RED) {
function BDebug(config) {
RED.nodes.createNode(this,config);
this.config = config;
var node = this;
var NodeHelper = require('./NodeHelper.js');
var h = new NodeHelper( node );
this.on('input', function(msg) {
var topic = msg.topic;
var payload = msg.payload;
if( topic !== undefined && payload !== undefined ) {
h.DisplayStatus( h.ToBoolean( payload ) );
}
});

h.DisplayUnkownStatus();
}
RED.nodes.registerType("BDebug",BDebug);
}

+ 106
- 0
boolean-logic/BooleanLogic.html 查看文件

@@ -0,0 +1,106 @@
<!--
Licensed under the MIT license, see LICENSE file.
Author: Per Malmberg (https://github.com/PerMalmberg)
-->

<script type="text/javascript">
RED.nodes.registerType('BooleanLogic',{
category: 'boolean logic',
color: '#C0DEED',
defaults: {
name: {
value: ""
},
operation: {
value: "AND",
required: true,
validate:
function(v) {
return v !== undefined && v.length > 0;
}
},
inputCount: {
value: 2,
required: true,
validate:
function(v) {
return !isNaN( parseInt( v ) ) && parseInt( v ) >= 2;
}
},
topic: {
value: "result",
required: true,
validate:
function(v) {
return v !== undefined && v.length > 0;
}
}
},
inputs:1,
outputs:1,
icon: "serial.png",
label:
function() {
var label = this.operation + " [" + this.inputCount + "]";
if( this.name !== undefined && this.name.length > 0 ) {
label = label + " (" + this.name + ")";
}
return label;
},
paletteLabel: function() {
return "Boolean logic";
}
});
</script>

<script type="text/x-red" data-template-name="BooleanLogic">
<div class="form-row">
<label for="node-input-operation"><i class="icon-tag"></i> Operation</label>
<select type="text" id="node-input-operation" placeholder="Operation">
<option value="AND">And</option>
<option value="OR">Or</option>
<option value="XOR">Exclusive Or</option>
</select>
</div>
<div class="form-row">
<label for="node-input-inputCount"><i class="icon-tag"></i> Number of topics</label>
<input type="text" id="node-input-inputCount" placeholder="Number of topics to consider">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="icon-tag"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="Topic">
</div>
</script>

<script type="text/x-red" data-help-name="BooleanLogic">
<p>A node that performs Boolean logic on the incoming payloads.<br/>
Select the operation in the settings.<br/>
Changing the topic is usually only needed when chaining multiple boolean nodes after each other becuse the topics will then all be the same when delivered to the nodes further down the chain.<br/>
<br/>
The node expects a fixed number of topics (configured in the settings) on which it will operate. It will only output a value
when it has seen the expected number of topics. If it ever sees more than the configured number of topics it will log a message then reset its state and start over.<br/>
<br/>
Example:<br/>
The node has been configured for two topics, with the operation OR.
<br/>
<ol>
<li>Topic 'A' with value false arrives - only one topic seen so no output.</li>
<li>Topic 'B' with value true arrives - two topics seen so the node evaluates the received topics and outputs true.</li>
<li>Topic 'C' arrives. This is the third topic which is one more than the configured number so the node resets and will not output another value until
two new topics have been received. Note that when resetting it does not output any value.</li>
</ol>
<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>
<li>For numbers, 0 evaluates to false, all other numbers evaluates to true.</li>
<li>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.</li>
</ol>
<br>
The XOR operation operates in a one, and only one mode, i.e. (A ^ B) ^ C ... ^ n
</p>
</script>

+ 96
- 0
boolean-logic/BooleanLogic.js 查看文件

@@ -0,0 +1,96 @@
// Licensed under the MIT license, see LICENSE file.
// Author: Per Malmberg (https://github.com/PerMalmberg)
module.exports = function(RED) {
function BooleanLogic(config) {
RED.nodes.createNode(this,config);
this.config = config;
this.state = {};
var node = this;
var NodeHelper = require('./NodeHelper.js');
var h = new NodeHelper( node );
this.on('input', function(msg) {
var topic = msg.topic;
var payload = msg.payload;
if( topic !== undefined && payload !== undefined ) {
var value = h.ToBoolean( payload );
var state = node.state;
state[topic] = value;
// Do we have as many inputs as we expect?
var keyCount = Object.keys(state).length;

if( keyCount == node.config.inputCount ) {
var res = CalculateResult();
h.SetResult( res, node.config.topic );
}
else if(keyCount > node.config.inputCount ) {
node.warn(
(node.config.name !== undefined && node.config.name.length > 0
? node.config.name : "BooleanLogic")
+ " [" + node.config.operation + "]: 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 = {};
h.DisplayUnkownStatus();
}
}
});
function CalculateResult() {
var res;
if( node.config.operation == "XOR") {
res = PerformXOR();
}
else {
// We need a starting value to perform AND and OR operations.
var keys = Object.keys(node.state);
res = node.state[keys[0]];
for( var i = 1; i < keys.length; ++i ) {
var key = keys[i];
res = PerformSimpleOperation( node.config.operation, res, node.state[key] );
}
}
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.state ) {
if( node.state[key] ) {
trueCount++;
}
}
return trueCount == 1;
}
function PerformSimpleOperation( operation, val1, val2 ) {
var res;
if( operation === "AND" ) {
res = val1 && val2;
}
else if( operation === "OR" ) {
res = val1 || val2;
}
else {
node.error( "Unknown operation: " + operation );
}
return res;
}
h.DisplayUnkownStatus();
}
RED.nodes.registerType("BooleanLogic",BooleanLogic);
}

+ 39
- 0
boolean-logic/Invert.html 查看文件

@@ -0,0 +1,39 @@
<!--
Licensed under the MIT license, see LICENSE file.
Author: Per Malmberg (https://github.com/PerMalmberg)
-->

<script type="text/javascript">
RED.nodes.registerType('Invert',{
category: 'boolean logic',
color: '#C0DEED',
defaults: {
name: {
value: "Invert"
}
},
inputs:1,
outputs:1,
icon: "serial.png",
label:
function() {
return this.name||"Invert";
},
paletteLabel: function() {
return "Invert";
}
});
</script>

<script type="text/x-red" data-template-name="Invert">
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>

<script type="text/x-red" data-help-name="Invert">
<p>A node that emits the inverted input value.<br/>
The status indicator represents the output value.
</p>
</script>

+ 25
- 0
boolean-logic/Invert.js 查看文件

@@ -0,0 +1,25 @@
// Licensed under the MIT license, see LICENSE file.
// Author: Per Malmberg (https://github.com/PerMalmberg)
module.exports = function(RED) {
function Invert(config) {
RED.nodes.createNode(this,config);
this.config = config;
var node = this;
var NodeHelper = require('./NodeHelper.js');
var h = new NodeHelper( node );
this.on('input', function(msg) {
var topic = msg.topic;
var payload = msg.payload;
if( topic !== undefined && payload !== undefined ) {
h.SetResult( !h.ToBoolean( payload ), topic );
}
});
h.DisplayUnkownStatus();
}
RED.nodes.registerType("Invert",Invert);
}

+ 60
- 0
boolean-logic/NodeHelper.js 查看文件

@@ -0,0 +1,60 @@
// Licensed under the MIT license, see LICENSE file.
// Author: Per Malmberg (https://github.com/PerMalmberg)

var NodeHelper = function( node ) {
var myNode = node;
var self = this;
var decimal = /^\s*[+-]{0,1}\s*([\d]+(\.[\d]*)*)\s*$/
this.ToBoolean = function( value ) {
var res = false;

if (typeof value === 'boolean') {
res = value;
}
else if( typeof value === 'number' || typeof value === 'string' ) {
// Is it formated as a decimal number?
if( decimal.test( value ) ) {
var v = parseFloat( value );
res = v != 0;
}
else {
res = value.toLowerCase() === "true";
}
}
return res;
};

this.DisplayStatus = function( value ) {
myNode.status(
{
fill: value ? "green" : "red",
shape: value ? "dot" : "ring",
text: value ? "true" : "false"
}
);
};
this.DisplayUnkownStatus = function() {
myNode.status(
{
fill: "gray",
shape: "dot",
text: "Unknown"
});
};
this.SetResult = function( value, optionalTopic ) {
self.DisplayStatus( value );
var msg = {
topic: optionalTopic === undefined ? "result" : optionalTopic,
payload: value
};

myNode.send(msg);
};
};
module.exports = NodeHelper;

+ 21
- 0
package.json 查看文件

@@ -0,0 +1,21 @@
{
"name": "node-red-contrib-boolean-logic",
"version": "0.0.3",
"description": "A set of Node-RED nodes for boolean logic",
"author": "Per Malmberg (https://github.com/PerMalmberg)",
"dependencies": {
},
"keywords": [ "node-red", "boolean", "logic" ],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/PerMalmberg/node-red-contrib-boolean-logic"
},
"node-red": {
"nodes": {
"BooleanLogic": "boolean-logic/BooleanLogic.js",
"Invert": "boolean-logic/Invert.js",
"BDebug": "boolean-logic/BDebug.js"
}
}
}

正在加载...
取消
保存