Defining and reuse a "classic" JS function

Hello !
I'm a bit lost in node-red, maybe I didn't wired the right nodes together... :sweat_smile:

The context
I'm working with LoRa sensors that send hexa code in the payload. Those datas are specific informations based on the "port" they use : 5 is battery info, 9 is an alive message, 30 is opening counter + temperature + humidity. So each one must be treated differently.
As we're three to work on the project, I wanted to have a clear flow, so everyone could see what happens.
I have a NR Function node by port used and each hexa data in the payload is converted specifically.

The question
As I'm lazy, like every programmer I guess, I wanted to create some hex2bin, hex2dec, hex2float functions and reuse them in my flow, without redefining them each time. They should be called in every Function node.
But I didn't find how to do this... And as Node-Red is using the concept of "Function", it's hard to find answers on Internet for "JS function" ! :slight_smile:

Create 3 subflows for hex2bin, hex2dec & hex2float

A subflow is kinda like a reusable function.

If you wanted to you could create just one subflow (that does all three operations) and you specify which operation by topic or by a subflow property.

Have a play, see what best suits your needs.

Or a different approach, write them as a JavaScript module and make it available for use in functions through functionGlobalContext, see the configuration documentation

2 Likes

But I didn't find how to do this... And as Node-Red is using the concept of " Function ", it's hard to find answers on Internet for " JS function " !

You can search for javascript hex2bin, you will find something like

function hex2bin(hex){
    return ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8);
}

For use in node-red, use a function node, something like:

function hex2bin(hex){
    return ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8);
}

input = msg.payload
output = hex2bin(input)

return {payload:output} 

I already have the functions, I just want to know how to reuse them in the whole flow, without copy/paste them. :slight_smile:
Like an include, if you prefer.

I'll give a try to a subflow and the functionGlobalContext and let you know. :slight_smile:
A lot of reading for this sunday !

Have a look here:

Ah ok misunderstood then subflow or functionGlobalContext are good ways to go.

1 Like

Works well !
I've done a mix with a subflow. :slight_smile:
My subflow, called "JS classic function", has an Inject node "trigger-on-start" and 2 functions (hex2bin and hex2dec) wired to it, so it's executed at 0.1s after Node-Red start and the functions are in memory.
I just had tyo had my Subflow to the flow to enable the classic JS functions.

image
image

Here's the code, if somebody is trying to do the same. :slight_smile:

[{"id":"f9586397.5973","type":"subflow","name":"JS functions","info":"","category":"","in":[],"out":[],"env":[],"color":"#DDAA99"},{"id":"7d7eee51.7878a8","type":"function","z":"f9586397.5973","name":"hex2dec","func":"global.set('hex2dec', function(hexa) {\n    return parseInt(hexa, 16);\n}, 'memoryOnly');\n","outputs":1,"noerr":0,"x":400,"y":80,"wires":[[]]},{"id":"2012dbe6.92df34","type":"function","z":"f9586397.5973","name":"hex2bin","func":"global.set('hex2bin', function(hex) {\n    return (\"00000000\" + (parseInt(hex, 16)).toString(2)).substr(-8);\n}, 'memoryOnly');","outputs":1,"noerr":0,"x":400,"y":40,"wires":[[]]},{"id":"df964a73.694328","type":"inject","z":"f9586397.5973","name":"Trigger on start","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":140,"y":40,"wires":[["2012dbe6.92df34","7d7eee51.7878a8"]]},{"id":"35ca0b72.bb35ec","type":"tab","label":"Test functions","disabled":false,"info":""},{"id":"92ec9209.762ac","type":"inject","z":"35ca0b72.bb35ec","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":120,"wires":[["3c377357.2eb52c"]]},{"id":"3c377357.2eb52c","type":"function","z":"35ca0b72.bb35ec","name":"Test 2 functions","func":"msg.payload = {\n    bin: global.get('hex2bin', 'memoryOnly')(\"0x777\"), \n    dec: global.get('hex2dec', 'memoryOnly')(\"0x777\") \n}\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":120,"wires":[["867de411.01ff3"]]},{"id":"ce6ea1e.e1e90e","type":"subflow:f9586397.5973","z":"35ca0b72.bb35ec","name":"JS Classic functions","env":[],"x":170,"y":60,"wires":[]},{"id":"867de411.01ff3","type":"debug","z":"35ca0b72.bb35ec","name":"Full msg","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":520,"y":120,"wires":[]}]

I don't think you need the subflow, since you use it to set the global functions once.
I'd use a function node instead.

That is an odd implementation of a subflow, but if it works, it works :wink:

"Normally" you would add an input and an output and route your message through the "node" (read subflow).

The subflow is just to have a "cleaner" view. :slightly_smiling_face:
Our first flow is very messy for the moment as we're new to Node-Red and we're trying to sell our project to our Directors. So it's straight to the result instead of a beautiful and well think code. There's a lot of functions, templates, gauges and other nodes. Maybe we should think differently...
As our LoRa sensor aren't well known, I have to write a full "deciphering" module. So it will probably looks way different at the end !

Anyway, many thanks for your help !

Similar to my approach here: Importing function from another node

Just for completeness. :slightly_smiling_face:

Hello.

I'm also trying to reuse functions by recalling them from the context store.
Example:

[{"id":"728cb92b07d335fb","type":"inject","z":"866282f4.d0922","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":440,"y":2840,"wires":[["487bcdf41f1bddf6"]]},{"id":"487bcdf41f1bddf6","type":"function","z":"866282f4.d0922","name":"func test","func":"\nglobal.set('lockCountdown', function lockCountdown() {\n    //node = this.node\n    var details = context.get('lockCountdown');\n    //node.warn(details);\n    if(details.command === 'start'){\n        let lockCountdownValue = context.get('lockCountdownValue');\n        let min = 0;\n        if(lockCountdownValue > min){\n            lockCountdownValue--\n            context.set('lockCountdownValue',lockCountdownValue);\n            context.t5 = setTimeout(lockCountdown, 1000);\n            this.status({fill:\"green\",shape:\"dot\",text:\"lockCountdownValue trigger in: \" + context.get('lockCountdownValue') + \" s\"});  \n      } else if(lockCountdownValue === min){\n            let arg = 'lock'\n            //lockMC(arg);\n            node.send(msg)\n            context.set('lockCountdown', {\"command\":'stop', \"triggered\": false});\n            this.status({fill:\"red\",shape:\"dot\",text:\"machine lock trigger sent\" });\n        } else {\n          //node.warn('lockCountdown() send warning: this should not happen');\n          //add error catching\n        }\n    }\n    else if(details.command  === 'stop'){\n        let command = details.command;\n        context.set('lockCountdown', {\"command\":command, \"triggered\": false});\n        clearTimeout(context.t5);  // timer used to trigger dlockCountdown()\n        this.status({fill:\"yellow\",shape:\"square\",text:\"lockCountdownValue trigger in: \" + context.get('lockCountdownValue') + \" s\"});\n        //node.warn(context.get('lockCountdown').triggered)\n    }\n    else if(details.command  === 'clear'){\n        let command = details.command;\n        context.set(['lockCountdown','lockCountdownValue'], [{\"command\":command, \"triggered\": false},undefined]);\n        lockCountdownTimer();\n        clearTimeout(context.t5);  // timer used to trigger lockCountdown()\n        this.status({fill:\"blue\",shape:\"square\",text:\"lockCountdownValue trigger in: \" + context.get('lockCountdownValue') + \" s\"});\n        //node.warn(context.get('lockCountdown').triggered)\n    }\n}\n)\n\ncontext.set('lockCountdown',{\"command\":\"start\",\"triggered\":false})\ncontext.set('lockCountdownValue',5)\ncontext.set('testFunction',\"return \" + String(global.get('lockCountdown')))\nfunc = new Function(context.get('testFunction'))();\nfunc()\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":2840,"wires":[["75358f110c2664d5"]]},{"id":"75358f110c2664d5","type":"debug","z":"866282f4.d0922","name":"1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":770,"y":2840,"wires":[]}]

I managed to get node.status to "work" by replacing it with this.status. When triggered I'm getting

TypeError: this.status is not a function

but then it starts to work when it's recalled again.
As this is for information only I can accept this behaviour (I would like to still understand what's happening)
The problem is that I cannot get node.send() or node.,warn() to work as I'm getting:
ReferenceError: node is not defined
Being able to reuse some more complex functions will be an advantage in my opinion. The subflow route is not suitable as I would like to recall these functions inside a function node.

The behavior you see effects from the way setInterval calls the provided function - which is different from your way: From the source of the function node:

            setInterval: function() {
                var func = arguments[0];
                var timerId;
                arguments[0] = function() {
                    try {
                        func.apply(node,arguments);
                    } catch(err) {
                        node.error(err,{});
                    }
                };
                timerId = setInterval.apply(node,arguments);
                node.outstandingIntervals.push(timerId);
                return timerId;
            },

You can easily spot the difference: Your launching call is

func = new Function(context.get('testFunction'))();
func();

whereas the following calls initiated via setInterval call your global function as

timerId = setInterval.apply(node,arguments);

defining node as the this argument to the scope. The solution is straight forward:

func.apply(node);

You'll still get a "ReferenceError: node is not defined" due to the fact that you're referencing node in line 17.

            node.send(msg)

You may fix this by replacing node with this as well:

            this.send(msg)

Final remark:
Please open just a new topic next time if you need some support. This is preferred rather than continuing a (quite ancient) discussion closed long time ago.

1 Like