Skip to main content
added 2618 characters in body
Source Link
user8472
  • 111
  • 1
  • 3

Short Answer

Serial devices are listed deterministically by path in /dev/serial/by-path with a verbose naming scheme that represents the physical device connection. This name will always be the same as long as the device is plugged into the same USB port. These are symlinks to the dynamic naming of devices in /dev/ttyUSB*

As long as you always have your arduino plugged into the same port on your machine or USB hub it will always result in the same path name in the by-path directory. You could then just symlink to the appropriate by-path filename to make sure your script always works and has a memorable name. This solution works even if you have several arduinos connected to your system.

If I plugged an arduino into the second USB2.0 port (4.2) on my system I could always access it at /dev/arduino using this symlink definition:

ln -s /dev/serial/by-path/pci-0000:00:14.0-usb-0:4.2:1.0-port0 /dev/arduino

Background Info

The ttyUSB0, ttyUSB1 naming scheme depends on the time order of connecting devices and the next available index. The same device may show up with a new index if the system was unable to free all handles to it from a previous connection of the device or even if an error caused the device to renumerate and the previous file handle was unavailable still. The by-path directory symlinks always point the constant physical port name to the latest ttyUSB# instance of a device connection.

$ ls -la /dev/serial/by-path/
total 0
drwxr-xr-x. 2 root root 200 Feb  8 21:46 .
drwxr-xr-x. 4 root root  80 Feb  7 22:17 ..
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:1:1.0-port0 -> ../../ttyUSB4            
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.0-port0 -> ../../ttyUSB0          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.1-port0 -> ../../ttyUSB1          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.2-port0 -> ../../ttyUSB2          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.3-port0 -> ../../ttyUSB3          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.2:1.0-port0 -> ../../ttyUSB6          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.3.3.1.2.4:1.0-port0 -> ../../ttyUSB7  
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.3.3.3:1.0-port0 -> ../../ttyUSB8   

As long as you alwaysIn this example I have yourfive USB-serial device plugged intodevices connected as well as several hubs. The "pci-0000:00:14.0-usb-" prefix is always the same portbecause all of my USB chips are on your machine orthe same PCI bus. If you had a thunderbolt connected USB hub this number would probably change. The subsequent numbering scheme represents the path to the device on a specific USB port. For the symlink beginning 0:1 I'm not sure what the 0 is, 1 is the address of the USB3 chip on my motherboard, 4.1, 4.2, 4.3 are the three builtin USB2 ports on my system. The device connected to port 4.1 is a USB-serial adapter with four serial ports on a single device, therefore it will always result inhas final values of 1.0, 1.1, 1.2, and 1.3 to signify all of the ttys on the single device.

ttyUSB7 is a USB-Serial adapter plugged into a long string of USB hubs. ttyUSB8 is plugged into the same path namestring of hubs but only part way along the string. The ttyUSB* naming shows you what order I plugged the devices in. You can see that /dev/ttyUSB5 is missing. That is because I unplugged a device that regularly fails to get all of its file handles released so when I plugged it in again that number wasn't reused. Despite that I can still reach the device under it's new filename because I have the by-path symlink.

Alternative Naming Scheme

On my CentOS system these by-path directory. On my CentOS system these symlinks are being created with UDEV rules from the default system ruleset in /usr/lib/udev/rules.d/60-serial.rules.

Since my system doesn't have the ability to extend the PCI bus the pci-0000:00:14.0-usb- prefix never changes and is just more typing to point to a specific port. For the sake of convenience in having a shorter naming scheme, the UDEV rule below uses a variation of the 60-serial.rules to create a symlink in /dev/ which strips pci-0000:00:14.0-usb- off the beginning of the link name and -port0 off the end.

ThisMy new rule relies on the ID_PATH environment variable having been defined (ID_PATH contains the long version device path - "pci-0000:00:14.0-usb-0:4.3.3.1.2.4:1.0-port0"). RulesYou don't have to care about how ID_PATH gets created, you just need to run your rule after it exists in order to use/manipulate it. UDEV rules are executed in numerical naming order so I just named my rule with a 90 prefix, /etc/udev/rules.d/90-usbtty.rules, rather than trying to find out when ID_PATH got defined and then it was available to modify.

This rule creates shorter names and puts them in a convenientthe /dev/ directory for my convenience.

More Involved Naming Code

It is nice that this rule all fits into have a single line of theone-liner UDEV filerule,      but if it were any more complex  (and arguably now) it would need its owna separate script file to be intelligible and functional.  You If you did want to create such a script the example below could be a starting point. You can substitute /bin/sh for any other executable file.  UDEV

UDEV environment variables are available in the called program and standard output (frome.g. echo/print statements) of the form ENV_VAR='VALUE' cause new environment variables to become available.

One last gotcha I ran into that might force a separte script rather than the one-liner is quote escaping. You can see in the script version of the rule that the more standard -d '-' is passed to cut, whereas in the oneliner --delimiter=- was required. As far as I can tell UDEV doesn't allow quote escaping, so specifying the delimiter without a quote is necessary.

For the examples shown I have five USB-serial devices connected as well as several hubs. The "ttyUSB." prefix is just a string in the rule. The subsequent numbering scheme represents the path to the device. For the symlink beginning 0:1 I'm not sure what the 0 is, 1 is the address of my builtin USB3 chip, 4.1, 4.2, If you try 4.3-d - arethen the three builtin USB2 ports on my system. The device connected to port 4.1dash is a USB-serial adapter with four serial ports oninterpreted as a single device, therefore it has final values of 1.0, 1.1, 1.2, and 1.3flag to signify all of the ttys on the single device.

ttyUSB7 is a USB-Serial adapter plugged into a long string of USB hubs. ttyUSB8cut is plugged into the same string of hubs but only part way along the stringrather than a delimeter character.

Serial devices are listed deterministically by path in /dev/serial/by-path with a verbose naming scheme.

$ ls -la /dev/serial/by-path/
total 0
drwxr-xr-x. 2 root root 200 Feb  8 21:46 .
drwxr-xr-x. 4 root root  80 Feb  7 22:17 ..
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:1:1.0-port0 -> ../../ttyUSB4            
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.0-port0 -> ../../ttyUSB0          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.1-port0 -> ../../ttyUSB1          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.2-port0 -> ../../ttyUSB2          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.3-port0 -> ../../ttyUSB3          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.2:1.0-port0 -> ../../ttyUSB6          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.3.3.1.2.4:1.0-port0 -> ../../ttyUSB7  
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.3.3.3:1.0-port0 -> ../../ttyUSB8   

As long as you always have your USB-serial device plugged into the same port on your machine or USB hub it will always result in the same path name in the by-path directory. On my CentOS system these symlinks are being created with UDEV rules from /usr/lib/udev/rules.d/60-serial.rules.

For the sake of convenience in having a shorter naming scheme, the rule below uses a variation of the 60-serial.rules to create a symlink in /dev/ which strips pci-0000:00:14.0-usb- off the beginning of the link name and -port0 off the end.

This rule relies on the ID_PATH environment variable having been defined. Rules are executed in numerical naming order so I just named my rule /etc/udev/rules.d/90-usbtty.rules rather than trying to find out when ID_PATH got defined.

This rule creates shorter names in a convenient directory.

It is nice that this rule all fits in a single line of the UDEV file,   but if it were any more complex  (and arguably now) it would need its own script.  You can substitute /bin/sh for any other executable file.  UDEV environment variables are available in the called program and standard output (from echo/print statements) of the form ENV_VAR='VALUE' cause new environment variables to become available.

You can see in the script version of the rule that the more standard -d '-' is passed to cut, whereas in the oneliner --delimiter=- was required. As far as I can tell UDEV doesn't allow quote escaping, so specifying the delimiter without a quote is necessary.

For the examples shown I have five USB-serial devices connected as well as several hubs. The "ttyUSB." prefix is just a string in the rule. The subsequent numbering scheme represents the path to the device. For the symlink beginning 0:1 I'm not sure what the 0 is, 1 is the address of my builtin USB3 chip, 4.1, 4.2, 4.3 are the three builtin USB2 ports on my system. The device connected to port 4.1 is a USB-serial adapter with four serial ports on a single device, therefore it has final values of 1.0, 1.1, 1.2, and 1.3 to signify all of the ttys on the single device.

ttyUSB7 is a USB-Serial adapter plugged into a long string of USB hubs. ttyUSB8 is plugged into the same string of hubs but only part way along the string.

Short Answer

Serial devices are listed deterministically by path in /dev/serial/by-path with a verbose naming scheme that represents the physical device connection. This name will always be the same as long as the device is plugged into the same USB port. These are symlinks to the dynamic naming of devices in /dev/ttyUSB*

As long as you always have your arduino plugged into the same port on your machine or USB hub it will always result in the same path name in the by-path directory. You could then just symlink to the appropriate by-path filename to make sure your script always works and has a memorable name. This solution works even if you have several arduinos connected to your system.

If I plugged an arduino into the second USB2.0 port (4.2) on my system I could always access it at /dev/arduino using this symlink definition:

ln -s /dev/serial/by-path/pci-0000:00:14.0-usb-0:4.2:1.0-port0 /dev/arduino

Background Info

The ttyUSB0, ttyUSB1 naming scheme depends on the time order of connecting devices and the next available index. The same device may show up with a new index if the system was unable to free all handles to it from a previous connection of the device or even if an error caused the device to renumerate and the previous file handle was unavailable still. The by-path directory symlinks always point the constant physical port name to the latest ttyUSB# instance of a device connection.

$ ls -la /dev/serial/by-path/
total 0
drwxr-xr-x. 2 root root 200 Feb  8 21:46 .
drwxr-xr-x. 4 root root  80 Feb  7 22:17 ..
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:1:1.0-port0 -> ../../ttyUSB4            
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.0-port0 -> ../../ttyUSB0          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.1-port0 -> ../../ttyUSB1          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.2-port0 -> ../../ttyUSB2          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.3-port0 -> ../../ttyUSB3          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.2:1.0-port0 -> ../../ttyUSB6          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.3.3.1.2.4:1.0-port0 -> ../../ttyUSB7  
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.3.3.3:1.0-port0 -> ../../ttyUSB8   

In this example I have five USB-serial devices connected as well as several hubs. The "pci-0000:00:14.0-usb-" prefix is always the same because all of my USB chips are on the same PCI bus. If you had a thunderbolt connected USB hub this number would probably change. The subsequent numbering scheme represents the path to the device on a specific USB port. For the symlink beginning 0:1 I'm not sure what the 0 is, 1 is the address of the USB3 chip on my motherboard, 4.1, 4.2, 4.3 are the three builtin USB2 ports on my system. The device connected to port 4.1 is a USB-serial adapter with four serial ports on a single device, therefore it has final values of 1.0, 1.1, 1.2, and 1.3 to signify all of the ttys on the single device.

ttyUSB7 is a USB-Serial adapter plugged into a long string of USB hubs. ttyUSB8 is plugged into the same string of hubs but only part way along the string. The ttyUSB* naming shows you what order I plugged the devices in. You can see that /dev/ttyUSB5 is missing. That is because I unplugged a device that regularly fails to get all of its file handles released so when I plugged it in again that number wasn't reused. Despite that I can still reach the device under it's new filename because I have the by-path symlink.

Alternative Naming Scheme

On my CentOS system these by-path symlinks are being created with UDEV rules from the default system ruleset in /usr/lib/udev/rules.d/60-serial.rules.

Since my system doesn't have the ability to extend the PCI bus the pci-0000:00:14.0-usb- prefix never changes and is just more typing to point to a specific port. For the sake of convenience in having a shorter naming scheme, the UDEV rule below uses a variation of the 60-serial.rules to create a symlink in /dev/ which strips pci-0000:00:14.0-usb- off the beginning of the link name and -port0 off the end.

My new rule relies on the ID_PATH environment variable having been defined (ID_PATH contains the long version device path - "pci-0000:00:14.0-usb-0:4.3.3.1.2.4:1.0-port0"). You don't have to care about how ID_PATH gets created, you just need to run your rule after it exists in order to use/manipulate it. UDEV rules are executed in numerical naming order so I just named my rule with a 90 prefix, /etc/udev/rules.d/90-usbtty.rules, rather than trying to find out when ID_PATH got defined and then it was available to modify.

This rule creates shorter names and puts them in the /dev/ directory for my convenience.

More Involved Naming Code

It is nice to have a one-liner UDEV rule,   but if it were any more complex  (and arguably now) it would need a separate script file to be intelligible and functional. If you did want to create such a script the example below could be a starting point. You can substitute /bin/sh for any other executable file.

UDEV environment variables are available in the called program and standard output (e.g. echo/print statements) of the form ENV_VAR='VALUE' cause new environment variables to become available.

One last gotcha I ran into that might force a separte script rather than the one-liner is quote escaping. You can see in the script version of the rule that the more standard -d '-' is passed to cut, whereas in the oneliner --delimiter=- was required. As far as I can tell UDEV doesn't allow quote escaping, so specifying the delimiter without a quote is necessary. If you try -d - then the dash is interpreted as a flag to cut rather than a delimeter character.

Fixed typos; improved grammar, punctuation and formatting.
Source Link

For the sake of convenience in having a shorter naming scheme, the rule below uses a variation of the 60-serial.rules to create a symlink in /dev/ which strips pci-0000:00:14.0-usb- off the beginning of the link name and -port0 off the end.

This rule relies on the ID_PATH environment variable having been defined. Rules are executed in numerical naming order so I just named bymy rule /etc/udev/rules.d/90-usbtty.rules rather than trying to find out when ID_PATH got defined.

$ ls -lald /dev/ttyUSB.*
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:1:1.0 -> ttyUSB4                           
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.0 -> ttyUSB0                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.1 -> ttyUSB1                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.2 -> ttyUSB2                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.3 -> ttyUSB3                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.2:1.0 -> ttyUSB6                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.3.3.1.2.4:1.0 -> ttyUSB7                 
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.3.3.3:1.0 -> ttyUSB8                     

It is nice that this rule all fits in a single line of the UDEV file but,  but if it were any more complex     (and arguablearguably now) it would need it'sits own script. You  You can substituesubstitute /bin/sh/bin/sh for any other executable file. UDEV  UDEV environment variables are available in the called program and standard and standard output from (echofrom echo/print) statements of) of the form ENV_VAR='VALUE'ENV_VAR='VALUE' cause cause new environment variables to become available.

#!/bin/sh

p=$(echo $"${ID_PATH}" | cut -d '-' -f 4)
echo "USB_PATH='$p'"

You can see in the script version of the rule that the more standard -d '-' is passed to cutcut, whereas in the oneliner --delimiter=- was required. As far as I can tell UDEV doesn't allow quote escaping, so specifingspecifying the delimeterdelimiter without a quote is necessary.

For the examples shown I have 5five USB-serial devices connected as well as several hubs. The 'ttyUSB.' "ttyUSB." prefix is just a string in the rule. The subsequent numbering scheme represents the path to the device. For the symlink beginning 0:1 I'm not sure what the 0 is, 1 is the address of my builtin USB3 chip, 4.1, 4.2, 4.3 are the three builtin USB2 ports on my system. The device connected to port 4.1 is a USB-serial adapter with 4four serial portports on a single device, therefore it has final values of 1.0, 1.1, 1.2, and 1.3 to signify all of the ttys on the single device.

For the sake of convenience in having a shorter naming scheme the rule below uses a variation of the 60-serial.rules to create a symlink in /dev/ which strips pci-0000:00:14.0-usb- off the beginning of the link name and -port0 off the end.

This rule relies on the ID_PATH environment variable having been defined. Rules are executed in numerical naming order so I just named by rule /etc/udev/rules.d/90-usbtty.rules rather than trying to find out when ID_PATH got defined.

$ ls -la /dev/ttyUSB.*
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:1:1.0 -> ttyUSB4                           
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.0 -> ttyUSB0                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.1 -> ttyUSB1                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.2 -> ttyUSB2                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.3 -> ttyUSB3                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.2:1.0 -> ttyUSB6                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.3.3.1.2.4:1.0 -> ttyUSB7                 
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.3.3.3:1.0 -> ttyUSB8                     

It is nice that this rule all fits in a single line of the UDEV file but if it were any more complex  (and arguable now) it would need it's own script. You can substitue /bin/sh for any other executable file. UDEV environment variables are available in the called program and standard output from (echo/print) statements of the form ENV_VAR='VALUE' cause new environment variables to become available.

#!/bin/sh

p=$(echo ${ID_PATH} | cut -d '-' -f 4)
echo "USB_PATH='$p'"

You can see in the script version of the rule that the more standard -d '-' is passed to cut whereas in the oneliner --delimiter=- was required. As far as I can tell UDEV doesn't allow quote escaping so specifing the delimeter without a quote is necessary.

For the examples shown I have 5 USB-serial devices connected as well as several hubs. The 'ttyUSB.' prefix is just a string in the rule. The subsequent numbering scheme represents the path to the device. For the symlink beginning 0:1 I'm not sure what the 0 is, 1 is the address of my builtin USB3 chip, 4.1, 4.2, 4.3 are the three builtin USB2 ports on my system. The device connected to port 4.1 is a USB-serial adapter with 4 serial port on a single device therefore it has final values of 1.0, 1.1, 1.2, and 1.3 to signify all of the ttys on the single device.

For the sake of convenience in having a shorter naming scheme, the rule below uses a variation of the 60-serial.rules to create a symlink in /dev/ which strips pci-0000:00:14.0-usb- off the beginning of the link name and -port0 off the end.

This rule relies on the ID_PATH environment variable having been defined. Rules are executed in numerical naming order so I just named my rule /etc/udev/rules.d/90-usbtty.rules rather than trying to find out when ID_PATH got defined.

$ ls -ld /dev/ttyUSB.*
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:1:1.0 -> ttyUSB4                           
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.0 -> ttyUSB0                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.1 -> ttyUSB1                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.2 -> ttyUSB2                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.3 -> ttyUSB3                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.2:1.0 -> ttyUSB6                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.3.3.1.2.4:1.0 -> ttyUSB7                 
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.3.3.3:1.0 -> ttyUSB8                     

It is nice that this rule all fits in a single line of the UDEV file,  but if it were any more complex   (and arguably now) it would need its own script.  You can substitute /bin/sh for any other executable file.  UDEV environment variables are available in the called program and standard output (from echo/print statements) of the form ENV_VAR='VALUE' cause new environment variables to become available.

#!/bin/sh

p=$(echo "${ID_PATH}" | cut -d '-' -f 4)
echo "USB_PATH='$p'"

You can see in the script version of the rule that the more standard -d '-' is passed to cut, whereas in the oneliner --delimiter=- was required. As far as I can tell UDEV doesn't allow quote escaping, so specifying the delimiter without a quote is necessary.

For the examples shown I have five USB-serial devices connected as well as several hubs. The "ttyUSB." prefix is just a string in the rule. The subsequent numbering scheme represents the path to the device. For the symlink beginning 0:1 I'm not sure what the 0 is, 1 is the address of my builtin USB3 chip, 4.1, 4.2, 4.3 are the three builtin USB2 ports on my system. The device connected to port 4.1 is a USB-serial adapter with four serial ports on a single device, therefore it has final values of 1.0, 1.1, 1.2, and 1.3 to signify all of the ttys on the single device.

Source Link
user8472
  • 111
  • 1
  • 3

Serial devices are listed deterministically by path in /dev/serial/by-path with a verbose naming scheme.

$ ls -la /dev/serial/by-path/
total 0
drwxr-xr-x. 2 root root 200 Feb  8 21:46 .
drwxr-xr-x. 4 root root  80 Feb  7 22:17 ..
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:1:1.0-port0 -> ../../ttyUSB4            
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.0-port0 -> ../../ttyUSB0          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.1-port0 -> ../../ttyUSB1          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.2-port0 -> ../../ttyUSB2          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.1:1.3-port0 -> ../../ttyUSB3          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.2:1.0-port0 -> ../../ttyUSB6          
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.3.3.1.2.4:1.0-port0 -> ../../ttyUSB7  
lrwxrwxrwx. 1 root root  13 Feb  8 21:46 pci-0000:00:14.0-usb-0:4.3.3.3:1.0-port0 -> ../../ttyUSB8   

As long as you always have your USB-serial device plugged into the same port on your machine or USB hub it will always result in the same path name in the by-path directory. On my CentOS system these symlinks are being created with UDEV rules from /usr/lib/udev/rules.d/60-serial.rules.

For the sake of convenience in having a shorter naming scheme the rule below uses a variation of the 60-serial.rules to create a symlink in /dev/ which strips pci-0000:00:14.0-usb- off the beginning of the link name and -port0 off the end.

This rule relies on the ID_PATH environment variable having been defined. Rules are executed in numerical naming order so I just named by rule /etc/udev/rules.d/90-usbtty.rules rather than trying to find out when ID_PATH got defined.

SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", IMPORT{program}="/bin/sh -c 'echo USB_PATH=$( echo ${ID_PATH} | cut --delimiter=- -f 4 )'", SYMLINK+="ttyUSB.$env{USB_PATH}"

This rule creates shorter names in a convenient directory.

$ ls -la /dev/ttyUSB.*
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:1:1.0 -> ttyUSB4                           
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.0 -> ttyUSB0                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.1 -> ttyUSB1                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.2 -> ttyUSB2                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.1:1.3 -> ttyUSB3                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.2:1.0 -> ttyUSB6                         
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.3.3.1.2.4:1.0 -> ttyUSB7                 
lrwxrwxrwx. 1 root root         7 Feb  8 21:46 /dev/ttyUSB.0:4.3.3.3:1.0 -> ttyUSB8                     

It is nice that this rule all fits in a single line of the UDEV file but if it were any more complex (and arguable now) it would need it's own script. You can substitue /bin/sh for any other executable file. UDEV environment variables are available in the called program and standard output from (echo/print) statements of the form ENV_VAR='VALUE' cause new environment variables to become available.

SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", IMPORT{program}="/path/to/script", SYMLINK+="ttyUSB.$env{USB_PATH}"
#!/bin/sh

p=$(echo ${ID_PATH} | cut -d '-' -f 4)
echo "USB_PATH='$p'"

You can see in the script version of the rule that the more standard -d '-' is passed to cut whereas in the oneliner --delimiter=- was required. As far as I can tell UDEV doesn't allow quote escaping so specifing the delimeter without a quote is necessary.

For the examples shown I have 5 USB-serial devices connected as well as several hubs. The 'ttyUSB.' prefix is just a string in the rule. The subsequent numbering scheme represents the path to the device. For the symlink beginning 0:1 I'm not sure what the 0 is, 1 is the address of my builtin USB3 chip, 4.1, 4.2, 4.3 are the three builtin USB2 ports on my system. The device connected to port 4.1 is a USB-serial adapter with 4 serial port on a single device therefore it has final values of 1.0, 1.1, 1.2, and 1.3 to signify all of the ttys on the single device.

ttyUSB7 is a USB-Serial adapter plugged into a long string of USB hubs. ttyUSB8 is plugged into the same string of hubs but only part way along the string.