I had been struggling to come up with a solution for providing *only* the devices needed to each instance of Octoprint, especially given part of the “job finished” routine is cutting power to the printers, meaning the /dev/ttyUSB<ID> was liable to change every time.
The solution ended up being udev rules. I opted to assign my static devices based on exact kernel ID of the port because I expected to use the same model webcam and figured there would be no unique attribute to select off of in that case. If I was going to identify 2/4 of the ports, it was only 1 more guess and check to hit 4/4.
The Process
Identify the kernel ID or unique attributes of your device, udevadm provides attributes from the selected device up to the parent root hub which can be helpful in verification of the device you’re targeting.
udevadm info --name=/dev/ttyUSB0 --attribute-walk
After finding a suitable set of attributes, you’ll want to create (or edit) the file located at
/etc/udev/rules.d/99-usb-serial.rules
I keep my rules in the following syntax, but the order isn’t important as you’ve got a comma between the values and no space between the key definition and the value. I’ve provided the output of udevadm (truncated) and then my associated rule to provide some context
looking at device '/devices/pci0000:00/0000:00:14.0/usb3/3-11/3-11:1.0/video4linux/video0':
KERNEL=="video0"
SUBSYSTEM=="video4linux"
DRIVER==""
ATTR{dev_debug}=="0"
ATTR{index}=="0"
ATTR{name}=="C270 HD WEBCAM"
ATTR{power/async}=="disabled"
ATTR{power/control}=="auto"
ATTR{power/runtime_active_kids}=="0"
ATTR{power/runtime_active_time}=="0"
ATTR{power/runtime_enabled}=="disabled"
ATTR{power/runtime_status}=="unsupported"
ATTR{power/runtime_suspended_time}=="0"
ATTR{power/runtime_usage}=="0"
looking at parent device '/devices/pci0000:00/0000:00:14.0/usb3/3-11/3-11:1.0':
KERNELS=="3-11:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="uvcvideo"
ATTRS{authorized}=="1"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceClass}=="0e"
ATTRS{bInterfaceNumber}=="00"
ATTRS{bInterfaceProtocol}=="00"
ATTRS{bInterfaceSubClass}=="01"
ATTRS{bNumEndpoints}=="01"
ATTRS{iad_bFirstInterface}=="00"
ATTRS{iad_bFunctionClass}=="0e"
ATTRS{iad_bFunctionProtocol}=="00"
ATTRS{iad_bFunctionSubClass}=="03"
ATTRS{iad_bInterfaceCount}=="02"
...
KERNELS=="3-11:1.0", SUBSYSTEM=="video4linux", SYMLINK+="ender_max_video"
You can see I opted to match on KERNELS, knowing this was a specific port on the front of the machine and then targeted the video4linux SUBSYSTEM instead of the parent SUBSYSTEMS of uvcvideo. Notice the plural of SUBSYSTEMS vs SUBSYSTEM. They *are* different values and will not match the same fields.
The SYMLINK+= is the key that tells udev to create another device in /dev with the name you’ve provided.
After you’ve made the appropriate changes, it’s time to reload your rules and trigger the device manager to populate the new devices you’ve defined.
udevadm control --reload-rules
udevadm trigger