505 wiersze
22 KiB
JavaScript
505 wiersze
22 KiB
JavaScript
/**
|
|
* Copyright 2018 Bart Butenaers
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
**/
|
|
module.exports = function(RED) {
|
|
|
|
function CalculatorNode(config) {
|
|
RED.nodes.createNode(this, config);
|
|
this.inputMsgField = config.inputMsgField;
|
|
this.outputMsgField = config.outputMsgField;
|
|
this.operation = config.operation;
|
|
this.constant = config.constant;
|
|
this.round = config.round;
|
|
this.decimals = config.decimals;
|
|
|
|
var node = this;
|
|
|
|
// Test if an object contains the specified property (handles multiple levels like obj.a.b.c).
|
|
// (See https://www.customd.com/articles/37/checking-javascript-objects-for-existence-of-a-nested-element )
|
|
function objectHasProperty(obj, prop) {
|
|
var parts = prop.split('.');
|
|
for (var i = 0, l = parts.length; i < l; i++) {
|
|
var part = parts[i];
|
|
if ((obj !== null) && (typeof(obj) === 'object') && (part in obj)) {
|
|
obj = obj[part];
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// https://www.jacklmoore.com/notes/rounding-in-javascript/
|
|
function round(value, decimals) {
|
|
return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
|
|
}
|
|
|
|
// Check whether the input is correct
|
|
function checkInput(checkNumber, inputValue, minCount, maxCount) {
|
|
var values = [];
|
|
var numbers = [];
|
|
var isArray = Array.isArray(inputValue);
|
|
|
|
if (!isArray) {
|
|
if (minCount > 1) {
|
|
node.error("The msg." + this.inputMsgField + " should be an array");
|
|
return null;
|
|
}
|
|
|
|
// Seems we have enough with an array containing a single item
|
|
values.push(inputValue);
|
|
}
|
|
else {
|
|
// Let's check all the numbers in the array
|
|
values = inputValue;
|
|
}
|
|
|
|
if (maxCount && minCount === maxCount) {
|
|
if (values.length !== minCount) {
|
|
if (node.constant) {
|
|
node.error("The msg." + this.inputMsgField + " should be an array with " + (--minCount) + " numbers (because constant value specified)");
|
|
}
|
|
else {
|
|
node.error("The msg." + this.inputMsgField + " should be an array with " + minCount + " numbers");
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
else {
|
|
if (values.length < minCount) {
|
|
if (node.constant) {
|
|
node.error("The msg." + this.inputMsgField + " should be an array with minimum " + (--minCount) + " numbers (because constant value specified)");
|
|
}
|
|
else {
|
|
node.error("The msg." + this.inputMsgField + " should be an array with minimum " + minCount + " numbers");
|
|
}
|
|
return null;
|
|
}
|
|
if (maxCount && values.length > maxCount) {
|
|
if (node.constant) {
|
|
node.error("The msg." + this.inputMsgField + " should be an array with maximum " + (--maxCount) + " numbers (because constant value specified)");
|
|
}
|
|
else {
|
|
node.error("The msg." + this.inputMsgField + " should be an array with maximum " + maxCount + " numbers");
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < values.length; i++) {
|
|
var number = parseFloat(values[i]);
|
|
if (checkNumber && isNaN(number)){
|
|
node.error("The msg." + this.inputMsgField + " should only contain number(s)");
|
|
return null;
|
|
}
|
|
numbers.push(number);
|
|
}
|
|
|
|
return numbers;
|
|
}
|
|
|
|
node.on("input", function(msg) {
|
|
var operation = node.operation;
|
|
var numbers = [];
|
|
var msgKeyValue;
|
|
var count;
|
|
var result;
|
|
|
|
if (!objectHasProperty(msg, node.inputMsgField)) {
|
|
node.error("The input message doesn't have have a msg." + node.inputMsgField + " field")
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
msgKeyValue = RED.util.getMessageProperty(msg, node.inputMsgField);
|
|
}
|
|
catch(err) {
|
|
node.error("The msg." + node.inputMsgField + " field can not be read");
|
|
return;
|
|
}
|
|
|
|
// Check whether the input data is an arry.
|
|
// Remark: we won't take into account the constant value (below)
|
|
var isArray = Array.isArray(msgKeyValue);
|
|
|
|
// When a constant value is specified, this will be appended to the end of the array
|
|
if (node.constant) {
|
|
if (!isArray) {
|
|
// To be able to append the constantValue (as second value), we need to convert the number to an array with one number
|
|
msgKeyValue = [ msgKeyValue ];
|
|
}
|
|
|
|
msgKeyValue.push(parseFloat(node.constant));
|
|
}
|
|
|
|
if (!operation || operation === "") {
|
|
operation = msg.operation;
|
|
|
|
if (!operation) {
|
|
node.error("An msg.operation should be supplied");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
switch(operation) {
|
|
case "abs":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.abs(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "acos":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.acos(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "acosh":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.acosh(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "asin":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.asin(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "asinh":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.asinh(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "atan":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.atan(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "atanh":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.atanh(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "avg":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
result = numbers.reduce(function(a, b) { return a + b; });
|
|
result = result / numbers.length;
|
|
break;
|
|
case "cbrt":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.cbrt(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "ceil":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.ceil(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "cos":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.cos(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "cosh":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.cosh(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "dec":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = a - 1;
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "div":
|
|
numbers = checkInput(true, msgKeyValue, 2);
|
|
if (!numbers) return;
|
|
|
|
if (node.constant === 0) {
|
|
node.error("The constant value not be 0 (as denominator)");
|
|
return null;
|
|
}
|
|
|
|
for (var i = 1; i < numbers.length; i++) {
|
|
if (numbers[i] === 0) {
|
|
node.error("The msg." + node.inputMsgField + " should only contain non-zero number(s) for the denominators");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
result = numbers.reduce(function(a, b) { return a / b; });
|
|
break;
|
|
case "exp":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.exp(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "inc":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = a + 1;
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "floor":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.floor(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "log":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.log(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "log10":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.log10(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "max":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
result = numbers.reduce(function(a, b) { return (a > b) ? a : b });
|
|
break;
|
|
case "min":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
result = numbers.reduce(function(a, b) { return (a > b) ? b : a });
|
|
break;
|
|
case "mult":
|
|
numbers = checkInput(true, msgKeyValue, 2);
|
|
if (!numbers) return;
|
|
result = numbers.reduce(function(a, b) { return a * b; });
|
|
break;
|
|
case "mod":
|
|
numbers = checkInput(true, msgKeyValue, 2, 2);
|
|
if (!numbers) return;
|
|
result = numbers[0] % numbers[1];
|
|
break;
|
|
case "pow":
|
|
numbers = checkInput(true, msgKeyValue, 2, 2);
|
|
if (!numbers) return;
|
|
result = Math.pow(numbers[0], numbers[1]);
|
|
break;
|
|
case "rand":
|
|
// When the payload contains an array, then we will generate an array (with same length) of random numbers.
|
|
// Regardless of the content of the content of the payload array, since we don't need it for our calculations ...
|
|
if (isArray) {
|
|
numbers = new Array(msgKeyValue.length);
|
|
}
|
|
else {
|
|
numbers = new Array(1);
|
|
}
|
|
|
|
// Remark: 'forEach' does not work on an un-initialized array
|
|
for (var j = 0; j < numbers.length; j++) {
|
|
numbers[j] = Math.random();
|
|
}
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "randb":
|
|
numbers = checkInput(true, msgKeyValue, 2, 2);
|
|
if (!numbers) return;
|
|
result = Math.floor(Math.random() * (numbers[1] - numbers[0] + 1)) + numbers[0];
|
|
break;
|
|
case "randa":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
result = numbers[Math.floor(Math.random() * numbers.length)];
|
|
break;
|
|
case "len":
|
|
numbers = checkInput(false, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
result = numbers.length;
|
|
break;
|
|
// Implementation of sorting, since the sort node does not behave correctly at this moment.
|
|
// (see https://github.com/akashtalole/node-red-contrib-sort/issues/1)
|
|
case "sorta":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.sort(function(a, b) {
|
|
return a - b;
|
|
});
|
|
result = numbers;
|
|
break;
|
|
case "sortd":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.sort(function(a, b) {
|
|
return b - a;
|
|
});
|
|
result = numbers;
|
|
break;
|
|
case "range":
|
|
numbers = checkInput(true, msgKeyValue, 2, 2);
|
|
if (!numbers) return;
|
|
//numbers[0] = Math.trunc(numbers[0]);
|
|
//numbers[1] = Math.trunc(numbers[1]);
|
|
result = [];
|
|
for (var k = numbers[0]; k <= numbers[1]; k++) {
|
|
result.push(k);
|
|
}
|
|
break;
|
|
case "dist":
|
|
numbers = checkInput(true, msgKeyValue, 2);
|
|
if (!numbers) return;
|
|
numbers.sort();
|
|
result = numbers[numbers.length - 1] - numbers[0];
|
|
break;
|
|
case "rdec":
|
|
numbers = checkInput(true, msgKeyValue, 2, 2);
|
|
if (!numbers) return;
|
|
// See http://www.jacklmoore.com/notes/rounding-in-javascript/
|
|
result = Number(Math.round(numbers[0] + 'e' + numbers[1]) + 'e-' + numbers[1]);
|
|
break;
|
|
case "round":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.round(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "sin":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.sin(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "sinh":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.sinh(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "sqrt":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.sqrt(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "sum":
|
|
numbers = checkInput(true, msgKeyValue, 2);
|
|
if (!numbers) return;
|
|
result = numbers.reduce(function(a, b) { return a + b; });
|
|
break;
|
|
case "sub":
|
|
numbers = checkInput(true, msgKeyValue, 2);
|
|
if (!numbers) return;
|
|
result = numbers.reduce(function(a, b) { return a - b; });
|
|
break;
|
|
case "tan":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.tan(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "tanh":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.tanh(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
case "trunc":
|
|
numbers = checkInput(true, msgKeyValue, 1);
|
|
if (!numbers) return;
|
|
numbers.forEach(function(a, index) {
|
|
numbers[index] = Math.trunc(a);
|
|
});
|
|
result = (isArray) ? numbers : numbers[0];
|
|
break;
|
|
default:
|
|
node.error("The msg.operation contains an unsupported operation '" + operation + "'");
|
|
return null;
|
|
}
|
|
|
|
// If required, round the result to the specified number of decimals
|
|
if (node.round) {
|
|
if (Array.isArray(result)) {
|
|
for (var j = 0; j < result.length; j++) {
|
|
result[j] = round(result[j], node.decimals);
|
|
}
|
|
}
|
|
else {
|
|
result = round(result, node.decimals);
|
|
}
|
|
}
|
|
|
|
RED.util.setMessageProperty(msg, node.outputMsgField, result, true);
|
|
|
|
node.send(msg);
|
|
});
|
|
}
|
|
|
|
RED.nodes.registerType("calculator", CalculatorNode);
|
|
}
|