RPI USB allocation

My project runs on a Raspberry Pi 3B which has two identical serial-USB Victron devises (more devices will be added in time) plugged into it. The rpi can allocate different /dev/ttyUSB* names especially if a new device is connected and then rebooted. I don't want to make changes to the rpi (udev rules etc) as the rpi could be changed / swapped.

Victron data looks like this https://www.sv-zanshin.com/r/manuals/victron-ve-direct-protocol.pdf 2

In node-red is it possible read the data from the individual USB devices and divert to the correct function? If so which node would be best to use?


Can someone give me a link if this has already been covered as I've not found anything that helps?

Many thanks for any input

Looks like a serial in node set to 19200 baud should do it. Well one per device.

I have two serial nodes and these pass data but there is always the chance that the Pi will assign a different /ttyUSB* . If this happens the wrong serial data goes to the function.

What I was wondering is if the data from each serial node could be vetted and then switched to the relevant function.

Sorry if I didn't make myself clear.

If there is information in the data to indicate the source then yes, you can use a Switch node to send the messages to the right place dependent on the contents.

If there is nothing in the data then you are back to using udev rules and the id of the device.


I have tried using the switch but I'm struggling. So for instance if I set the Switch node to "Contains" and put the 3 letters BMV into the string part, it would need to grab data until it saw the 4 letters MPPT.

I guess for something like an Arduino I would look for BMV and set a flag so all following data would be stored until it saw MPPT when the flag would be cleared and now all following data would be stored for that device.

Is this possible with node-red?

udev is a bit of a pain as it all becomes specific for that install on the RPi, I have tried it before.

The Victron USB-Serial devices have the same vendor and product ID, only serial numbers change.

I did hope it could be done in node-red.

You could run something like this in an exec node to grab the serial numbers:

sh -c 'udevadm info -a -p $(udevadm info -q path -n /dev/ttyUSB0) | fgrep "ATTRS{serial}"'

And a second exec node for /dev/ttyUSB1. Then just parse the output of the exec node to see which is which.

1 Like

Show us what the data looks like and what you need to switch on.


At present I have 2 USB/Serial devices connected to my RPi. Both devices spew out a string of data every second and the data from each is very similar in content except each will have its own unique identifier for the device that sent it.

In the data from device A connected to USBx will be the unique string BMV.
In the data from device B connected to USBy will be the unique string MPPT.

So I have wondered if once a function had detected the device connected, could that function set the msg.topic for all data it passes? I couldn't work out how to set the msg.topic until the next reboot of the RPi.
Hopefully this shows my thoughts pictorially.

Data from the USB port looks like this, please note the line containing BMV.

Ideas, help and examples would be appreciated.

You say it spews out a string of data, but the debug you have shown is multiple strings. do you mean it sends out multiple messages each containing a string, one of which will be something like BMV_712 Smart? Or do you mean that each time is sends one string containing lots of stuff including the BMV bit?

I would do something like @elmicha suggests at deploy time. and write the results to a global context bar. Then the function nodes can pick them up and add them ( or a change node could be used). But as @Colin suggested if the contents are unique enough then maybe the source doesn’t matter ? Also it’s worth looking at the complete msg coming from the serial port as there are some other properties already set.

The data in the image is from the serial node. Here is a link to the Victron proctocol https://www.sv-zanshin.com/r/manuals/victron-ve-direct-protocol.pdf , it says " The device transmits blocks of data at 1 second intervals. Each field is sent using the following
format: ".

Both Victron devices send a 'V' and the value which follows will more than likely be volts but the measurement point and value will be different.

The data is similar for both devices i.e. Volts and Amps, I think there needs to be some form of identification for each device so as the identify the source.

Are you able to give an example of a global context bar, it sounds outside the scope of my present knowledge?

You didn't actually answer my question, but it appears the device sends multiple messages, one of which you want to interrogate. You could write a function that monitors the messages and wait until you get one of the identifying ones. Then you will have to write the topic you want to use from here on into the node context, so that it will be available for later use. So first have a read of the node red docs on writing function nodes and in particular the bit on saving information to the node context. Come back when you have had a look at that and basically understand how it works.

Thanks you.

Yesterday I wrote a function to, and could identify the message containing MPPT or BMV, I could set the topic but not so that all future messages that passed were given the same topic. At that point I probably questioned myself as to whether what I was trying to do was possible in node-red. Just my lack of understanding.

As you have pointed out it is possible so I will continue.

It sounds like you have made a good start. What you need to do is to save the topic in the node context, then each time you get a message in, pick that up and add it to the current message. You can have one function node for each port and feed all the outputs to a switch node that can test the topic and route it off to the appropriate place.

Colin, it would I've been successful! The two devices would appear to be detected, I haven't tried rebooting the Pi yet though.

Thanks for your help and if you can see any improvements in the function please let me know.

[{"id":"ae52e98e.a2f388","type":"serial in","z":"97d772de.13c7e","name":"","serial":"b90a2efa.d1c1e","x":150,"y":600,"wires":[["771e75cf.ebe07c"]]},{"id":"771e75cf.ebe07c","type":"function","z":"97d772de.13c7e","name":"Device on USB","func":"var id = context.get('id') || 0;\n\nif(msg.payload.includes('GPRMC')){\n    id = 'gps'\n    context.set('id', id);\n} else if(msg.payload.includes('BMV')){\n    id = 'bmv';\n    context.set('id', id);\n} else if(msg.payload.includes('MPPT')){\n    id = 'mppt';\n    context.set('id', id);\n} else {\n    //do nothing\n}\n\nmsg.topic = id;\nreturn msg;","outputs":1,"noerr":0,"x":160,"y":680,"wires":[["9affd28d.9ffa5"]]},{"id":"9affd28d.9ffa5","type":"switch","z":"97d772de.13c7e","name":"","property":"topic","propertyType":"msg","rules":[{"t":"cont","v":"bmv","vt":"str"},{"t":"cont","v":"mppt","vt":"str"},{"t":"cont","v":"gps","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":330,"y":680,"wires":[["c7a6f3c2.7351c"],["4bc9b463.9bcbbc"],[]]},{"id":"b90a2efa.d1c1e","type":"serial-port","z":"","serialport":"/dev/ttyUSB1","serialbaud":"19200","databits":"8","parity":"none","stopbits":"1","newline":"\\n","bin":"false","out":"char","addchar":false,"responsetimeout":"10000"}]

Looks pretty good to me. A very minor point, I would have used a conditional at the end to tell it not to bother sending a message at all if the topic is not yet known, so I would have put

if (id) {
  msg.topic = id
  return msg

Since the return msg will not be executed if the topic is not yet known then it will not pass on anything.

1 Like


Could you help with this? If I run this:
sh -c 'udevadm info -a -p $(udevadm info -q path -n /dev/ttyUSB0) | fgrep "ATTRS{serial}"'
in terminal on my RPi I receive a reply of:

I have tried running ithe same in an exec node and don't seem to receive a reply. There seems to be very few examples of using the exec node so before I waste many hours, what am i doing wrong?

[{"id":"dfcd9715.21dc88","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"431dcb9f.0c7444","type":"serial in","z":"dfcd9715.21dc88","name":"","serial":"945372cb.77e01","x":90,"y":240,"wires":[["9916d496.387ae8","28e38cad.dbc024","ab227b07.7fc008"]]},{"id":"64e4d956.1d8d58","type":"debug","z":"dfcd9715.21dc88","name":"GPS","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":710,"y":320,"wires":[]},{"id":"c45f28d5.229658","type":"debug","z":"dfcd9715.21dc88","name":"BMV","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":710,"y":160,"wires":[]},{"id":"5af0f86f.455308","type":"switch","z":"dfcd9715.21dc88","name":"","property":"topic","propertyType":"msg","rules":[{"t":"cont","v":"bmv","vt":"str"},{"t":"cont","v":"mppt","vt":"str"},{"t":"cont","v":"gps","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":510,"y":240,"wires":[["c45f28d5.229658"],[],["de9ebd1c.f7cd5","c05b93cc.4d558"]]},{"id":"9916d496.387ae8","type":"function","z":"dfcd9715.21dc88","name":"","func":"var id = context.get('id') || 0;\n\nif(msg.payload.includes('GPRMC')){\n    id = 'gps'\n    context.set('id', id);\n} else if(msg.payload.includes('BMV')){\n    id = 'bmv';\n} else if(msg.payload.includes('MPPT')){\n    id = 'mppt';\n} else {\n    //do nothing\n}\n\nmsg.topic = id;\nreturn msg;","outputs":1,"noerr":0,"x":290,"y":240,"wires":[["5af0f86f.455308"]]},{"id":"28e38cad.dbc024","type":"debug","z":"dfcd9715.21dc88","name":"USB direct","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":240,"y":140,"wires":[]},{"id":"c05b93cc.4d558","type":"debug","z":"dfcd9715.21dc88","name":"MPPT","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":710,"y":240,"wires":[]},{"id":"de9ebd1c.f7cd5","type":"function","z":"dfcd9715.21dc88","name":"","func":"var Payload = msg.payload.includes('GPRMC');\n    if(Payload){\n        var output = msg.payload.split(\",\")\n        var valid = String(output[2]);\n        if(valid === 'A'){\n            var lat = parseFloat((output[3]/100).toFixed(5));\n            var north = String(output[4]);\n            var lon = parseFloat((output[5]/100).toFixed(5));\n            var west =String(output[6]);\n            \n            if(west === 'W'){\n                lon = lon * -1;\n            }\n            if(north === 'S'){\n                north = north * -1;\n            }\n            msg.payload = {lat,lon};\n            return msg\n        } else if(valid === 'V'){\n            //invalid data\n        }\n    }\n","outputs":1,"noerr":0,"x":510,"y":360,"wires":[["64e4d956.1d8d58"]]},{"id":"ab227b07.7fc008","type":"exec","z":"dfcd9715.21dc88","command":"sh -c 'udevadm info -a -p $(udevadm info -q path -n /dev/ttyUSB0) | fgrep \"ATTRS{serial}\"'","addpay":false,"append":"","useSpawn":"true","timer":"","oldrc":false,"name":"","x":420,"y":420,"wires":[["2edf564a.d0627a"],["a31e75da.76f798"],["9112f869.2daa48"]]},{"id":"2edf564a.d0627a","type":"debug","z":"dfcd9715.21dc88","name":"ONE","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":840,"y":400,"wires":[]},{"id":"a31e75da.76f798","type":"debug","z":"dfcd9715.21dc88","name":"TWO","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":840,"y":460,"wires":[]},{"id":"9112f869.2daa48","type":"debug","z":"dfcd9715.21dc88","name":"THREE","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":830,"y":520,"wires":[]},{"id":"945372cb.77e01","type":"serial-port","z":"","serialport":"/dev/ttyUSB0","serialbaud":"9600","databits":"8","parity":"none","stopbits":"1","waitfor":"","dtr":"none","rts":"none","cts":"none","dsr":"none","newline":"\\n","bin":"false","out":"char","addchar":"","responsetimeout":"10000"}]