Help with a function node


HI All

I am busy getting to grips with JS and node-red and am studiying several sources to get up to speed, but I am after a little help with writing a function in the mean time.

Into the function node I have 2 feeds and i need to find within the array (shown below) the msg.payload.cardUID that is presented to the function node.


Is this possible and if so do i need to add a few more nodes to delay or convert?

I need to parse out either a true/false or a 1/0 if the cardUID exists in the array. The cardUID is a varying value.

Thanks for your expert help.



two feeds into the function node means it will execute twice - once for each input msg. If you want them to be processed at the same time, you need to feed them into a join node, then feed that into the function.

and unless you want us to guess, it would be helpful if you provided your function code.



Thanks for the speedy reply.

I have tried so many versions in the function i dont know which to send, but here are the 2 nodes before the function I am trying to create.

[{"id":"416c668.c944818","type":"function","z":"e79d0bbd.096098","name":"cardUID","func":"msg.topic="cardUID";\nmsg.payload=msg.payload.cardUID;\nreturn msg;","outputs":1,"noerr":0,"x":608,"y":40,"wires":[["bc7b9f5e.5a7fe"]]},{"id":"2ffbb947.5690f6","type":"alafile in","z":"e79d0bbd.096098","name":"CSV Lookup","filename":"/home/pi/.node-red/medhelp/Users","format":"csv","columns":"*","headers":true,"x":436,"y":269,"wires":[[]]}]

So what I am trying to achieve is to look up in a csv if a card UID already exists before proceeding.

The Alafile lookp produces the array with all the lines in the csv.

All i need to achieve is lookup the card UID in the csv file. Seems simple but I have been scratching my head on this hence the forum post.



Hi @RackIoT - please read this post on sharing flows/code in the forum: How to share code or flow json




thanks knolleary.

Please find below what was previously posted in the correct format.

[{"id":"416c668.c944818","type":"function","z":"e79d0bbd.096098","name":"cardUID","func":"msg.topic=\"cardUID\";\nmsg.payload=msg.payload.cardUID;\nreturn msg;","outputs":1,"noerr":0,"x":608,"y":40,"wires":[["bc7b9f5e.5a7fe"]]},{"id":"2ffbb947.5690f6","type":"alafile in","z":"e79d0bbd.096098","name":"CSV Lookup","filename":"/home/pi/.node-red/medhelp/Users","format":"csv","columns":"*","headers":true,"x":436,"y":269,"wires":[[]]}]




So as was previously mentioned, if you want to combine the results from two nodes then you have to use a Join node, set to generate key/value pairs. Feed the two nodes into that and see what you get out. Make sure that the two messages have different topics. Also read the Info tab for the Join node so you can understand how to configure it.



Interesting use case (in my case for learning purposes). If I understood correctly there are two totally different kind of msgs in your flow. A kind that has msg.topic and another kind has no msg.topic ?

If the above assumption is correct then a flow with this logic should work:

1- When the msg has a topic (as a matter of fact msg.topic is undefined) then the only processing is to store msg. topic in the memory (for later use).
2- When the msg has NO topic then we scan the array to check if one of its elements is equal to the value stored before

This is how the flow would looks like :



Hi Andrei

Yes, you are on the correct track.

I need to check a csv file to see if a payload from topic cardUID exists in the csv, if it exists, then parse a notification either false or 0 or "Card Exists" on. If it doesn't exist, then add the payload along with a name to the csv file using the below function node.

[{"id":"bc7b9f5e.5a7fe","type":"function","z":"e79d0bbd.096098","name":"Merge the two","func":" = || {};\n\nswitch (msg.topic) {\n    case \"Name\":\n = msg.payload;\n        msg = null;\n        break;\n    case \"cardUID\":\n = msg.payload;\n        msg = null;\n        break;\n        \n    default:\n        msg = null;\n    \tbreak;\n\n}\n\nif( != null && != null ) {\n\tmsg2 = {};\n    msg2.payload = \"\"\",\";\n    \n;\n\treturn msg2;\n} ","outputs":1,"noerr":0,"x":754,"y":96,"wires":[["25fe6988.b556be","281ac8e5.2641e8"]]}]

This is for a simple door access system with a csv file that holds card numbers and names. Basic security system for my son who is trying to keep his sister out of his room.




I see. In such case it is likely that the flow can be simpler than I imagined. I will see if I can work out another suggestion. In the mean time keep an eye on your children, specially if your daughter watches the series "Dexter´s Laboratory" :smile:



Thanks @Andrei

I look forward to see what you suggest. I am being bugged to get this sorted out..........I do think he got the idea from "Dexter's Laboratory" !!!




ok, before I continue please test this partial flow and tell me if I am still on the right track.

Given a cvs file like this:

UID, Name
UID002,Dee Dee
UID005,Major Glory
UID006,Val Hallen

In the first step the flow will read the file and have it stored in the flow context.

In a second step a jsonata expression will check if msg.topic is a value included in the UID list and will return true or false.

[{"id":"bf609542.556b38","type":"tab","label":"Flow 6","disabled":false,"info":""},{"id":"ef80926.9986d7","type":"inject","z":"bf609542.556b38","name":"Trigger Flow","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":80,"wires":[["f620a872.527c18"]]},{"id":"f620a872.527c18","type":"file in","z":"bf609542.556b38","name":"Read CSV","filename":"C:\\Users\\OCM\\.node-red\\static\\nrfiles\\dexter.csv","format":"utf8","chunk":false,"sendError":false,"x":290,"y":80,"wires":[["7837e666.ccbed8"]]},{"id":"5ffe2334.f88e9c","type":"debug","z":"bf609542.556b38","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":730,"y":80,"wires":[]},{"id":"7837e666.ccbed8","type":"csv","z":"bf609542.556b38","name":"","sep":",","hdrin":true,"hdrout":"","multi":"mult","ret":"\\n","temp":"","skip":"0","x":430,"y":80,"wires":[["3d4fdc44.b1c5b4"]]},{"id":"b6b0d2ab.f0843","type":"debug","z":"bf609542.556b38","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"result","x":530,"y":160,"wires":[]},{"id":"3d4fdc44.b1c5b4","type":"change","z":"bf609542.556b38","name":"","rules":[{"t":"set","p":"csv","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":80,"wires":[["5ffe2334.f88e9c"]]},{"id":"87fc7f29.ab3a1","type":"inject","z":"bf609542.556b38","name":"","topic":"UID007","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":160,"wires":[["b998fe74.fbb4c"]]},{"id":"b998fe74.fbb4c","type":"change","z":"bf609542.556b38","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"csv","tot":"flow"},{"t":"set","p":"result","pt":"msg","to":"topic in $.[payload.UID]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":160,"wires":[["b6b0d2ab.f0843"]]}]

If this is good then it is missing to work on the last part to comply with this requirement:

If it doesn't exist, then add the payload along with a name to the csv file using the below function node.



mmm.. silly me.. what I did above is no sense (vis-a-vis with topic and payload usage)... I guess I got the idea now.

Probably this is what you are expecting:

[{"id":"bf609542.556b38","type":"tab","label":"Flow 6","disabled":false,"info":""},{"id":"ef80926.9986d7","type":"inject","z":"bf609542.556b38","name":"Trigger Flow","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":80,"wires":[["f620a872.527c18"]]},{"id":"f620a872.527c18","type":"file in","z":"bf609542.556b38","name":"Read CSV","filename":"C:\\Users\\OCM\\.node-red\\static\\nrfiles\\dexter.csv","format":"utf8","chunk":false,"sendError":false,"x":290,"y":80,"wires":[["7837e666.ccbed8"]]},{"id":"5ffe2334.f88e9c","type":"debug","z":"bf609542.556b38","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":730,"y":80,"wires":[]},{"id":"7837e666.ccbed8","type":"csv","z":"bf609542.556b38","name":"","sep":",","hdrin":true,"hdrout":"","multi":"mult","ret":"\\n","temp":"","skip":"0","x":430,"y":80,"wires":[["3d4fdc44.b1c5b4"]]},{"id":"b6b0d2ab.f0843","type":"debug","z":"bf609542.556b38","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":510,"y":160,"wires":[]},{"id":"3d4fdc44.b1c5b4","type":"change","z":"bf609542.556b38","name":"","rules":[{"t":"set","p":"csv","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":570,"y":80,"wires":[["5ffe2334.f88e9c"]]},{"id":"87fc7f29.ab3a1","type":"inject","z":"bf609542.556b38","name":"","topic":"cardUID","payload":"UID007","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":160,"wires":[["b998fe74.fbb4c"]]},{"id":"b998fe74.fbb4c","type":"change","z":"bf609542.556b38","name":"","rules":[{"t":"set","p":"csv","pt":"msg","to":"csv","tot":"flow"},{"t":"set","p":"result.cardUID","pt":"msg","to":"payload in $.[csv.UID]","tot":"jsonata"},{"t":"set","p":"result.Name","pt":"msg","to":"payload in $.[csv.Name]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":160,"wires":[["b6b0d2ab.f0843"]]},{"id":"5b4302fc.fd2dcc","type":"inject","z":"bf609542.556b38","name":"","topic":"Name","payload":"Mom","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":220,"wires":[["b998fe74.fbb4c"]]}]



It is not clear for me what information and what is the rule to append something to the file. Considering this is an access system then probably you want to log in a file each attempt made ? The flow below saves a log file (logline.txt).

[{"id":"c8c1ca3b.e4d8c8","type":"tab","label":"Dexter List","disabled":false,"info":""},{"id":"b3b4a21c.2f6c9","type":"inject","z":"c8c1ca3b.e4d8c8","name":"Trigger Flow","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":160,"wires":[["45f7c805.f01608"]]},{"id":"45f7c805.f01608","type":"file in","z":"c8c1ca3b.e4d8c8","name":"Read CSV","filename":"C:\\Users\\OCM\\.node-red\\static\\nrfiles\\dexter.csv","format":"utf8","chunk":false,"sendError":false,"x":310,"y":160,"wires":[["a97ac176.6e676"]]},{"id":"a97ac176.6e676","type":"csv","z":"c8c1ca3b.e4d8c8","name":"","sep":",","hdrin":true,"hdrout":false,"multi":"mult","ret":"\\n","temp":"","skip":"0","x":450,"y":160,"wires":[["3b798098.1b92f"]]},{"id":"b7c75a79.0c2a28","type":"debug","z":"c8c1ca3b.e4d8c8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":850,"y":260,"wires":[]},{"id":"3b798098.1b92f","type":"change","z":"c8c1ca3b.e4d8c8","name":"","rules":[{"t":"set","p":"csv","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":630,"y":160,"wires":[[]]},{"id":"61923d0.76ef2c4","type":"inject","z":"c8c1ca3b.e4d8c8","name":"Valid card","topic":"","payload":"UID003","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":260,"wires":[["5f821927.3bafc8"]]},{"id":"5f821927.3bafc8","type":"change","z":"c8c1ca3b.e4d8c8","name":"","rules":[{"t":"set","p":"csv","pt":"msg","to":"csv","tot":"flow"},{"t":"set","p":"list","pt":"msg","to":"csv{UID : Name}","tot":"jsonata"},{"t":"set","p":"valid","pt":"msg","to":"payload in $keys(list)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":260,"wires":[["94e8cfd6.08269"]]},{"id":"76e40342.b4463c","type":"inject","z":"c8c1ca3b.e4d8c8","name":"Invalid card","topic":"","payload":"UID022","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":320,"wires":[["5f821927.3bafc8"]]},{"id":"94e8cfd6.08269","type":"function","z":"c8c1ca3b.e4d8c8","name":"Logline","func":"let pay = msg.payload;\nif (msg.valid) {\n    msg.payload = `Pass:      ${pay},${msg.list[pay]}`} else {\n    msg.payload = `Violation: ${pay}, Not in the list`    \n    }\n\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":260,"wires":[["3077ba82.c54b06"]]},{"id":"47fe33d4.d0d61c","type":"comment","z":"c8c1ca3b.e4d8c8","name":"Dexter file","info":"UID, Name\nUID001,Dexter\nUID002,Dee Dee\nUID003,Mandark\nUID004,Simion\nUID005,Major Glory\nUID006,Val Hallen\nUID007,Dad\nUID008,Mom","x":140,"y":80,"wires":[]},{"id":"3077ba82.c54b06","type":"file","z":"c8c1ca3b.e4d8c8","name":"Save file","filename":"C:\\Users\\OCM\\.node-red\\static\\nrfiles\\logline.csv","appendNewline":true,"createDir":false,"overwriteFile":"false","x":680,"y":260,"wires":[["b7c75a79.0c2a28"]]}]

What is this return character?

HI Andrei

Thanks for your help on that flow it worked nearly 100%.

1 last question, the card reader is introducing a carrige return which is flowing through and messing up the csv file.

Any idea on a function I can use to remove this?





Use a change node

Change msg.payload



I am happy to know about the progress.

As for the question on removing the CR it is possible to use a regex expression. I prefer however using the string slice method. In windows I would need to remove two characters. There are possibly other ways though.

let bad = "3141592\n\r";
let good = bad.slice(0,-2);


[{"id":"1faf8001.775d3","type":"tab","label":"Flow 5","disabled":false,"info":""},{"id":"a533de7a.1c0f9","type":"inject","z":"1faf8001.775d3","name":"","topic":"","payload":"testing ended","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":180,"wires":[["76fa6197.c2c7c"]]},{"id":"1b85b7b4.1c1678","type":"debug","z":"1faf8001.775d3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":570,"y":180,"wires":[]},{"id":"76fa6197.c2c7c","type":"function","z":"1faf8001.775d3","name":"Remove CR","func":"let bad = \"3141592\\n\\r\";\nlet good = bad.slice(0,-2);\nnode.warn(bad);\nnode.warn(good);\nreturn msg;","outputs":1,"noerr":0,"x":390,"y":180,"wires":[["1b85b7b4.1c1678"]]}]


1 Like


.trim() is often simpler as it will remove whitespace at beginning or end only if it is there or not... so is safe to apply "just in case"

let good = bad.trim();
1 Like


Thanks @Andrei and @dceejay for the help.

Works great in the test flow sent both with trim and slice, but how do I add to that function where the inbound msg.payload contains the CR.

Works great if I specify a fixed number with a carige return, how do I add msg.payload to the "let bad =" statement as I have tried several way and none seem to change.

Excuse what may be a simple question, but I am fairly new to JS and NR.



Hi @RackIoT, your welcomed.

What is nice about the string method trim() is that it will remove trailing and leading "garbage (cr, lf, tab, space, etc)" from the string. Also it will remove automatically any amount of characters (not only one).

The best place to use the function is right after the string is created , maybe the card reader ?

I assume you made some adjustments to the flow. If you provide a new picture of the flow (or its code) we can have a look to check the placement of the trim() function.



Hi @Andrei

Below is a screen shot of the flow:


The Wiegand Decoder just calls a python script and the script/reader combination is what is introducing the CR.

I have placed teh function node from you as above. Below is what I get out of the wiegand decoder node on a debug.


It is just a standadr msg.payload format, but it contains the CR.

So in short all I want to do is remove the CR from a variable msg.payload as the cadr numbers are all different and my son wants to see who has gone in his room........fussy like that I suppose.