B4J Question [SOLVED] [RPi] Get VID/PID (VendorId/ProductId) or assign fixed port number to USB serial device

raphaelcno

Active Member
Licensed User
Longtime User
Edit:
To assign a fixed port number to a USB serial device connected to Raspberry Pi, go to post #10.

----------

I'm looking at the possibility to improve the jSerial libray by adding the getLinuxPortProperties method from jSSC (SerialPortList.java).
The purpose is to be able to select a specific USB/RS232 adapter connected to a Raspberry Pi, based on the idVendor or idProduct or serial number of the adapter.

jSerial – Serial.java: https://github.com/AnywhereSoftware...r/src/anywheresoftware/b4j/serial/Serial.java

jSSC – SerialPortList.java: https://github.com/gohai/java-simpl.../processing/src/java/jssc/SerialPortList.java
Method getLinuxPortProperties from line 368:

jSSC – SerialPortList.java – getLinuxPortProperties:
    public static Map<String, String> getLinuxPortProperties(String portName) {
        Map<String, String> props = new HashMap<String, String>();
        try {
            // portName has the format /dev/ttyUSB0
            String dev = portName.split("/")[2];
            File sysfsNode = new File("/sys/bus/usb-serial/devices/"+dev);

            // resolve the symbolic link and store the resulting components in an array
            String[] sysfsPath = sysfsNode.getCanonicalPath().split("/");

            // walk the tree to the root
            for (int i=sysfsPath.length-2; 0 < i; i--) {
                String curPath = "/";
                for (int j=1; j <= i; j++) {
                    curPath += sysfsPath[j]+"/";
                }

                // look for specific attributes
                String[] attribs = { "idProduct", "idVendor", "manufacturer", "product", "serial" };
                for (int j=0; j < attribs.length; j++) {
                    try {
                        Scanner in = new Scanner(new FileReader(curPath+attribs[j]));
                        // we treat the values just as strings
                        props.put(attribs[j], in.next());
                    } catch (Exception e) {
                        // ignore the attribute
                    }
                }

                // stop once we have at least one attribute
                if (0 < props.size()) {
                    break;
                }
            }
        } catch (Exception e) {
            // nothing to do, return what we have so far
        }
        return props;
    }

I must say that programming in Java is not my strong side :rolleyes:, but I have tried to find inspiration by looking at other examples where maps are used.

If I understand correctly, I should add following lines at the beginning of the jSerial library in order to use the map object:

jSerial:
import java.util.Map.Entry;
import anywheresoftware.b4a.objects.collections.Map;

and then the new method could be something like this:

Alternative 1:
jSerial - Alternative 1:
    /**
     * Returns a map with the properties of a port.
     * To be used with Linux operating system.
     */
    public Map<String, String> GetLinuxPortProperties(String portName) {
        Map<String, String> mProps = new HashMap<String, String>();
        mProps.Initialize();
        mProps.getObject().putAll(SerialPortList.getLinuxPortProperties(portName));
        return mProps;
    }

Alternative 2:
jSerial - Alternative 2:
        /**
         * Returns a map with the properties of a port.
         * To be used with Linux operating system.
         * The keys are the properties names (strings) and the values are the properties values (strings).
         */
        public Map GetLinuxPortProperties(String portName) {
            Map mProps = new Map();
            mProps.Initialize();
            for (Entry<String, String> e : SerialPortList.getLinuxPortProperties(portName).getObject().entrySet()) {
            // or  getObject().SerialPortList.getLinuxPortProperties(portName).entrySet() ??
                mProps.Put(e.getKey(), e.getValue());
            }
            return mProps;
        }

Could someone tell me if I'm on the right way with one of these two alternatives, or if I'm completely lost?

Thank you in advance for your help :)
 
Last edited:

raphaelcno

Active Member
Licensed User
Longtime User
With this code:

B4X:
Sub AppStart (Args() As String)
    Log("Hello world!!!")
    Log("1")
    Log("/sys/bus/usb-serial/devices/:")
    For Each f As String In File.ListFiles("/sys/bus/usb-serial/devices/")
        Log("- " & f)
    Next
    Log("2")
    For Each f As String In File.ListFiles("/sys/bus/usb-serial/devices/")
        Log("- " & f & ":")
        Dim folder As String = File.Combine("/sys/bus/usb-serial/devices/", f)
        For Each ff As String In File.ListFiles(folder)
            Log("  - " & ff)
            Log(File.ReadString(folder, ff))
        Next
    Next
End Sub

I get following output:

B4X:
Waiting for debugger to connect...
Program started.
Hello world!!!
1
/sys/bus/usb-serial/devices/:
- ttyUSB1
- ttyUSB0
2
- ttyUSB1:
  - subsystem
Error occurred on line: 24 (Main)          <<<<<<<<<< Line 24 = Log(File.ReadString(folder, ff))
java.io.FileNotFoundException: /sys/bus/usb-serial/devices/ttyUSB1/subsystem (Er en filkatalog)          <<<<<<<<<< "Er en filkatalog" = "Is a folder"
    at java.base/java.io.FileInputStream.open0(Native Method)
    at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
    at anywheresoftware.b4a.objects.streams.File.OpenInput(File.java:218)
    at anywheresoftware.b4a.objects.streams.File.ReadString(File.java:272)
    at b4j.example.main._appstart(main.java:115)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:632)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:234)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:91)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:98)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:78)
    at b4j.example.main.main(main.java:29)
Program terminated (StartMessageLoop was not called).


For information, here is a picture showing a part of the file structure below /sys/bus/usb-serial/devices/:

IMG_20201006_233603240_756x1008_2.jpg
 
Upvote 0

raphaelcno

Active Member
Licensed User
Longtime User
Good!
Here is the output:

B4X:
Waiting for debugger to connect...
Program started.
Hello world!!!
1
/sys/bus/usb-serial/devices/:
- ttyUSB1
- ttyUSB0
2
- ttyUSB1:
  - subsystem
  - driver
  - power
  - port_number
0
  - tty
  - uevent
DRIVER=pl2303
- ttyUSB0:
  - subsystem
  - driver
  - power
  - port_number
0
  - tty
  - uevent
DRIVER=pl2303
Program terminated (StartMessageLoop was not called).
 
Upvote 0

raphaelcno

Active Member
Licensed User
Longtime User
I have now looked at the files below /sys/bus/usb-serial/devices/ttyUSB*, but I have not found any information about VendorId, ProductId or Serial number of the USB serial converter. Some of the files cannot be opened (access denied).

After reading different forums about Raspberry Pi, I found that following commands can be used to get information about a USB serial device:
B4X:
Alt. 1: udevadm info --query=all --name=ttyUSB0
Alt. 2: udevadm info --a --name /dev/ttyUSB0
Alt. 3: udevadm info -a -p /sys/bus/usb-serial/devices/ttyUSB0/

The output with alternative 1 is shorter and seems to be sufficient for my needs.

So I have used the jShell library in B4J to run this command:

B4X:
Sub AppStart (Args() As String)
    For Each f As String In File.ListFiles("/sys/bus/usb-serial/devices/")
        Log("- " & f)
        Dim shl As Shell
        ' Command: udevadm info --query=all --name=ttyUSB0
        shl.Initialize("shl", "udevadm", Array As String("info", "--query=all", "--name=" & f))
        'shl.WorkingDirectory =
        shl.Run(5000)  ' Timeout 5000 ms
        'shl.RunSynchronous(5000)  ' Timeout 5000 ms
        ' ==> Result in event Sub shl_ProcessCompleted
    Next
    StartMessageLoop
End Sub

Sub shl_ProcessCompleted (Success As Boolean, ExitCode As Int, StdOut As String, StdErr As String)
    Log("----------")
    If Success And ExitCode = 0 Then
        Log("shl Success - StdOut:")
        Log(StdOut)
    Else
        Log("shl Error - StdErr: ")
        Log(StdErr)
    End If
    Log("----------")
    'ExitApplication
End Sub

Here is the output:

B4X:
- ttyUSB1
- ttyUSB0
----------
shl Success - StdOut:
P: /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/ttyUSB1/tty/ttyUSB1
N: ttyUSB1
L: 0
S: serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0
S: serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0
E: DEVPATH=/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/ttyUSB1/tty/ttyUSB1
E: DEVNAME=/dev/ttyUSB1
E: MAJOR=188
E: MINOR=1
E: SUBSYSTEM=tty
E: USEC_INITIALIZED=43657938
E: ID_BUS=usb
E: ID_VENDOR_ID=067b          <<<<<<<<<< = VendorId
E: ID_MODEL_ID=2303          <<<<<<<<<< = ProductId
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI
E: ID_VENDOR_FROM_DATABASE=Prolific Technology, Inc.
E: ID_MODEL_FROM_DATABASE=PL2303 Serial Port
E: ID_VENDOR=Prolific_Technology_Inc.
E: ID_VENDOR_ENC=Prolific\x20Technology\x20Inc.
E: ID_MODEL=USB-Serial_Controller
E: ID_MODEL_ENC=USB-Serial\x20Controller
E: ID_REVISION=0300
E: ID_SERIAL=Prolific_Technology_Inc._USB-Serial_Controller
E: ID_TYPE=generic
E: ID_USB_INTERFACES=:ff0000:
E: ID_USB_INTERFACE_NUM=00
E: ID_USB_DRIVER=pl2303
E: ID_PATH=platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0
E: ID_PATH_TAG=platform-fd500000_pcie-pci-0000_01_00_0-usb-0_1_3_1_0
E: DEVLINKS=/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0 /dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0
E: TAGS=:systemd:
----------
----------
shl Success - StdOut:
P: /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/ttyUSB0/tty/ttyUSB0
N: ttyUSB0
L: 0
S: serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-port0
S: serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0
E: DEVPATH=/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.1/1-1.1:1.0/ttyUSB0/tty/ttyUSB0
E: DEVNAME=/dev/ttyUSB0
E: MAJOR=188
E: MINOR=0
E: SUBSYSTEM=tty
E: USEC_INITIALIZED=6483123
E: ID_BUS=usb
E: ID_VENDOR_ID=0557
E: ID_MODEL_ID=2008
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI
E: ID_VENDOR_FROM_DATABASE=ATEN International Co., Ltd
E: ID_MODEL_FROM_DATABASE=UC-232A Serial Port [pl2303]
E: ID_VENDOR=Prolific_Technology_Inc.
E: ID_VENDOR_ENC=Prolific\x20Technology\x20Inc.\x20
E: ID_MODEL=USB-Serial_Controller_D
E: ID_MODEL_ENC=USB-Serial\x20Controller\x20D
E: ID_REVISION=0300
E: ID_SERIAL=Prolific_Technology_Inc._USB-Serial_Controller_D
E: ID_TYPE=generic
E: ID_USB_INTERFACES=:ff0000:
E: ID_USB_INTERFACE_NUM=00
E: ID_USB_DRIVER=pl2303
E: ID_PATH=platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0
E: ID_PATH_TAG=platform-fd500000_pcie-pci-0000_01_00_0-usb-0_1_1_1_0
E: DEVLINKS=/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-port0 /dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0
E: TAGS=:systemd:
----------

So now I can extract VendorId and ProductId for each ttyUSB* :)

----

Well now I have also found another solution.
It is possible to assign a fixed port number (like an alias) to each USB serial device.
This is done by creating a file with customised rules based on idVendor, idProduct or serial number:
B4X:
SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="ttyUSB_device1"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", SYMLINK+="ttyUSB_device2"
See this article:
https://www.domoticz.com/wiki/Assign_fixed_device_name_to_USB_port

Maybe it's possible to create such a file programmatically with the B4J app, so the user doesn't need to do this manually on each RPi.
 
Last edited:
Upvote 0

raphaelcno

Active Member
Licensed User
Longtime User
Well now I have also found another solution.
It is possible to assign a fixed port number (like an alias) to each USB serial device.
This is done by creating a file with customised rules based on idVendor, idProduct or serial number:
B4X:
SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="ttyUSB_device1"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", SYMLINK+="ttyUSB_device2"
See this article:
https://www.domoticz.com/wiki/Assign_fixed_device_name_to_USB_port

Maybe it's possible to create such a file programmatically with the B4J app, so the user doesn't need to do this manually on each RPi.


You can create this file from B4J like this:

B4X:
    ' Create file  10-usb-serial.rules  in directory  /etc/udev/rules.d/
    Dim twRules As TextWriter
    twRules.Initialize(File.OpenOutput("/etc/udev/rules.d", "10-usb-serial.rules", False))  ' False = Don't append
    twRules.WriteLine("SUBSYSTEM==""tty"", ATTRS{idVendor}==""067b"", SYMLINK+=""ttyUSB_Door1""")  ' Prolific PL2303
    twRules.WriteLine("SUBSYSTEM==""tty"", ATTRS{idVendor}==""0557"", SYMLINK+=""ttyUSB_Door2""")  ' ATEN UC-232A
    twRules.WriteLine("SUBSYSTEM==""tty"", ATTRS{idVendor}==""1546"", SYMLINK+=""ttyUSB_Gps""")  ' GPS u-blox
    twRules.Close
    
    ' Load the rules
    ' Command: sudo udevadm trigger
    Dim shl2 As Shell
    shl2.Initialize("shl", "sudo", Array As String("udevadm", "trigger"))
    shl2.Run(-1)

Of course you must adjust the values of "ATTRS{idVendor}" etc. to your own needs.
 
Upvote 0

raphaelcno

Active Member
Licensed User
Longtime User
I changed the title of the thread from
"Adding GetLinuxPortProperties in jSerial library (for USB Serial converter)"
to
"[SOLVED] [RPi] Get VID/PID (VendorId/ProductId) or assign fixed port number to USB serial device".
 
Last edited:
Upvote 0
Top