Monitoring CPU metrics on Grafana using Node-red

Grafana 8.2.2 seems does not support running a shell script natively. I would recommend parsing the CPU status via an external REST API. Node-red will be used in the following example.

In this article, you will learn:

  1. How to monitor cpu status on Node-red
  2. How to create a REST api on Node-red
  3. How to parse the REST data using Grafana Infinity plug-in

Monitoring CPU status

We will be using exec node to run shell commands. In addition, we can add buttons to shutdown and reboot as well.

Notice that the script for running CPU usage is a bit complicated: top -d 0.5 -b -n3| grep "Cpu(s)"|tail -n 1| awk '{print 100-$8}'as well as ram free | grep Mem | awk '{printf "%.2f", ($2-$7)*100/$2}'. The final number is calculated in percentage, where 100 is overloaded. You may play around with the parameters, I will make a post to briefly introduce shell manipulation in the future.

[{"id":"2653a0b0.26d638","type":"ui_gauge","z":"1a2e114b.5f398f","name":"","group":"1890881e.83819","order":2,"width":0,"height":0,"gtype":"gage","title":"CPU Temperature","label":"C","format":"{{value}}","min":0,"max":"100","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":1090,"y":120,"wires":[]},{"id":"fba68adf.14e13","type":"exec","z":"1a2e114b.5f398f","command":"vcgencmd measure_temp","addpay":false,"append":"","useSpawn":"","timer":"","name":"raspberry pi temperature","x":630,"y":160,"wires":[["fa5b499.e176cb8"],[],[]]},{"id":"7c8379de.068868","type":"inject","z":"1a2e114b.5f398f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"2","crontab":"","once":false,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":310,"y":200,"wires":[["fba68adf.14e13","972ece2a.3dbe8","6242be99.26ac88"]]},{"id":"fa5b499.e176cb8","type":"function","z":"1a2e114b.5f398f","name":"trim string","func":"str = msg.payload\nmsg.payload = str.substring(5,9);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":860,"y":120,"wires":[["2653a0b0.26d638","28b64a2c.b32116","7d734a89.e1e164"]]},{"id":"683a2b05.736204","type":"ui_button","z":"1a2e114b.5f398f","name":"","group":"c5f1b8aa.45bc08","order":2,"width":0,"height":0,"label":"Reboot","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"","x":320,"y":580,"wires":[["1cf31554.2aaa63"]]},{"id":"1cf31554.2aaa63","type":"exec","z":"1a2e114b.5f398f","command":"sudo reboot","addpay":false,"append":"","useSpawn":"","timer":"","name":"sudo reboot","x":590,"y":580,"wires":[[],[],[]]},{"id":"38e53dbf.ca594a","type":"ui_button","z":"1a2e114b.5f398f","name":"","group":"c5f1b8aa.45bc08","order":3,"width":0,"height":0,"label":"Shutdown","color":"","bgcolor":"red","icon":"","payload":"","payloadType":"str","topic":"","x":320,"y":640,"wires":[["c409cae5.8e4128"]]},{"id":"c409cae5.8e4128","type":"exec","z":"1a2e114b.5f398f","command":"sudo shutdown 0","addpay":false,"append":"","useSpawn":"","timer":"","name":"sudo shutdown -h now","x":620,"y":640,"wires":[[],[],[]]},{"id":"28b64a2c.b32116","type":"ui_chart","z":"1a2e114b.5f398f","name":"","group":"1890881e.83819","order":3,"width":0,"height":0,"label":"","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"12","removeOlderPoints":"1000","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":1070,"y":160,"wires":[[]]},{"id":"972ece2a.3dbe8","type":"exec","z":"1a2e114b.5f398f","command":"top -d 0.5 -b -n3| grep \"Cpu(s)\"|tail -n 1| awk '{print 100-$8}'","addpay":false,"append":"","useSpawn":"","timer":"","name":"cpu usage","x":590,"y":240,"wires":[["b9372186.ed1a5","8144e676.4d5d68"],[],[]]},{"id":"6242be99.26ac88","type":"exec","z":"1a2e114b.5f398f","command":"free | grep Mem | awk '{printf \"%.2f\", ($2-$7)*100/$2}'","addpay":false,"append":"","useSpawn":"","timer":"","name":"ram usage","x":590,"y":320,"wires":[["d8ede4a4.507998","9b301b09.8c0468"],[],[]]},{"id":"b9372186.ed1a5","type":"ui_gauge","z":"1a2e114b.5f398f","name":"","group":"1890881e.83819","order":1,"width":0,"height":0,"gtype":"gage","title":"Processor","label":"CPU","format":"{{value}}","min":0,"max":"102","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":1080,"y":200,"wires":[]},{"id":"9b301b09.8c0468","type":"ui_gauge","z":"1a2e114b.5f398f","name":"","group":"9a96a8b1.92db78","order":1,"width":0,"height":0,"gtype":"gage","title":"Memory","label":"RAM","format":"{{value}}","min":0,"max":"100","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":1060,"y":300,"wires":[]},{"id":"d8ede4a4.507998","type":"debug","z":"1a2e114b.5f398f","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1070,"y":340,"wires":[]},{"id":"8144e676.4d5d68","type":"debug","z":"1a2e114b.5f398f","name":"","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1090,"y":240,"wires":[]},{"id":"57aad7a5.391708","type":"inject","z":"1a2e114b.5f398f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":false,"onceDelay":"","topic":"","payload":"","payloadType":"date","x":310,"y":460,"wires":[["8c19e802.e92e88"]]},{"id":"8c19e802.e92e88","type":"exec","z":"1a2e114b.5f398f","command":"df -h / | awk '{print $5}'","addpay":false,"append":"","useSpawn":"","timer":"","name":"disk storage usage","x":610,"y":460,"wires":[["9869bc88.1d73f"],[],[]]},{"id":"9869bc88.1d73f","type":"function","z":"1a2e114b.5f398f","name":"trim string","func":"var test = msg.payload.substr(5);  //trim off Use%\nmsg.payload = test.substring(0, test.length - 1) //trim off the next line string\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":900,"y":460,"wires":[["1ba34040.c7d4","9c3b4510.f9fbc8"]]},{"id":"9c3b4510.f9fbc8","type":"debug","z":"1a2e114b.5f398f","name":"","active":false,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":1130,"y":480,"wires":[]},{"id":"1ba34040.c7d4","type":"ui_gauge","z":"1a2e114b.5f398f","name":"","group":"72fc319.cc425d","order":1,"width":0,"height":0,"gtype":"gage","title":"Disk","label":"Usage","format":"{{value}}","min":0,"max":"100","colors":["#00b500","#e6e600","#ca3838"],"x":1110,"y":440,"wires":[]},{"id":"7d734a89.e1e164","type":"debug","z":"1a2e114b.5f398f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1050,"y":80,"wires":[]},{"id":"1890881e.83819","type":"ui_group","name":"Col1","tab":"c3173234.2636e","order":1,"disp":false,"width":"6"},{"id":"c5f1b8aa.45bc08","type":"ui_group","name":"Actions","tab":"c3173234.2636e","order":4,"disp":true,"width":"6"},{"id":"9a96a8b1.92db78","type":"ui_group","name":"Col2","tab":"c3173234.2636e","order":2,"disp":false,"width":"6"},{"id":"72fc319.cc425d","type":"ui_group","name":"Col3","tab":"c3173234.2636e","order":3,"disp":false,"width":"6"},{"id":"c3173234.2636e","type":"ui_tab","name":"RPi Control","icon":"dashboard","order":1}]

Creating REST api on Node-red

We will use the http in and http response node to set up the REST API. Please note that you cannot initiate an HTTP response by itself. You should rather trigger the flow by going into the URL, or by an HTTP request node or by Grafana in this case. The http in node (Method: GET) will initiate the flow once the URL has been requested. Here is an example of setting up a simple payload as a content of the URL /cpumetrics. Open the browser and navigate to http://localhost:1880/cpumetrics.

[{"id":"c01ca5bf.a938b8","type":"http in","z":"9566abd8.7dc4d8","name":"","url":"/cpumetrics","method":"get","upload":false,"swaggerDoc":"","x":140,"y":140,"wires":[["1b98b2af.7af11d"]]},{"id":"8ccf75b3.ddd338","type":"comment","z":"9566abd8.7dc4d8","name":"REST endpoint","info":"","x":140,"y":80,"wires":[]},{"id":"1b98b2af.7af11d","type":"function","z":"9566abd8.7dc4d8","name":"simple payload","func":"msg.payload = {\"message\": \"this is the payload\"};\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":140,"wires":[["26f72364.1a687c"]]},{"id":"26f72364.1a687c","type":"http response","z":"9566abd8.7dc4d8","name":"","statusCode":"","headers":{},"x":510,"y":140,"wires":[]}]

REST api with query parameters

Sometimes you want your reply to be changed dynamically based on different parameters. You could use an if-else statement on the function to give a specific reply to different parameters. After deploying the flow, navigate to the URL: http://localhost:1880/cpumetrics?metrics=cpu. If the metric parameter is equal to “cpu”, the page will reply a {“message”: “cpu loading”} otherwise, {“message”: “some other metrics”} will be replied.

[{"id":"59ff2a1.fa600d4","type":"http in","z":"1adc4b7f.bb9655","name":"","url":"/cpumetrics","method":"get","upload":false,"swaggerDoc":"","x":240,"y":200,"wires":[["a3d72dd3.05bc2","2ec4bd64.072302","e6e4ab4c.254ea8","8a2f5529.8baa88","54c1e70d.ab3e18","c4d1aa9b.1bb7c8"]]},{"id":"4eefdf83.b473c","type":"comment","z":"1adc4b7f.bb9655","name":"REST endpoint","info":"","x":240,"y":140,"wires":[]},{"id":"266c286f.d993d8","type":"http response","z":"1adc4b7f.bb9655","name":"","statusCode":"","headers":{},"x":1030,"y":220,"wires":[]},{"id":"a3d72dd3.05bc2","type":"function","z":"1adc4b7f.bb9655","name":"request with Query Parameters","func":"if (msg.payload.metrics === \"cpu\"){\n    msg.payload = {\"message\": \"cpu loading\"};\n}else{\n    msg.payload = {\"message\": \"some other metrics\"};\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":100,"wires":[["266c286f.d993d8","2ec4bd64.072302"]]}]

REST api with cpu metrics

In the last example, we will put the real-time cpu metrics as content. A join node is used to put together the disk space and ram space into a single payload. Besides, we will use a http request node to invoke the endpoint. The payload could be checked on the debug window.

[{"id":"4512cdf1.3cd794","type":"exec","z":"608a04eb.6011bc","command":"free | grep Mem | awk '{printf \"%.2f\", ($2-$7)*100/$2}'","addpay":"","append":"","useSpawn":"false","timer":"","oldrc":false,"name":"RAM","x":490,"y":220,"wires":[["c5b1a07e.aa5fb"],[],[]]},{"id":"1ade78d7.8383f7","type":"http in","z":"608a04eb.6011bc","name":"","url":"/cpumetrics","method":"get","upload":false,"swaggerDoc":"","x":120,"y":160,"wires":[["334a3882.c0e9b8","696e606d.dc86f"]]},{"id":"696e606d.dc86f","type":"function","z":"608a04eb.6011bc","name":"append topic","func":"msg.topic = \"ram\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":220,"wires":[["4512cdf1.3cd794"]]},{"id":"334a3882.c0e9b8","type":"exec","z":"608a04eb.6011bc","command":"df -h / | awk '{print $5}'","addpay":"","append":"","useSpawn":"false","timer":"","oldrc":false,"name":"disk space","x":350,"y":160,"wires":[["c9c3ed28.056db"],[],[]]},{"id":"c9c3ed28.056db","type":"function","z":"608a04eb.6011bc","name":"trim off string","func":"var test = msg.payload.substr(5);  //trim off Use%\nmsg.payload = test.substring(0, test.length - 2) //trim off the next line string\nmsg.topic = \"storage\";\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":530,"y":160,"wires":[["c5b1a07e.aa5fb"]]},{"id":"c5b1a07e.aa5fb","type":"join","z":"608a04eb.6011bc","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":710,"y":180,"wires":[["a00ebb59.59d158","2b0fcd92.4fd282"]]},{"id":"a00ebb59.59d158","type":"http response","z":"608a04eb.6011bc","name":"","statusCode":"","headers":{},"x":870,"y":160,"wires":[]},{"id":"4899725b.041d9c","type":"comment","z":"608a04eb.6011bc","name":"REST endpoint","info":"","x":120,"y":100,"wires":[]},{"id":"5f21f1c0.0f39c","type":"http request","z":"608a04eb.6011bc","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"localhost:1880/cpumetrics","tls":"","persist":false,"proxy":"","authType":"","x":290,"y":320,"wires":[[]]},{"id":"7904b22.74ae64c","type":"inject","z":"608a04eb.6011bc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":320,"wires":[["5f21f1c0.0f39c"]]},{"id":"2fc5dcef.b8ce74","type":"comment","z":"608a04eb.6011bc","name":"Invoke REST endpoint","info":"","x":160,"y":280,"wires":[]},{"id":"2b0fcd92.4fd282","type":"debug","z":"608a04eb.6011bc","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":890,"y":200,"wires":[]}]

Setting up Grafana plug-in

After preparing the data for display, we could set up the Grafana dashboard to display those data. Go to Configurations -> plugins, search for Infinity and install it.

Then go to Configurations -> Data sources -> Add data source.

The current version (Version 0.7.8) of Infinity does not require connectivity testing in this setup stage, so leave all fields blank and click Save & test.

Head over to your dashboard and add a new panel. Select a Stat graph, and select Infinity as the Data source. Put http://localhost:1880/cpumetrics in the URL field. Click Add Colums and put storage and ram as the selector, Number as type. You should now see the data shown in the preview panel.

Assigning fixed names for USB devices in Raspberry Pi

You can find the USB devices by ls /dev/*USB*

pi@raspberrypi:~ $ ls /dev/*USB*
/dev/ttyUSB0  /dev/ttyUSB1
pi@raspberrypi:~ $

and more information by lsusb or dmesg | grep ttyUSB.

pi@raspberrypi:~ $ lsusb
Bus 002 Device 003: ID 05e3:0626 Genesys Logic, Inc.
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 006: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC
Bus 001 Device 005: ID 046d:c534 Logitech, Inc. Unifying Receiver
Bus 001 Device 004: ID 05e3:0610 Genesys Logic, Inc. 4-port hub
Bus 001 Device 003: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter
Bus 001 Device 002: ID 2109:3431 VIA Labs, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
pi@raspberrypi:~ $
pi@raspberrypi:~ $
pi@raspberrypi:~ $ dmesg | grep ttyUSB
[    5.284638] usb 1-1.3: ch341-uart converter now attached to ttyUSB0
[    5.312424] usb 1-1.4: FTDI USB Serial Device converter now attached to ttyUSB1

But sometimes, you may want to use the /dev/ path to descript your USB, such as selecting the serial port in NodeRed.

Procedure of assigning fixed name

  1. Finding unique attributes for your USB devices
  2. Creating udev rules for your applications
  3. Creating match keys and assignment keys to bind the USB devices to your desired name.
  4. Applying rules

Finding unique attributes of USB devices

Using udevadm info [options] [devpath|file|unit…]to check the details of the USB devices. -a option stands for --attribute-walk.

pi@raspberrypi:/etc/udev/rules.d $ udevadm info -a /dev/ttyUSB0

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/ttyUSB0/tty/ttyUSB0':
    KERNEL=="ttyUSB0"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/ttyUSB0':
    KERNELS=="ttyUSB0"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="ch341-uart"
    ATTRS{port_number}=="0"

  looking at parent device '/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0':
    KERNELS=="1-1.3:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="ch341"
    ATTRS{bNumEndpoints}=="03"
    ATTRS{bInterfaceProtocol}=="02"
    ATTRS{bInterfaceSubClass}=="01"
    ATTRS{bInterfaceClass}=="ff"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{supports_autosuspend}=="1"
    ATTRS{authorized}=="1"

  looking at parent device '/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3':
    KERNELS=="1-1.3"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bcdDevice}=="0264"
    ATTRS{busnum}=="1"
    ATTRS{authorized}=="1"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{urbnum}=="31"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bDeviceClass}=="ff"
    ATTRS{devspec}=="(null)"
    ATTRS{bMaxPower}=="98mA"
    ATTRS{idProduct}=="7523"
    ATTRS{rx_lanes}=="1"
    ATTRS{tx_lanes}=="1"
    ATTRS{product}=="USB Serial"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{speed}=="12"
    ATTRS{removable}=="unknown"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{configuration}==""
    ATTRS{devpath}=="1.3"
    ATTRS{quirks}=="0x0"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{bmAttributes}=="80"
    ATTRS{ltm_capable}=="no"
    ATTRS{version}==" 1.10"
    ATTRS{devnum}=="65"
    ATTRS{maxchild}=="0"
    ATTRS{idVendor}=="1a86"

...
...
...

Some unique attributes are ” idProduct”, “idVendor”. Make sure you are searching these attributes under the corresponding Port, in my case, usb1/1-1/1-1.3. Repeat the udevadm info command and search for attributes for the other devices. Remember to change the /dev/ttyUSB0 to /dev/ttyUSB1 or else only plug a single device at a time.

To understand more about USB ports, please visit Extra readings down below.

Creating udev rules

Linux stores file-like device nodes in /dev directory. Each node points to a part of a system or device, no matter it exists or not. Because the /dev directories contain every device that may exits in the system, it may be very large and become difficult to manage.

udev plays an important role to manage /dev directories by providing a path forward, by matching information provided by sysfs and rules provided by users.

udev files should be kept in /etc/udev/rules.d directories and with a .rulessuffix. Files in /etc/udev/rules.d are parsed in lexical order. In general, you want your rule file to be parsed first, so it is suggested that /etc/udev/rules.d/10-local.rulesis a good choice.

sudo touch /etc/udev/rules.d/10-local.rules to create a rule file.

In a rules file, lines starting with “#” are treated as comments. Every other non-blank line is a rule. Rules cannot span multiple lines.

To learn more about udev, you may man udev man udevadmor visit http://www.reactivated.net/writing_udev_rules.html#udevinfo

Writing rules

Each line of rule should contain at least one match key and at least one assignment to construct a key-value pair. When all match keys are fulfilled, the rule will apply and the action of assignment will be performed.

Do not insert any line breaks in the rules, udev will see your one rule as multiple rules. And here are my rules to assign a new name for the 2 USB devices. Just add the following lines in your 10-local.rules file.

SUBSYSTEM=="tty", ATTRS{idProduct}=="7523", ATTRS{idVendor}=="1a86", SYMLINK+="ttyUSB_HL-340_device"
SUBSYSTEM=="tty", ATTRS{idProduct}=="6001", ATTRS{idVendor}=="0403", SYMLINK+="ttyUSB_FT232_device"

SUBSYSTEM will match against the subsystem of the device.
SYMLINK is the alternative name(s) you want to assign to the USB device. This will not change or hide the original name but only provide an alternative name to link to the device.

Besides an exact matching of strings, you could also use shell-style pattern matching, such as *, ? and [].

Applying rules

Run udevadm trigger to apply the new rules.

sudo udevadm trigger

To check the result.


pi@raspberrypi:/etc/udev/rules.d $ ls -l /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0 Dec  3 12:05 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 Dec  3 12:05 /dev/ttyUSB1
lrwxrwxrwx 1 root root         7 Dec  3 12:05 /dev/ttyUSB_FT232_device -> ttyUSB1
lrwxrwxrwx 1 root root         7 Dec  3 12:05 /dev/ttyUSB_HL-340_device -> ttyUSB0

If you know the top-level device path, you can use udevadm test to show the action. Or else, just use

pi@raspberrypi:/etc/udev/rules.d $ udevadm test -a -p  $(udevadm info -q path -n /dev/ttyUSB_FT232_device)

Serial port name in Node-red

Now you can specify the USB you are pointing to in Node-red rather than guessing which is ttyUSB0 and ttyUSB1.


———————-This is separator———————–

Extra readings

You may use lsusb -t to explore what devices are connected to your pi. The following corresponds to no USB device connected.

pi@raspberrypi:~ $ lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M

And the following corresponds to 4 USB devices connected. Dev 31 is a wireless mouse and keyboard. All devices are via USB2.0.

pi@raspberrypi:~ $ lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
        |__ Port 1: Dev 27, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
        |__ Port 2: Dev 31, If 0, Class=Human Interface Device, Driver=usbhid, 12M
        |__ Port 2: Dev 31, If 1, Class=Human Interface Device, Driver=usbhid, 12M
        |__ Port 3: Dev 26, If 0, Class=Vendor Specific Class, Driver=ftdi_sio, 12M
        |__ Port 4: Dev 30, If 0, Class=Hub, Driver=hub/4p, 480M

If you have a USB hub, then those connected devices will be under the hub port. In my case, the hub is connected to Port 3.


pi@raspberrypi:~ $ lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
        |__ Port 3: Dev 36, If 0, Class=Hub, Driver=hub/4p, 480M
            |__ Port 1: Dev 37, If 0, Class=Human Interface Device, Driver=usbhid, 12M
            |__ Port 1: Dev 37, If 1, Class=Human Interface Device, Driver=usbhid, 12M
            |__ Port 2: Dev 41, If 0, Class=Vendor Specific Class, Driver=ftdi_sio, 12M
            |__ Port 3: Dev 42, If 0, Class=Vendor Specific Class, Driver=ch341, 12M

If you have USB 3.0 devices, they will be using Bus 02. In my case, both the USB hub and USB flash drive support USB 3.0.

pi@raspberrypi:~ $ lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
    |__ Port 1: Dev 13, If 0, Class=Hub, Driver=hub/4p, 5000M
        |__ Port 4: Dev 14, If 0, Class=Mass Storage, Driver=usb-storage, 5000M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M
        |__ Port 1: Dev 56, If 0, Class=Hub, Driver=hub/4p, 480M

The number after the word ‘usb’ in dmesg is actually the USB Bus and Port. In my case, the ch341-uart converter is under bus 1, port 1.3. In similar manner, usb 3.0 devices will be usb 2-X.X.

[161104.469392] usb 1-1.3: ch341-uart converter now attached to ttyUSB1