Udev is a device manager for Linux that dynamically creates and removes nodes for hardware devices. In short, it helps your computer find your robot easily. By default, hardware devices attached to your Linux (Ubuntu) PC will belong to the root user. This means that any programs (e.g. ROS nodes) running as an unpriveleged (i.e. not root) user will not be able to access them. On top of that, devices will receive names such as ttyACMx
and ttyUSBx
arbitrarily based on the order in which they were plugged in. Luckily, you can solve this, and more, with udev rules.
You probably already have at least one udev rule on your system that solves the naming problem for network devices, and you can take a peek at it in the /etc/udev/rules.d/
folder – it’s probably named 70-persistent-net.rules
.
Some driver/software packages will already provide udev rules you can use. Check the /etc/udev/rules.d/
folder to see if there’s anything installed already. If the package is lazy and gives you a udev rule to install yourself, you can do this using:
[code lang=bsh]sudo cp <rule file> /etc/udev/rules.d/>[/code]
Writing a new udev rule:
If you still need to write your own rule to setup naming and permissions for your device, read on. Rules can get extremely complex, but the below should cover 99% of use cases for ROS applications. If you’re looking for 99.9%, I suggest you start here. As an example, we will examine the udev rule provided by the urg_node
driver in ROS:
[code lang=bsh]SUBSYSTEMS=="usb", KERNEL=="ttyACM[0-9]*", ACTION=="add", ATTRS{idVendor}=="15d1", ATTRS{idProduct}=="0000", MODE="666", PROGRAM="/opt/ros/hydro/lib/urg_node/getID /dev/%k q", SYMLINK+="sensors/hokuyo_%c", GROUP="dialout"[/code]
A udev rule is made up of a bunch of comma separated tags, as above. The tags are divided into two parts: matching and configuration, however they can be written into the rule in any order (confusingly enough).
Matching:
The matching part lets the udev device manager match the rule to the device you want. The manager will try to match all new devices as they get plugged in, so it’s important that the rule be specific enough to capture only the device you’re looking for, otherwise you’ll end up with a /dev/hokuyo
symlink to an IMU. There are many potential matching tags, and the best way to pick the useful ones is to get all the device attributes straight from udev.
Run the following command, inserting a <devpath> such as /dev/ttyACM0:
[code lang=bsh] udevadm info -a -p $(udevadm info -q path -n <devpath>) [/code]
You will get a list of all device attributes visible to udev. looking at device ‘…/ttyACM0’:
[code lang=bsh] KERNEL=="ttyACM0" SUBSYSTEM=="tty" DRIVER=="" looking at parent device '...': KERNELS=="3-3:1.0" SUBSYSTEMS=="usb" DRIVERS=="cdc_acm" ATTRS{bInterfaceClass}=="02" ATTRS{bInterfaceNumber}=="00" looking at parent device '...': ... ATTRS{idVendor}=="0483" ATTRS{idProduct}=="5740" ... [/code]
Each of the device attributes is a potential tag. You can use any of the tags in the first section to filter, along with tags from a parent device. Use regex to make matching more flexible (e.g. [0-9] to match any number, * to match anything at all). Example:
[code lang=bsh]SUBSYSTEM=="tty", KERNEL=="ttyACM[0-9]*", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ...[/code]
Due to the way udev is designed, you can only pull in the tags from one parent device. So in the above example you can always use the KERNEL
and the SUBSYSTEM
tag in your udev rule, but you cannot use both DRIVERS
and ATTRS{idVendor}
to do matching. Usually this is not a problem, since idVendor and idProduct are always present, and identify the majority of device types uniquely.
You should also add the ACTION
tag, which is usually “add”, sometimes “remove” if you want to do something when the device is unplugged.
[code lang=bsh]..., ACTION=="add", ...[/code]
Configuration:
Now that you have a rule that matches the device you want, you can add several configuration tags:
Tag | Usage |
MODE="0666" |
Set permissions to allow any user read/write access to the device. |
SYMLINK+=”hokuyo” |
Create a symlink in /dev/ for this device. |
RUN+=”/bin/echo 'hello world' |
Execute an arbitrary command. (Advanced usage, more here) |
It is good practice to make sure symlinks are unique for each device, so the above is actually poor practice! If you have multiple devices of the same type (e.g. 2 Hokuyos), or if you have multiple devices using a generic USB-to-Serial converter (e.g. FTDI), a basic idVendor and idProduct rule will not properly discriminate between these devices, since udev will map all matching devices to the same symlink. There are several approaches:
1. Directly through device attributes:
If your device has a unique identifier, such as a serial number, encoded in its attributes, you can painlessly create a unique symlink for each device:
[code lang=bsh]..., SYMLINK+=”device_$attr{serial}”, ...[/code]
The manufacturer will not always make it this easy for you. If a parent device has a serial number, you can use the following trick using environment variables. Create a udev rule for the parent device to store an environment variable:
[code lang=bsh]..., <match parent device>..., ENV{SERIAL_NUMBER}="$attr{serial_number}"[/code]
And a rule for the child device that uses the variable in the symlink:
[code lang=bsh]..., <match child device>..., SYMLINK+="device_$env{SERIAL_NUMBER}"[/code]
2. Through an external program:
If the manufacturer does not expose a unique identifier through the device attributes at all, you could execute an external command using the PROGRAM
tag:
[code lang=bsh]PROGRAM="/bin/device_namer %k", SYMLINK+="%c"[/code]
Unlike the RUN
tag which spins off, this command will block (needs to be executed before the rule is fully processed), so it must return quickly. The urg_node driver above uses this tag to execute a ROS binary:
[code lang=bsh]PROGRAM="/opt/ros/hydro/lib/urg_node/getID /dev/%k q", SYMLINK+="sensors/hokuyo_%c"[/code]
Substitution argument %k
refers to the device path relative to /dev/
, and %c
refers to the output of the PROGRAM
tag.
Running a new udev rule:
Once you have copied (sudo cp
) your rule into the /etc/udev/rules.d/
folder, you can test it with your device. To get udev to recognize your rule, run the following command:
[code lang=bsh]sudo udevadm control --reload-rules && sudo service udev restart && sudo udevadm trigger[/code]
You should be able to find a symlink in /dev/
that links to the full device path (e.g. /dev/ttyACM0
), and the permissions on the device path should be read/write for all users. If your permissions aren’t being set, and you symlink is not being created in /dev/
as expected, you can try simulating udev’s processing of the rule by running the following with the appropriate device path:
[code lang=bsh]udevadm test $(udevadm info -q path -n /dev/ttyACM0)[/code]
Things to keep in mind
- Check that your rule follows the naming convention –
<priority>-<device name>.rules
. Technically you can have multiple rules for the same device, and the number determines what order they’d get executed in. Since we’re writing addon rules, a priority of 99 is safest. - You can have multiple rules in a file separated by newlines. Make sure that each individual rule is on one line.
- Check that all tags (matching and configuration) are comma separated.
- Check that your rule file has a trailing newline.
- Check that your rule is owned by the root user –
ll /etc/udev/rules.d/
should say ‘root root
‘ for the rule file.
Trackbacks/Pingbacks