En primer lugar, conectamos el Arduino al puerto USB para que su dispositivo de bloques se añada a la carpeta /dev, y a continuación listamos los dispositivos conectados:
user@localhost:~$ lsusb
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 007 Device 002: ID 046d:c517 Logitech, Inc. LX710 Cordless Desktop Laser
Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 002: ID 0424:2504 Standard Microsystems Corp. USB 2.0 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 002: ID 2341:0043 Arduino SA Uno R3 (CDC ACM)
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Vemos que el dispositivo conectado es Arduino UNO R3; el tipo de dispositivo es CDC ACM.
user@localhost:~$ ls -hals /dev/tty*
0 crw-rw-rw- 1 root tty 5, 0 ene 2 18:02 /dev/tty
0 crw--w---- 1 root tty 4, 0 ene 2 17:37 /dev/tty0
0 crw--w---- 1 root tty 4, 1 ene 2 17:37 /dev/tty1
...
0 crw-rw---- 1 root dialout 166, 0 ene 2 17:56 /dev/ttyACM0
...
0 crw-rw---- 1 root dialout 4, 64 ene 2 17:37 /dev/ttyS0
0 crw-rw---- 1 root dialout 4, 65 ene 2 17:37 /dev/ttyS1
...
En el listado de dispositivos tipo tty encontramos el /dev/ttyACM0 que corresponde al Arduino.
user@localhost:~$ sudo udevadm test dev
calling: test
version 237
This program is for debugging only, it does not run any program
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.
Load module index
Parsed configuration file /lib/systemd/network/99-default.link
Created link configuration context.
Reading rules file: /lib/udev/rules.d/39-usbmuxd.rules
Reading rules file: /lib/udev/rules.d/40-usb-media-players.rules
Reading rules file: /lib/udev/rules.d/40-usb_modeswitch.rules
...
Reading rules file: /etc/udev/rules.d/70-snap.core.rules
...
Reading rules file: /lib/udev/rules.d/99-systemd.rules
rules contain 393216 bytes tokens (32768 * 12 bytes), 37361 bytes strings
28452 strings (241719 bytes), 25114 de-duplicated (207697 bytes), 3339 trie nodes used
ACTION=add
DEVPATH=/dev
Unload module index
Unloaded link configuration context
Vemos que prácticamente todos los archivos de reglas estan en la carpeta /lib/udev/rules.d excepto uno, que está en /etc. Como no queremos modificar ninguno de estos archivos, vamos a añadir nuestra regla udev en la carpeta /etc.
user@localhost:~$ grep -rnw '/lib/udev/rules.d' -e 'ACM'
/lib/udev/rules.d/77-mm-usb-device-blacklist.rules:194:# Netchip Technology, Inc. Linux-USB Serial Gadget (CDC ACM mode)
/lib/udev/rules.d/77-mm-usb-serial-adapters-greylist.rules:37:# Netchip Technology, Inc. Linux-USB Serial Gadget (CDC ACM mode
Vemos que dos archivos de configuración son los que contienen la partícula 'ACM', así que uno de ellos es el responsable de establecer el nombre del dispositivo; pondremos ahí nuestra regla udev.
Para saber que atributos formarán la regla, buscamos más información con udevadm.
user@localhost:~$ udevadm info --name=/dev/ttyACM0 --attribute-walk
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/pci0000:00/0000:00:1a.1/usb4/4-1/4-1:1.0/tty/ttyACM0':
KERNEL=="ttyACM0"
SUBSYSTEM=="tty"
DRIVER==""
looking at parent device '/devices/pci0000:00/0000:00:1a.1/usb4/4-1/4-1:1.0':
KERNELS=="4-1:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="cdc_acm"
ATTRS{authorized}=="1"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceClass}=="02"
ATTRS{bInterfaceNumber}=="00"
ATTRS{bInterfaceProtocol}=="01"
ATTRS{bInterfaceSubClass}=="02"
ATTRS{bNumEndpoints}=="01"
ATTRS{bmCapabilities}=="6"
ATTRS{supports_autosuspend}=="1"
looking at parent device '/devices/pci0000:00/0000:00:1a.1/usb4/4-1':
KERNELS=="4-1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="02"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="8"
ATTRS{bMaxPower}=="100mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 2"
ATTRS{bcdDevice}=="0001"
ATTRS{bmAttributes}=="c0"
ATTRS{busnum}=="4"
ATTRS{configuration}==""
ATTRS{devnum}=="2"
ATTRS{devpath}=="1"
ATTRS{idProduct}=="0043"
ATTRS{idVendor}=="2341"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Arduino (www.arduino.cc)"
ATTRS{maxchild}=="0"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="unknown"
ATTRS{serial}=="5583432343335161F001"
ATTRS{speed}=="12"
ATTRS{urbnum}=="84"
ATTRS{version}==" 1.10"
looking at parent device '/devices/pci0000:00/0000:00:1a.1/usb4':
KERNELS=="usb4"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{authorized}=="1"
ATTRS{authorized_default}=="1"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{bConfigurationValue}=="1"
ATTRS{bDeviceClass}=="09"
ATTRS{bDeviceProtocol}=="00"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{bMaxPower}=="0mA"
ATTRS{bNumConfigurations}=="1"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bcdDevice}=="0415"
ATTRS{bmAttributes}=="e0"
ATTRS{busnum}=="4"
ATTRS{configuration}==""
ATTRS{devnum}=="1"
ATTRS{devpath}=="0"
ATTRS{idProduct}=="0001"
ATTRS{idVendor}=="1d6b"
ATTRS{interface_authorized_default}=="1"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Linux 4.15.0-72-generic uhci_hcd"
ATTRS{maxchild}=="2"
ATTRS{product}=="UHCI Host Controller"
ATTRS{quirks}=="0x0"
ATTRS{removable}=="unknown"
ATTRS{serial}=="0000:00:1a.1"
ATTRS{speed}=="12"
ATTRS{urbnum}=="27"
ATTRS{version}==" 1.10"
looking at parent device '/devices/pci0000:00/0000:00:1a.1':
KERNELS=="0000:00:1a.1"
SUBSYSTEMS=="pci"
DRIVERS=="uhci_hcd"
ATTRS{broken_parity_status}=="0"
ATTRS{class}=="0x0c0300"
ATTRS{consistent_dma_mask_bits}=="32"
ATTRS{d3cold_allowed}=="0"
ATTRS{device}=="0x2835"
ATTRS{dma_mask_bits}=="32"
ATTRS{driver_override}=="(null)"
ATTRS{enable}=="1"
ATTRS{irq}=="17"
ATTRS{local_cpulist}=="0-1"
ATTRS{local_cpus}=="03"
ATTRS{msi_bus}=="1"
ATTRS{numa_node}=="-1"
ATTRS{revision}=="0x02"
ATTRS{subsystem_device}=="0x01da"
ATTRS{subsystem_vendor}=="0x1028"
ATTRS{vendor}=="0x8086"
looking at parent device '/devices/pci0000:00':
KERNELS=="pci0000:00"
SUBSYSTEMS==""
DRIVERS==""
Vemos que el dispositivo ttyACM0 está en el subsistema tty.
Usaremos este script para averiguar cual es el último ttyS disponible, y usar el siguiente libre; lo guardamos en /usr/local/sbin/unique-num.
#!/bin/bash
if [ $# -ne 3 ]; then
echo "Usage: $0 location prefix var-name">&2
exit 1
fi
location="$1"
prefix="$2"
key="$3"
needindex=1
index=0
while [ $needindex -eq 1 ]
do
if [ ! -e $location/$prefix$index ]; then
needindex=0
echo "$key=$index"
else
(( index++ ))
fi
done
Ya podemos deducir nuestra regla:
user@localhost:~$ sudo echo 'IMPORT{program}="/usr/local/sbin/unique-num /dev ttyS TTYS_NUM' > /etc/udev/rules.d/76-mm-usb-device-blacklist.rules
user@localhost:~$ sudo echo 'SUBSYSTEM=="tty", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0043", SYMLINK+="ttyS$env{TTYS_NUM}"' >> /etc/udev/rules.d/76-mm-usb-device-blacklist.rules
user@localhost:~$ sudo udevadm trigger
user@localhost:~$ ls -hals /dev/tty* 0 crw-rw-rw- 1 root tty 5, 0 ene 2 18:02 /dev/tty
0 crw--w---- 1 root tty 4, 0 ene 2 17:37 /dev/tty0
0 crw--w---- 1 root tty 4, 1 ene 2 17:37 /dev/tty1
...
0 crw-rw---- 1 root dialout 166, 0 ene 2 17:56 /dev/ttyACM0
...
0 crw-rw---- 1 root dialout 4, 64 ene 2 17:37 /dev/ttyS0
0 crw-rw---- 1 root dialout 4, 65 ene 2 17:37 /dev/ttyS1
...
0 lrwxrwxrwx 1 root root 7 ene 2 17:56 /dev/ttyS32 -> ttyACM0
...
Una vez añadida la regla, recargamos las reglas del servicio udev, y hacemos un listado de la carpeta /dev para asegurarnos que nuestro enlace está ahí.
Y esto es todo.