Functions

Adding two numbers is something that at first I found surprisingly difficult.

Two numbers to add together, say 2 and 4. The answer of course is 6.

But there’s a ‘gottcha’ !

If the numbers are text, i.e. surrounded by inverted commas, the answer is 24.

(i.e. “2” + “4” = “24)

There didn’t seem to be a node to add text things together, so it was time to tackle the Function node.

This means writing code in Javascript, which is not all that intimidating. Thanks are due to Knolleary for outlining this program in answer to a question asked online.

A few notes about the Function node.

  1. it executes Javascript code (plus a few other instructions to allow node-RED to work)
  2. the program in the node is run each time a new message arrives
  3. a message typically and by convention, consists of two entities, msg.topic and msg.payload.
  4. the topic is an identifier and the payload is the data so for example of you want to tell the node that we have say, 3 oranges, then the msg. topic will be oranges, and the msg.payload will 3.
  5. it is also possible to call a message by a name of your own choosing, for example you could have a variable say msg.oranges which contains the value 3.
  6. each mesage arrives at the Function node as a separate event.  Any variables you declare inside the node are reset every time a new message arrives at the node.
  7. input to nodes is serial (one after another) so if we are operating on more than one value in our node we need to have somewhere that is external to, yet accessable from the node, to keep stuff safe until all our different data items are in the node
  8. the program in the node needs to know that all the data it needs to work has been received, otherwise it will send out a meaningless message.  It can solve this by seeing if either of the data variables are still ‘undefined’.

There are in fact quite a few ways to convert a number in text notation to a number. See: https://medium.com/@nikjohn/cast-to-number-in-javascript-using-the-unary-operator-f4ca67c792ce

Below is an example of adding two numbers together. It uses ‘flow’ and ‘undefined’ to make sure all the data is in the node before the addition and output operation is attempted.

So here is the listing of the Javascript program that I put in the Function Node.

First we declare our variables

var v1value = flow.get(“v1value”);
var v2value = flow.get(“v2value”);
var total;

next we listen for the message, examining the topic to see if it’s one of those we need, if it is we save its corresponding payload.

if (msg.topic == “v1topic”) {
    v1value = msg.payload;
    flow.set(“v1value”, msg.payload);
} else if (msg.topic == “v2topic”) {
    v2value = msg.payload;
    flow.set(“v2value”, msg.payload);
}

Before we have received all the variables that we need for the calculation, the contents of the variables will be undefined, otherwise they will contain their valid data. It is important to note that undefined does not mean zero; rather, we simply do not know its value.

So we now check if we have got all the necessary variables for the calculation and that they contain valid data. The expression !== means ‘not equal to’.

if (v1value !== undefined && v2value !== undefined) {

If the above statement is true, we have now received all the values we need to perform the calculation, but we need to ensure that they are not text strings, but actual numbers.

JavaScript uses the + operator for both addition and concatenation.Numbers are added. Strings are concatenated. In the case of minus, divide and multiply, Javascript will change strings into texts automatically, but with addition, it is necessary to explicitly ensure that the numbers are not strings.

If v1value and v2value were text they would be concatenated and not added. To ensure the input is in number format and not text format, we can convert the text to a number.

Numbers in text format are represented by their surrounding inverted commas, so 23 would be written “23”

There are at least two ways to ensure that the values really are numbers and not text before we do the addition. One way would be to send the messages through String nodes using the toFloat operator, and another would be to add a couple of lines of code, i.e.

v1value = (Number(v1value))
v2value = (Number(v2value))

lets now do the job and add them together.
    total = v1value + v2value;

load msg payload with the data to send out
    msg.payload = total;

if you want to wait for a completely new pair of numbers to include, use the following line to set v1value and v2value to undefined so it will wait for two new numbers:

    flow.set([“v1value”, “v2value”],[undefined,undefined]);

then send your result on to the next operation
    return msg;
}

done

Endnote: used above and not documented, flow.set is the description of where a variable will be kept until it’s needed. For example :

flow.set(“v1value”, msg.payload);

puts the contents of msg.payload into flow.v1value

So now here is the full documented program once again, this time as I am actually using it in the central heating application at home.

//Javascript program for node-RED Function Node
// to add two numbers
// Alex. February 2024

// When it receives a message, the Function Node
// initiates the execution of this program.
//
// To add two numbers together,
// this program relies on two separate values.
//
// As a result, the program will run twice,
// retrieving one parameter from each of
// the two messages that trigger its execution.
//
//
// First declare a couple of variables
var total; //result of adding two numbers
var totalr; // ditto but rounded

// In order for the program to function properly,
// two messages must be received by the Function
// Node.
// Each message contains one of the two numbers
// that need to be added together, as well as an
// identifier. (the msg.topic)
// The numbers are stored in a component called
// msg.payload, while the identifiers have their
// names in the corresponding msg.topic.
// It is advisable to select names for each
// msg.topic that are relevant to their purpose.
//
// We need first to give names to the numbers
// that are to be added together.
// v1value has the topic name of 'v1topic' and
// v2value 'v2topic
var v1value = flow.get("v1value");
var v2value = flow.get("v2value");


// At first, neither of them will have a value,
// but one of them will get a value when the
// function node receives one of the variables
// needed for the addition.
// Each message that arrives has it's own topic.
//
// Now let's take a look at the topic section of
// the message that just came to the Function node,
// to check if it matches any of the topics we
// require.
//
// if it is then we save its corresponding
// payload to the appropriate flow value

if (msg.topic == "v1topic") {
v1value = msg.payload;

// the next line ensures the input is a number
    // and not a string
v1value = (Number(v1value));
// next we save the newly aquired number in a
    // variable called flow.v1value so it can be
    // recalled when the second number arrives.
flow.set("v1value", v1value);
} else if (msg.topic == "v2topic") {
// otherwise we test the message for being the
    // other data that we need, and which is
// identified by the topic name v2topic
v2value = msg.payload;
v2value = (Number(v2value))
flow.set("v2value", v2value);
}

// Now we test to see if we have received both
// the messages we need to calculate the sum.
// If one of the messages has not been received,
// its storage location will remain as undefined.
// In the next line !== means not equal to
// and && means and

if (v1value !== undefined && v2value !== undefined) {
// So, assuming we have now received all (both)
    // of the messages, lets add them together.
total = v1value + v2value;

// If required, we can reset 'v1value’ and
    // 'v2value' to undefined so the Function node
    // will wait for two new numbers:
flow.set("v1value",undefined);
flow.set("v2value",undefined);


// round if you like, so we don't get silly
    // precision issues
totalr = Math.round(total * 1000) / 1000

//load msg payload with the data to send out
msg.payload = totalr;

//then send your result on to the next operation
return msg;
}

or, without the comments:

var total;
var totalr;
var v1value = flow.get("v1value");
var v2value = flow.get("v2value");
if (msg.topic == "v1topic") {
v1value = msg.payload;
v1value = (Number(v1value));
flow.set("v1value", v1value);
} else if (msg.topic == "v2topic") {
v2value = msg.payload;
v2value = (Number(v2value))
flow.set("v2value", v2value);
}
if (v1value !== undefined && v2value !== undefined) {
total = v1value + v2value;
flow.set("v1value",undefined);
flow.set("v2value",undefined);
    totalr = Math.round(total * 1000) / 1000
return msg;
}

and just for good measure, here is a little flow to test it with:

[
{
"id": "01e3fc6f5cf3b866",
"type": "inject",
"z": "0dd05071a7cad01a",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "v1topic",
"payload": "1",
"payloadType": "num",
"x": 220,
"y": 580,
"wires": [
[
"5e90f0bcc9c721ea"
]
]
},
{
"id": "8cd5297888ddd6dd",
"type": "inject",
"z": "0dd05071a7cad01a",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "v2topic",
"payload": "4",
"payloadType": "str",
"x": 220,
"y": 840,
"wires": [
[
"5e90f0bcc9c721ea"
]
]
},
{
"id": "2475a44a53ee0c34",
"type": "inject",
"z": "0dd05071a7cad01a",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "v1topic",
"payload": "2",
"payloadType": "str",
"x": 220,
"y": 640,
"wires": [
[
"5e90f0bcc9c721ea"
]
]
},
{
"id": "df10a78fd1a46ab5",
"type": "inject",
"z": "0dd05071a7cad01a",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "v2topic",
"payload": "3",
"payloadType": "num",
"x": 220,
"y": 780,
"wires": [
[
"5e90f0bcc9c721ea"
]
]
},
{
"id": "c721926e0cc62f0f",
"type": "inject",
"z": "0dd05071a7cad01a",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "v2topic",
"payload": "5",
"payloadType": "num",
"x": 220,
"y": 900,
"wires": [
[
"5e90f0bcc9c721ea"
]
]
},
{
"id": "b8b04dd63396c176",
"type": "debug",
"z": "0dd05071a7cad01a",
"name": "debug 13",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": true,
"complete": "true",
"targetType": "full",
"statusVal": "payload",
"statusType": "auto",
"x": 700,
"y": 700,
"wires": []
},
{
"id": "5e90f0bcc9c721ea",
"type": "function",
"z": "0dd05071a7cad01a",
"name": "function",
"func": "//Javascript program for node-RED Function Node to add two numbers\n// Alex. February 2024\n//\n// Each time a message arrives at the Function Node this program runs.\n// First declare a couple of variables\nvar total; //result of adding two numbers\nvar totalr; // ditto but rounded\n\n// In order for the program to function properly, \n// two messages must be received by the Function Node. \n// Each message contains one of the two numbers that need to be added together,\n// as well as an identifier. \n// The numbers are stored in a component called msg.payload, \n// while the identifiers have their names in the corresponding msg.topic. \n// It is advisable to select names for each msg.topic that are relevant \n// to their purpose.\n//\n// We need first to give names to the numbers that are to be added together.\n// v1value has the topic name of 'v1topic' and v2value 'v2topic\nvar v1value = flow.get(\"v1value\");\nvar v2value = flow.get(\"v2value\");\n\n// At first, neither of them will have a value, \n// but one of them will get a value when the function node \n// receives one of the variables needed for the addition.\n// Each message that arrives has it's own topic.\n// Now let's take a look at the topic section of the message \n// that just came to the Function node, \n// to check if it matches any of the topics we require.\n//\n//if it is then we save its corresponding payload to the \n// appropriate flow value \n\nif (msg.topic == \"v1topic\") {\n v1value = msg.payload;\n // the next line ensures the input is a number and not a string\n v1value = (Number(v1value));\n // next we save the newly aquired number in a variable \n // called flow.v1value so it can be recalled when the second number arrives.\n flow.set(\"v1value\", v1value);\n} else if (msg.topic == \"v2topic\") {\n // otherwise we test the message for being the other data we need\n // identified by the topic name v2topic\n v2value = msg.payload;\n v2value = (Number(v2value))\n flow.set(\"v2value\", v2value);\n}\n\n// Now we test to see if we have received all the messages\n// we need to perform the sum.\n// If one of the messages has not been received, \n// its storage location will remain undefined.\n// In the next line !== means not equal to and && means and\n\nif (v1value !== undefined && v2value !== undefined) {\n // So if we have now received all (both) of the messages,so lets go!\n total = v1value + v2value;\n\n // If required, we can reset 'v1value' and 'v2value' to undefined \n // so the Function node will wait for two new numbers:\n flow.set(\"v1value\",undefined);\n flow.set(\"v2value\",undefined);\n\n // round if you like, so we don't get silly precision issues\n totalr = Math.round(total * 1000) / 1000\n\n //load msg payload with the data to send out\n msg.payload = totalr;\n //then send your result on to the next operation\n return msg;\n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 520,
"y": 700,
"wires": [
[
"b8b04dd63396c176"
]
]
}
]

Copy the above, paste it into a node-red page and play with it !