udevルールファイルを使い、USBデバイスを接続したときのデバイスファイル名を固定する方法について。

ArduinoなどUSB-シリアル変換デバイスを複数接続すると、デバイスごとにデバイスファイル名(/dev/ttyUSBn)が作成されるが、接続する順序によってファイル名の番号部分が決まるため、順序が変わればファイル名も変わってしまう。 この動作は設定ファイルにデバイスファイル名を指定する場合には不都合なため、デバイスの種類やシリアル番号ごとに固定した(persistentな)デバイスファイル名を/dev/ttyUSBnとは別にシンボリックリンクとして作成させるようにする。

ここではUSB-シリアル変換器を対象としているが、以下の方法はUSB接続のデバイスを含む、udevで扱えるデバイスすべてに適用できる。

デバイス固有の値を求める

ファイル名を固定するにあたり、まずはデバイスを特定するための値として、USBデバイスのvendor ID, product ID, serial numberの3つの値を求める。

Arduinoなど、FTID(Future Technology Devices International Ltd)製のチップFT232R等を使用しているデバイスでは、vendor IDproduct IDはそれぞれ次のとおり。

vendor ID 0403
product ID 6001

秋月電子 ESP32-DevKitCなど、Silicon Labs製のチップCP2102N等を使用しているデバイスでは次のとおり。

vendor ID 10c4
product ID ea60

それ以外のデバイスやシリアル番号を含めて調べる方法については、次のとおり。

udevadm infoコマンド

udevadm infoコマンドを使う場合は次のようにする。 -q propertyでプロパティ情報を要求し、-nには対象のデバイスファイル名(ここでは/dev/ttyUSB0)を指定する。 デバイスは現在接続されている必要がある。

udevadm infoコマンドでデバイス情報を取得する
$ udevadm info -q property -n /dev/ttyUSB0 | grep -E "ID_SERIAL_SHORT=|ID_VENDOR_ID=|ID_MODEL_ID="
ID_MODEL_ID=6001
ID_SERIAL_SHORT=XXXXXXXX
ID_VENDOR_ID=0403

値の対応は次のとおり。

ID_MODEL_ID
product ID
ID_SERIAL_SHORT
serial number
ID_VENDOR_ID
vendor ID

dmesgまたは/var/log/kern.log

デバイスを接続したときに出力されるログ(dmesgまたは/var/log/kern.log)からも求めることができる。 以下の3箇所から必要な値を求める。

dmesgからデバイス情報を取得する
[1398698.113964] usb 1-6: new full-speed USB device number 6 using xhci_hcd
[1398698.267466] usb 1-6: New USB device found, idVendor=0403, idProduct=6001
[1398698.267469] usb 1-6: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[1398698.267471] usb 1-6: Product: FT232R USB UART
[1398698.267472] usb 1-6: Manufacturer: FTDI
[1398698.267473] usb 1-6: SerialNumber: XXXXXXXX
[1398698.270667] usb 1-6: Detected FT232RL
[1398698.271080] usb 1-6: FTDI USB Serial Device converter now attached to ttyUSB0

lsusbコマンド

lsusbコマンドを使う場合は、まずオプションなしで実行することで接続されているUSBデバイスとvendor ID, product IDの一覧を表示する

lsusbコマンドでデバイスとvendor ID・product IDの一覧を表示する
$ lsusb
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
    :
    :
Bus 001 Device 008: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
    :

目的のデバイスをvendor IDproduct IDを把握したら、-dオプションでそのIDを指定し、-vで詳細情報を取得する。 ここではserial numberが求まればよいので、iSerialの値を取得する。

lsusbコマンドで特定デバイスのserial numberを取得する
$ sudo lsusb -d 0403:6001 -v | grep iSerial
  iSerial                 3 XXXXXXXX

デバイスにアクセスする権限がない場合、Couldn't open device, some information will be missingと出力され、iSerialも表示されないので、その場合はrootでlsusbコマンドを実行する。

udevルールファイルの作成

次に、/etc/udev/rules.d/配下にudevルールファイルを作成して以下のような内容を書き込む。 ここではファイル名を/etc/udev/rules.d/90-usb-serial-devices.rulesとした。 デバイスが複数ある場合は、デバイス毎に指定して書き込む。

/etc/udev/rules.d/90-usb-serial-devices.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="XXXXXXXX", SYMLINK+="ttyUSB-FT232R", GROUP="dialout"
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ATTRS{serial}=="XXXXXXXX", SYMLINK+="ttyUSB-CP2102N", GROUP="dialout"

ここでの指定内容と意味は次のとおり。

SUBSYSTEM=="tty"
ルールを適用するデバイスのサブシステム(種類)
ATTRS{idVendor}=="vendor ID"
ルールを適用するデバイスのvendor ID
ATTRS{idProduct}=="product ID"
ルールを適用するデバイスのproduct ID
ATTRS{serial}=="serial number"
ルールを適用するデバイスのserial number
SYMLINK+="ttyUSB-FT232R"
上記3つの条件にANDで合致するデバイスが接続された場合に作成されるシンボリックリンクに、/dev/ttyUSB-FT232Rを加える。
このデバイスファイル名は必ずしもttyUSBで始まる必要はないので、/dev/arduino-nanoにするなど、わかりやすい名前を指定することができる。
ファイル名には接続されたデバイスのシリアル番号やプロダクト名を展開することもできる。 (後述: §.udevルールファイルの記述例)
GROUP="dialout"
デバイスファイルのグループをdialoutに設定する。 この設定は上記のSIMLINK+で作成されるシンボリックリンクのリンク元に適用される。
グループではなくパーミッションによるアクセス許可を設定する場合は、GROUP=の替わりにMODE="0666"のように指定することでパーミッションを指定することもできる。

ユーザーをdialoutグループに追加することでUSBデバイスへのアクセスを許可する方法についてはArduino IDE §.USBデバイスのパーミッション変更を参照。


ルールファイルを作成したら、udevをリロードして書き込んだ内容を反映させる。

$ sudo service udev reload

以降、USBデバイスを接続すると上記ルールファイルで指定されている内容に従って、シンボリックリンクが作成される。

$ ls -l /dev/ttyUSB*
lrwxrwxrwx 1 root root         7 11月 26 00:55 /dev/ttyUSB-FT232R -> ttyUSB1
crw-rw---- 1 root dialout 188, 0 11月 26 00:52 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 11月 26 00:55 /dev/ttyUSB1

PlatformIOの場合、platformio.iniの[env:NAME]セクションのmonitor_portないしはupload_portにこのデバイスファイル名を指定することができる。

platformio.ini
[env:XXX]
monitor_port = /dev/ttyUSB-FT232R
upload_port = /dev/ttyUSB-FT232R

Arduino IDEではこれらのシンボリックリンクは参照されない。 (§.Arduino IDEのシリアルポートメニュー)

udevルールファイルの記述例

udevルールファイルに記述できる内容は比較的自由度が高く、上記の例以外にも目的に応じて様々に設定できる。 以下はその例。

一つのデバイスに対して別名で複数のシンボリックリンクを作成する

SYMLINK+=には複数の名前を指定することもできる。 指定した名前の数だけシンボリックリンクが作成されることになる。 これにより、特定デバイスごとにわかりやすいエイリアスを割り当てることができる。

SYMLINK+=に複数の名前を指定する
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="XXXXXXXX", SYMLINK+="ttyUSB-FT232R arduino-nano", GROUP="dialout"
作成されるシンボリックリンク
lrwxrwxrwx 1 root root         7 11月 26 00:32 arduino-nano -> ttyUSB0
lrwxrwxrwx 1 root root         7 11月 26 00:32 ttyUSB-FT232R -> ttyUSB0
crw-rw---- 1 root dialout 188, 0 11月 26 00:32 ttyUSB0

シリアル番号を含めたファイル名でシンボリックリンクを作成する

$attr{key}を使用するとデバイスの属性(プロパティ)の値が展開される。 例として、$attr{serial}と記述すると接続されたデバイスのシリアル番号に展開される。 これにより、同じ製品を複数を接続した場合でも、シリアル番号が異なればそれぞれ個別のシンボリックリンクを作成することができる。

SYMLINK+=でデバイスのシリアル番号を展開する
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="ttyUSB-FT232R-$attr{serial}", GROUP="dialout"
作成されるシンボリックリンク
lrwxrwxrwx 1 root root         7 11月 26 00:47 /dev/ttyUSB-FT232R-XXXXXXXX -> ttyUSB1
lrwxrwxrwx 1 root root         7 11月 26 00:47 /dev/ttyUSB-FT232R-YYYYYYYY -> ttyUSB0
crw-rw---- 1 root dialout 188, 0 11月 26 00:47 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 11月 26 00:47 /dev/ttyUSB1

上記の例ではATTRS{serial}==記述せずvendor IDproduct IDのみで対象のデバイスを限定している。

USBポートに対して固有のファイル名でシンボリックリンクを作成する

USBデバイスを接続する際、dmesg等でUSBポート(差込口)に対応する固有の番号(usb n-mの部分)が割り当てられていることが確認できる。

dmesg
[148156.360914] usb 1-7: new full-speed USB device number 25 using xhci_hcd
  :
  :
[148809.522544] usb 1-11.3: new full-speed USB device number 28 using xhci_hcd
  :
  :

このようなバス番号デバイスパスは、$attr{busnum}$attr{devpath}を展開することで得られる。 これにより、デバイスが接続されたUSBポートごとに固有のシンボリックリンクを作成することができる。

SYMLINK+=でバス番号とデバイスパスを展開する
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="ttyUSB-usb-$attr{busnum}-$attr{devpath}", GROUP="dialout"
作成されるシンボリックリンク
lrwxrwxrwx 1 root root         7 11月 26 00:27 /dev/ttyUSB-usb-1-11.3 -> ttyUSB1
lrwxrwxrwx 1 root root         7 11月 26 00:27 /dev/ttyUSB-usb-1-7 -> ttyUSB0
crw-rw---- 1 root dialout 188, 0 11月 26 00:27 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1 11月 26 00:27 /dev/ttyUSB1

Arduino IDEのシリアルポートメニュー

Arduino IDEでは、/devディレクトリのシンボリックリンクではなく、/sys/class/tty配下からシリアルポートのパスを参照しているようなので、udevでシンボリックリンクを作成する方法ではArduino IDEのシリアルポート一覧に表示されるデバイスファイル名を変更することができない。 また、udevでは/dev/ttyUSBn自体のファイル名を変えることもできない。

Arduino IDE上のシリアルポート一覧