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はそれぞれ次のとおり。

FT232R
ID
vendor ID 0403
product ID 6001
manufacturer FTDI
product FT232R USB UART

Arduino Nano EveryではATSAMD11D14Aを使用していて、それぞれ次のようになる。

Arduino Nano Every
ID
vendor ID 2341
product ID 0058
manufacturer Arduino LLC
product Arduino Nano Every

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

CP2102N
ID
vendor ID 10c4
product ID ea60
manufacturer Silicon Labs
product CP2102N USB to UART Bridge Controller

Seeeduino XIAOでは通常時・モニター状態のときはUSB ACM deviceとして動作するが、アップロード時は一時的にUSB Mass Storage deviceとして認識されるように動作が切り替わる。 動作状態によってproduct IDが次のように変わる点に留意する必要がある。

Seeeduino XIAO
ID
モニター時 アップロード時
vendor ID 2886
product ID 802f 002f
manufacturer Seeed Seeed Studio
product Seeeduino XIAO

上記のようにmanufacturerの値も状態によって変わるが、productの値は動作状態によらず変わらないため、IDからデバイスを特定する場合はproduct IDではなくproductを参照するなどする。


上記以外のデバイスのIDを調べる方法、シリアル番号を調べる方法については、次のとおり。

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とした。 対象デバイスやルールを複数記述したい場合は、ルール毎にそれぞれ1行に指定して書き込む。 #で始まる行はコメントとして扱われる。

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

# CP2102N
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"
上記のSUBSYSTEMATTRS{...}の条件すべてにANDで合致するデバイスが接続された際に、作成するシンボリックリンクに/dev/ttyUSB-FT232Rを加える。
このデバイスファイル名は必ずしもttyUSBで始まる必要はないので、/dev/arduino-nanoにするなど、わかりやすい名前を指定することができる。
さらに、接続されたデバイスのシリアル番号やプロダクト名を展開することもできる。 (例: §.シリアル番号を含めたファイル名でシンボリックリンクを作成する)
GROUP="dialout"
デバイスファイルのグループをdialoutに設定する。 この設定は上記のSIMLINK+で作成されるシンボリックリンクのリンク元に適用される。
グループではなくパーミッションによるアクセス許可を設定する場合は、GROUP=の替わりにMODE="0666"のように指定することでパーミッションを指定することもできる。

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

上記の例では、vendor ID, product ID, serial numberの3つの値をもとにデバイスを特定しているが、この他にも次のような値を条件として用いることもできる。

ATTRS{manufacturer}=="manufacturer"
デバイスのmanufacturer (製造元)
ATTRS{product}=="product"
デバイスのproduct (製品名)
ATTRS{busnum}=="busnum"
デバイスのbusnum (デバイスが接続されているバス番号)
ATTRS{devpath}=="devpath"
デバイスのdevpath (接続されているデバイスのデバイスパス)
busnumdevpathを組み合わせることにより、例えば特定のUSBポートに接続されているデバイスを指定する、といったことができる (例: §.USBポートごとに固有のファイル名でシンボリックリンクを作成する§.USBポートに対して個別のエイリアスを与える)

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

udevadmコマンドを使ってudevルールをリロードする
$sudo udevadm control --reload-rules
serviceコマンドを使ってリロードする場合
$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+=に複数の名前を指定する場合は、それぞれを空白 で区切って指定する。

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}と記述すると接続されたデバイスのシリアル番号(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

USBポートに対して個別のエイリアスを与える

USBデバイスの種類ではなく、デバイスが接続されているUSBポートで特定したい場合は、一致条件にATTRS{busnum}ATTRS{devpath}を用いることができる。

これにより、例えばフロントパネルのUSBポート、そこに接続されているUSBハブのポート、さらにその1番目のポート…など、特定のポートに役割や名前を与えたい場合に、そのエイリアスとして個別にシンボリックリンクを作成することができる。

次の例では、バス番号1、デバイスパス11.2のUSBポートに接続されるデバイスに対して、/dev/usb-rear-hub1-port2のシンボリックリンクを作成するように指定している。

ATTRS{busnum}とATTRS{devpath}でUSBポートを特定する
SUBSYSTEM=="tty", ATTRS{busnum}=="1", ATTRS{devpath}=="11.2", SYMLINK+="usb-rear-hub1-port2"

バス番号・デバイスパスを調べるには、§.USBポートごとに固有のファイル名でシンボリックリンクを作成するでの例のようにdmesgを参照するか、udevadmコマンドを用いる。

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

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

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