RaspberryPiで同種複数のUSB機器を識別する事を考えてみる

# はじめに
この記事は、CPS Lab Advent Calendar 2018の13日目の記事です。
12日目の記事は[【Web Speech API】Speech Recognition ホームページでブラウザの音声認識を使う(無料)](https://qiita.com/hmmrjn/items/4b77a86030ed0071f548)
14日目の記事は[Labmartの顔認証を実装してみる](https://aj1m0n.hatenablog.com/entry/2018/12/14/183422)


# 背景
<img width="500" alt="pi1.png" src="https://qiita-image-store.s3.amazonaws.com/0/107175/4522ff8d-8345-352f-37fb-19744d4ca7be.png">
画像のように同種のドライバーのUSB機器を接続したとき、それぞれ挿した順番に`/dev`に認識されてしまいます。
例えば画像のようにArduinoを２つ接続すると両方ともACMのドライバーなので以下のようになります。

```shell
pi@raspberrypi:~ $ ls /dev | grep ttyACM*
ttyACM0
ttyACM1
```

これだとどっちがArduinoMEGAでどっちがArduinoUNOなのかわかりません。
USBカメラを複数個接続したときも`/dev/video*`で認識されるのでどれがどのカメラかわからなくなります。

そこで今USBポートに接続されている機器を識別する方法をまとめてみました。

# 環境
RaspberryPi2(RASBIAN JESSIE)


# どう認識されてるのかみてみる
まずUSBに接続されている機器を確認するコマンドとして`lsusb`コマンドがあります。
先程の画像の状態でコマンドを叩くと

```
pi@raspberrypi:~ $ lsusb
Bus 001 Device 006: ID 2019:ab2a PLANEX GW-USNano2 802.11n Wireless Adapter [Realtek RTL8188CUS]
Bus 001 Device 005: ID 2341:0043 Arduino SA Uno R3 (CDC ACM)
Bus 001 Device 004: ID 2341:0042 Arduino SA Mega 2560 R3 (CDC ACM)
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
```

こんな感じで`UNO R3`と`MEGA 2560 R3`が別のものとしてちゃんと認識されていることがわかります。
よく見ると`UNO R3`が`ID 2341:0043` 、`MEGA 2560 R3、`が`ID 2341:0042`で認識されているのでここで条件分けしてあげれば識別できそうです。
`2341`のほうが`idVendor`その右が`idProduct`です。

# 識別するその１
USB機器が接続されたときにどのようにマウントするかを決めたルールが`/etc/udev/rules.d/99-com.rules`に記述されています。
今回はここの末尾に`UNO R3`と`MEGA 2560 R3`を識別するルールを`idVendor`と`idProduct`をもとに追加します。

```sh:/etc/udev/rules.d/99-com.rules
# for UNO
KERNEL=="ttyACM*",  ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0043", SYMLINK+="ttyUSB_UNO"
# for MEGA
KERNEL=="ttyACM*",  ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0042", SYMLINK+="ttyUSB_MEGA"
```
このような記述を末尾に追加してリブートすると以下のように別の名前で識別されて、それぞれが対応した`ttyACM*`にリンクされている事がわかります。

```sh
pi@raspberrypi:~ $ ls /dev |grep USB
ttyUSB_MEGA
ttyUSB_UNO
pi@raspberrypi:~ $ ls -l /dev |grep USB
lrwxrwxrwx 1 root root           7 Dec 13 19:12 ttyUSB_MEGA -> ttyACM0
lrwxrwxrwx 1 root root           7 Dec 13 19:12 ttyUSB_UNO -> ttyACM1
```

# 識別するその２
## lsusbで観察
<img width="500" alt="pi2.png" src="https://qiita-image-store.s3.amazonaws.com/0/107175/15920391-692a-81dd-2c31-e418bc357a8e.png">
写真のようにesp8266とesp32の開発ボードを接続した場合これらは同じIDのFTDIチップとして認識されてしまい`lsusb`では区別できません。

```sh
pi@raspberrypi:~ $ ls /dev | grep ttyUSB*
ttyUSB0
ttyUSB1
pi@raspberrypi:~ $ lsusb
Bus 001 Device 006: ID 2019:ab2a PLANEX GW-USNano2 802.11n Wireless Adapter [Realtek RTL8188CUS]
Bus 001 Device 008: ID 0403:6015 Future Technology Devices International, Ltd Bridge(I2C/SPI/UART/FIFO)
Bus 001 Device 007: ID 0403:6015 Future Technology Devices International, Ltd Bridge(I2C/SPI/UART/FIFO)
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
```

`lsusb -v`でオプションを付けて観察してみます

```
Bus 001 Device 008: ID 0403:6015 Future Technology Devices International, Ltd Bridge(I2C/SPI/UART/FIFO)
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0         8
  idVendor           0x0403 Future Technology Devices International, Ltd
  idProduct          0x6015 Bridge(I2C/SPI/UART/FIFO)
  bcdDevice           10.00
  iManufacturer           1
  iProduct                2
  iSerial                 3
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           32
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower               90mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass       255 Vendor Specific Class
      bInterfaceSubClass    255 Vendor Specific Subclass
      bInterfaceProtocol    255 Vendor Specific Protocol
      iInterface              2
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0

Bus 001 Device 007: ID 0403:6015 Future Technology Devices International, Ltd Bridge(I2C/SPI/UART/FIFO)
Couldn't open device, some information will be missing
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0         8
  idVendor           0x0403 Future Technology Devices International, Ltd
  idProduct          0x6015 Bridge(I2C/SPI/UART/FIFO)
  bcdDevice           10.00
  iManufacturer           1
  iProduct                2
  iSerial                 3
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           32
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower               90mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass       255 Vendor Specific Class
      bInterfaceSubClass    255 Vendor Specific Subclass
      bInterfaceProtocol    255 Vendor Specific Protocol
      iInterface              2
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
```

この情報の範囲だとユニークな属性は見当たらなさそうです。
そこで`udevadm`コマンドを使います。

## udevadmで観察
`udevadm`では以下のように観察したいデバイスのパスを渡してあげます

```
pi@raspberrypi:~ $ udevadm info -a -p $(udevadm info -q path -n /dev/ttyUSB0)

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/platform/soc/3f980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/ttyUSB0/tty/ttyUSB0':
    KERNEL=="ttyUSB0"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/ttyUSB0':
    KERNELS=="ttyUSB0"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="ftdi_sio"
    ATTRS{port_number}=="0"
    ATTRS{latency_timer}=="1"

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2/1-1.2:1.0':
    KERNELS=="1-1.2:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="ftdi_sio"
    ATTRS{bInterfaceClass}=="ff"
    ATTRS{bInterfaceSubClass}=="ff"
    ATTRS{bInterfaceProtocol}=="ff"
    ATTRS{bNumEndpoints}=="02"
    ATTRS{authorized}=="1"
    ATTRS{supports_autosuspend}=="1"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{interface}=="FT231X USB UART"

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2':
    KERNELS=="1-1.2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{devpath}=="1.2"
    ATTRS{idVendor}=="0403"
    ATTRS{speed}=="12"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="7"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="90mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="a0"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="0"
    ATTRS{bcdDevice}=="1000"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{serial}=="DN02BCJT"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="16"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="FTDI"
    ATTRS{removable}=="removable"
    ATTRS{idProduct}=="6015"
    ATTRS{bDeviceClass}=="00"
    ATTRS{product}=="FT231X USB UART"

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1':
    KERNELS=="1-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="02"
    ATTRS{devpath}=="1"
    ATTRS{idVendor}=="0424"
    ATTRS{speed}=="480"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="2"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="2mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="5"
    ATTRS{bcdDevice}=="0200"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="101"
    ATTRS{ltm_capable}=="no"
    ATTRS{removable}=="unknown"
    ATTRS{idProduct}=="9514"
    ATTRS{bDeviceClass}=="09"

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1':
    KERNELS=="usb1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="01"
    ATTRS{devpath}=="0"
    ATTRS{idVendor}=="1d6b"
    ATTRS{speed}=="480"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{authorized_default}=="1"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="1"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="0mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="1"
    ATTRS{interface_authorized_default}=="1"
    ATTRS{bcdDevice}=="0404"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{serial}=="3f980000.usb"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="25"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="Linux 4.4.38-v7+ dwc_otg_hcd"
    ATTRS{removable}=="unknown"
    ATTRS{idProduct}=="0002"
    ATTRS{bDeviceClass}=="09"
    ATTRS{product}=="DWC OTG Controller"

  looking at parent device '/devices/platform/soc/3f980000.usb':
    KERNELS=="3f980000.usb"
    SUBSYSTEMS=="platform"
    DRIVERS=="dwc_otg"
    ATTRS{hnp}=="HstNegScs = 0x0"
    ATTRS{srp}=="SesReqScs = 0x1"
    ATTRS{regvalue}=="invalid offset"
    ATTRS{hsic_connect}=="HSIC Connect = 0x1"
    ATTRS{guid}=="GUID = 0x2708a000"
    ATTRS{mode}=="Mode = 0x1"
    ATTRS{srpcapable}=="SRPCapable = 0x1"
    ATTRS{regdump}=="Register Dump"
    ATTRS{gpvndctl}=="GPVNDCTL = 0x00000000"
    ATTRS{ggpio}=="GGPIO = 0x00000000"
    ATTRS{hprt0}=="HPRT0 = 0x00001005"
    ATTRS{wr_reg_test}=="Time to write GNPTXFSIZ reg 10000000 times: 520 msecs (52 jiffies)"
    ATTRS{driver_override}=="(null)"
    ATTRS{hcd_frrem}=="HCD Dump Frame Remaining"
    ATTRS{mode_ch_tim_en}=="Mode Change Ready Timer Enable = 0x0"
    ATTRS{gnptxfsiz}=="GNPTXFSIZ = 0x01000306"
    ATTRS{remote_wakeup}=="Remote Wakeup Sig = 0 Enabled = 0 LPM Remote Wakeup = 0"
    ATTRS{busconnected}=="Bus Connected = 0x1"
    ATTRS{hcddump}=="HCD Dump"
    ATTRS{gotgctl}=="GOTGCTL = 0x001c0001"
    ATTRS{spramdump}=="SPRAM Dump"
    ATTRS{grxfsiz}=="GRXFSIZ = 0x00000306"
    ATTRS{gsnpsid}=="GSNPSID = 0x4f54280a"
    ATTRS{gusbcfg}=="GUSBCFG = 0x20001700"
    ATTRS{hptxfsiz}=="HPTXFSIZ = 0x02000406"
    ATTRS{devspeed}=="Device Speed = 0x0"
    ATTRS{fr_interval}=="Frame Interval = 0x1d4c"
    ATTRS{rem_wakeup_pwrdn}==""
    ATTRS{bussuspend}=="Bus Suspend = 0x0"
    ATTRS{buspower}=="Bus Power = 0x1"
    ATTRS{hnpcapable}=="HNPCapable = 0x1"
    ATTRS{rd_reg_test}=="Time to read GNPTXFSIZ reg 10000000 times: 1410 msecs (141 jiffies)"
    ATTRS{enumspeed}=="Device Enumeration Speed = 0x1"
    ATTRS{inv_sel_hsic}=="Invert Select HSIC = 0x0"
    ATTRS{regoffset}=="0xffffffff"

  looking at parent device '/devices/platform/soc':
    KERNELS=="soc"
    SUBSYSTEMS=="platform"
    DRIVERS==""
    ATTRS{driver_override}=="(null)"

  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""
```

２つ並べると長いので割愛しますがこの中にあるユニークな属性を使って識別ルールを追加してあげます。
今回はesp8266側`ATTRS{serial}=="DN02BCJT"`とesp32側`ATTRS{serial}=="DN02TAFR"`を使います。

```sh:/etc/udev/rules.d/99-com.rules
# for esp8266
KERNEL=="ttyUSB*", ATTRS{serial}=="DN02BCJT", SYMLINK+="ttyUSB_esp8266"
# for esp8266
KERNEL=="ttyUSB*", ATTRS{serial}=="DN02TAFR", SYMLINK+="ttyUSB_32"
```

```
pi@raspberrypi:~ $ ls -l /dev |grep USB
crw-rw---- 1 root dialout 188,   0 Dec 13 19:50 ttyUSB0
crw-rw---- 1 root dialout 188,   1 Dec 13 19:50 ttyUSB1
lrwxrwxrwx 1 root root           7 Dec 13 19:50 ttyUSB_32 -> ttyUSB1
lrwxrwxrwx 1 root root           7 Dec 13 19:50 ttyUSB_esp8266 -> ttyUSB0
```
このようにして識別が可能です。

# 識別するその３
## USBを挿した位置による識別

`udevadm`した結果をみてみると`KERNELS=="1-1.2"`のような箇所が見つかると思います。実はここの値で4つあるUSBポートそれぞれで物理的にどこにささっているものか識別することができます。RaspberryPi2の場合、対応は画像のようになっているようです。
![pi3.png](https://qiita-image-store.s3.amazonaws.com/0/107175/798ea98e-db6e-dfea-f298-2a4932439628.png)

# 参考
こちらを参考にさせてもらいました。ありがとうございます。
https://www.irori.org/doc/usb-console.html
https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters