The multiple-identical-USB-device problem
I have a Rasperry Pi with four cameras. I take pix with fswebcam
which identifies the cameras as /dev/video0
.. video3
. Sometimes the camera is video0
, vide02
, video4
and video6
but we can forget about that for now.
I need a persistent ID to identify a camera number so that, e.g. video0
is always the same camera because I caption the pictures. Unfortunately this doesn’t happen reliably - on boot, the cameras get enumerated as video0
..video3
but not always the same way.
The cameras all have the same ID and serial number.
The solution to this problem involves udev rules, but there's a lot of fishhooks there as well.
If you issue the command
udevadm info –attribute-walk –path=/dev/video0
you get a screed of output but the salient bits are
KERNEL=”video0”, SUBSYSTEM=”video4linux” and KERNELS=”1:1.2.4:1.0”.
The KERNELS bit is a USB hub port. With four cameras there are four of these - they do not change on reboot , but the video{x}
associated with a port may change.
So we need a udev rule to tie a video number to a USB hub port - something like:
KERNEL==”video0”,SUBSYSTEM=”video4linux”,KERNELS==”1:1.2.4:1.0”,SYMLINK+=”camera0”
Looks simple – access the camera with
fswebcam –d $realpath /dev/camera0
Except it doesn’t work – if you put this in a udev rule and the system has allocated video0 (on boot) to a different port, the udev rule is ignored. The symlink to /dev/camera0
basically says no such device
. Square one.
What we want is to bind a symlink to a USB hub address, not a video{x}
number. It took a Python program.
First step was to run
fswebcam –d /dev/video${x} tst.jpg
for x
between 1 and 8. The existence of tst.jpg
after each call identifies whether there is a camera on this video number. From this make a list of active video numbers. My experience has been that it is either 0,1,2,3
or 0,2,4,6
for cameras I have used.
Others may of course build this list using a different process.
Then for each video number in the list run
udevadm info –attribute-walk –path=/dev/videox > dd
and extract the KERNELS= line
from dd
. From this process you end up with a list of the USB port addresses for the cameras. Sort this list so that at the next step, you always process it in the same order. Call this the "address list".
Run the udevadm … > dd
thing again and make a list that looks like
KERNEL==”video0”, SUBSYSTEM=”video4linux”,KERNELS==”1:1.2.4:1.0 ”,SYMLINK+=”camerax”. Call this the “video list”.
Now step through the address list - for each entry find the corresponding entry from the video list. Create a new list that looks like a collection of lines like
KERNEL==”video0”, SUBSYSTEM=”video4linux”,KERNELS==”1:1.2.4:1.0 ”,SYMLINK+=”camera2”
The x (symlink number) is replaced by the sequence number in the address list.
Now you have a udev rule that works. A symlink that is tied to a USB hub address no matter what video number is allocated to that port at boot.
Write the final list into a file /etc/udev/rules.d/cam.rules
. Run udevadm trigger
to activate it and the job is done. /dev/camera2
will be the same camera (USB port) regardless of its video number.