Raspberry Piとdualshock3接続(再び)

関連する投稿
ラズパイでラジコン(2):PS3コントローラとBluetooth接続

前回はとりあえずdualshock3をRaspberryPiに接続して、使えるようにした備忘録だったが、今回はRaspberry Pi2に最新のRaspbian Stretchをインストールしたので、再度セットアップしてもう少しだけ深堀りする。

そもそもdualshock3は不特定のホストとはペアリングできない。

Playstation本体とペアリングするときも最初は必ずUSBケーブルで接続しないとペアリングできない仕組みになっている。接続したときに何が行われているかは、sixpairのソースを見ると少しわかる。

sixpair.c

このソースはLinux系OSにlibusblibusb-devなどのUSB関連のライブラリとヘッダをインストールすると、ビルド&実行することができるUSBデバイス操作のプログラムになる。

sixpairの処理の流れ

  1. プログラム内部でhcitoolコマンドを実行して、RaspberryPi本体にあるBluetoothデバイスのMACアドレスを取得する。
  2. dualshock3のベンダーID(0x054c)とプロダクトID(0x0268)を基にUSB接続されているdualshock3を特定する。またUSBのインタフェースも特定しておく。
  3. USBのデバイス制御用?のAPIでホスト側のBluetoothのMACアドレスを、dualshock3のインタフェースに対して書き込む

コントローラーのベンダーとプロダクトIDはlsusbコマンドで確認できる。すべてのdualshock3コントローラーは同じである。

> lsusb
Bus 001 Device 005: ID 054c:0268 Sony Corp. Batoh Device / PlayStation 3 Controller
:
(省略)

手動でホスト側のBluetoothドングルのMACアドレスの調べる方法

> hcitool dev
Devices:
  hci0  00:1B:DB:06:BE:13

MACアドレスが上記の場合、以下のように0x01 0x00の後の3バイト目からMACアドレスがセットされたデータが、コントローラーに送信されてMACアドレスが登録される。(devhはコントローラーのデバイスハンドル)

char msg[8]= { 0x01, 0x00, 0x00, 0x1b, 0xdb, 0x06, 0xbe, 0x13 };
int ret = usb_control_msg(devh,
     USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
     0x09,
     0x03f5, itfnum, msg, sizeof(msg),
     5000);

MACアドレスを書き込むことによって、PSボタンを押すとその接続先のホストに対して接続要求?(アドバタイズ?)する仕組みだと思われる。

Raspberry Piとdualshock3のペアリング

まず、ホストであるRaspberryPiのペアリングの準備を行う。
ポイントは以下の3つのコマンドになる。

> sudo bluetoothctl
[bluetooth]# discoverable on
[bluetooth]# agent on

dualshock3のUSBケーブルを抜いて、無線状態にした後、PSボタンを押す。しばらくすると、接続要求?と思われるメッセージが繰り返しコンソールに表示される。(この間はLEDが点滅)

またしばらくすると接続をあきらめたかのようにメッセージが表示されなくなる。(LEDが消灯)PSボタンを押下すると一定時間だけペアリングできるようになると思われる。

[CHG] Device 00:21:4F:9C:35:62 Connected: yes
[CHG] Device 00:21:4F:9C:35:62 Connected: no
[CHG] Device 00:21:4F:9C:35:62 Connected: yes
[CHG] Device 00:21:4F:9C:35:62 Connected: no

LEDが点滅している間に、ホスト側からconnecttrustコマンドを表示されるMACアドレスに対して実行して、接続許可?する。connectだけだと、not availableが表示されたりしていたので、trustコマンドだけでもいいかもしれない。これで以降はPSボタンを押すだけで、自動的にBluetooth接続されるようになる。

[bluetooth]# connect  00:21:4F:9C:35:62
Device  00:21:4F:9C:35:62 not available
[bluetooth]# trust 00:21:4F:9C:35:62
[CHG] Device 00:21:4F:9C:35:62 Trusted: yes
Changing 00:21:4F:9C:35:62 trust succeeded
[bluetooth]# quit

この辺が曖昧だが、少なくともtrustが成功しないと、Raspberry Pi再起動後にPSボタンを押しても接続できないのは間違いない。

USBデバイス情報(デスクリプタ)の一覧表示

dualshock3に限らないが、USBデバイスにはどのように制御できるのかが記載されているデバイスデスクリプタという情報を保持している。USBホストはそのデスクリプタを取得して、内容を参考にして制御する。lsusbコマンドで表示することができるので、dualshock3の内容を残しておく。
(もちろんコントローラをUSB接続しておく必要がある)

USBデバイスの一覧

> lsusb
Bus 001 Device 005: ID 054c:0268 Sony Corp. Batoh Device / PlayStation 3 Controller
Bus 001 Device 004: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
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. SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

dualshock3のUSBデバイスデスクリプタ情報をダンプすると、中段あたりに"Report Descriptor:"という項目があり、サイズが148byteあると記載されている。ここにコントローラーの入出力フォーマットが定義されており、Report Sizeの単位はビット、Report Countが何ビット続いているかという内容になる。そのあとにInputOutputが記載されており、入出力どちらのデータかわかる。

> sudo lsusb -v -d  054c:0268

Bus 001 Device 005: ID 054c:0268 Sony Corp. Batoh Device / PlayStation 3 Controller
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x054c Sony Corp.
  idProduct          0x0268 Batoh Device / PlayStation 3 Controller
  bcdDevice            1.00
  iManufacturer           1 Sony
  iProduct                2 PLAYSTATION(R)3 Controller
  iSerial                 0
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           41
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              500mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.11
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength     148
          Report Descriptor: (length is 148)
            Item(Global): Usage Page, data= [ 0x01 ] 1
                            Generic Desktop Controls
            Item(Local ): Usage, data= [ 0x04 ] 4
                            Joystick
            Item(Main  ): Collection, data= [ 0x01 ] 1
                            Application
            Item(Main  ): Collection, data= [ 0x02 ] 2
                            Logical
            Item(Global): Report ID, data= [ 0x01 ] 1
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x01 ] 1
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Main  ): Input, data= [ 0x03 ] 3
                            Constant Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Size, data= [ 0x01 ] 1
            Item(Global): Report Count, data= [ 0x13 ] 19
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0x01 ] 1
            Item(Global): Physical Minimum, data= [ 0x00 ] 0
            Item(Global): Physical Maximum, data= [ 0x01 ] 1
            Item(Global): Usage Page, data= [ 0x09 ] 9
                            Buttons
            Item(Local ): Usage Minimum, data= [ 0x01 ] 1
                            Button 1 (Primary)
            Item(Local ): Usage Maximum, data= [ 0x13 ] 19
                            (null)
            Item(Main  ): Input, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Size, data= [ 0x01 ] 1
            Item(Global): Report Count, data= [ 0x0d ] 13
            Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
                            (null)
            Item(Main  ): Input, data= [ 0x03 ] 3
                            Constant Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Global): Usage Page, data= [ 0x01 ] 1
                            Generic Desktop Controls
            Item(Local ): Usage, data= [ 0x01 ] 1
                            Pointer
            Item(Main  ): Collection, data= [ 0x00 ] 0
                            Physical
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x04 ] 4
            Item(Global): Physical Minimum, data= [ 0x00 ] 0
            Item(Global): Physical Maximum, data= [ 0xff 0x00 ] 255
            Item(Local ): Usage, data= [ 0x30 ] 48
                            Direction-X
            Item(Local ): Usage, data= [ 0x31 ] 49
                            Direction-Y
            Item(Local ): Usage, data= [ 0x32 ] 50
                            Direction-Z
            Item(Local ): Usage, data= [ 0x35 ] 53
                            Rotate-Z
            Item(Main  ): Input, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Main  ): End Collection, data=none
            Item(Global): Usage Page, data= [ 0x01 ] 1
                            Generic Desktop Controls
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x27 ] 39
            Item(Local ): Usage, data= [ 0x01 ] 1
                            Pointer
            Item(Main  ): Input, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x30 ] 48
            Item(Local ): Usage, data= [ 0x01 ] 1
                            Pointer
            Item(Main  ): Output, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x30 ] 48
            Item(Local ): Usage, data= [ 0x01 ] 1
                            Pointer
            Item(Main  ): Feature, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Main  ): End Collection, data=none
            Item(Main  ): Collection, data= [ 0x02 ] 2
                            Logical
            Item(Global): Report ID, data= [ 0x02 ] 2
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x30 ] 48
            Item(Local ): Usage, data= [ 0x01 ] 1
                            Pointer
            Item(Main  ): Feature, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Main  ): End Collection, data=none
            Item(Main  ): Collection, data= [ 0x02 ] 2
                            Logical
            Item(Global): Report ID, data= [ 0xee ] 238
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x30 ] 48
            Item(Local ): Usage, data= [ 0x01 ] 1
                            Pointer
            Item(Main  ): Feature, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Main  ): End Collection, data=none
            Item(Main  ): Collection, data= [ 0x02 ] 2
                            Logical
            Item(Global): Report ID, data= [ 0xef ] 239
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x30 ] 48
            Item(Local ): Usage, data= [ 0x01 ] 1
                            Pointer
            Item(Main  ): Feature, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Main  ): End Collection, data=none
            Item(Main  ): End Collection, data=none
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
Device Status:     0x0000
  (Bus Powered)

ざっくり書くと、

Report Size
Report Count
Input
Report Size
Report Count
Input
Report Size
Report Count
Input
:

になる。入力(INPUT)データのサイズを計算すると、

8bit*1+1bit*19+1bit*13+8bit*4+8bit*39=384bit=48byte

合計48byteのデータが毎回送信されてくる。

詳しい情報は以下にもあった。

DualShock 3 - Eleccelerator Wiki

このサイトの"Sample HID Report"に16*3+1=49byteのデータがあるが、先頭の1バイトがレポートIDになる。dualshock3の場合は常に1だと思ってよさそう。

dualshock3の入力をどうやって読み取るか?

今回はRaspberry Piとペアリングさせて読み取りたい。手っ取り早かったのは、Node.jsとdualshockというライブラリを使ったものがシンプルですぐに動作した。ソースを見ると、デバイスファイルを参照している?ような記述があった。

そもそもdualshock3がRaspberry PiにBluetooth接続すると何が起こるのか?

dualshock3がbluetoothでRaspberryPiに接続すると、Linuxカーネル上では、HID(Human Interface Device)デバイスとして認識されて、/dev/hidraw0のデバイスファイルが作成される。(ほかにデバイスが接続されていると、末尾の番号が違うかもしれない)

このデバイスファイルは、USB接続、Bluetooth接続どちらの場合でも同じ内容のファイルになるので、このファイルから情報を読み取るのが良い気がする。

hidrawファイルへのアクセス方法を調べると、以下のようなサンプルプログラムが見つかる。

hid-example.c

ビルドして、dualshock3とペアリングした状態で実行してみる。(デバイスファイルのパスは適宜環境に合わせる)

> gcc -o hidraw-sample hidraw-sample.c
> sudo ./hidraw-sample
Report Descriptor Size: 148
Report Descriptor:
5 1 9 4 a1 1 a1 2 85 1 75 8 95 1 15 0 26 ff 0 81 3 75 1 95 13 15 0 25 1 35 0 45 1 5 9 19 1 29 13 81 2 75 1 95 d 6 0 ff 81 3 15 0 26 ff 0 5 1 9 1 a1 0 75 8 95 4 35 0 46 ff 0 9 30 9 31 9 32 9 35 81 2 c0 5 1 95 13 9 1 81 2 95 c 81 1 75 10 95 4 26 ff 3 46 ff 3 9 1 81 2 c0 a1 2 85 2 75 8 95 30 9 1 b1 2 c0 a1 2 85 ee 75 8 95 30 9 1 b1 2 c0 a1 2 85 ef 75 8 95 30 9 1 b1 2 c0 c0

Raw Name: Sony Computer Entertainment Wireless Controller
Raw Phys: 00:1b:dc:06:be:29
Raw Info:
        bustype: 5 (Bluetooth)
        vendor: 0x054c
        product: 0x0268
ioctl HIDIOCSFEATURE returned: 4
HIDIOCGFEATURE: Input/output error
write() wrote 2 bytes
read() read 16 bytes:
        1 0 0 0 0 0 81 7d 7d 7f 0 0 0 0 0 0

サンプルはデバイス情報やレポートデスクリプタのダンプと最後に入出力のサンプルが記載されている。最後に16バイト分データをダンプしているが、まさにコントローラーの入力データだと思われる。

プログラムを修正して、49バイト読み取ってみる。

read() read 49 bytes:
        1 0 0 0 0 0 81 7d 7d 7f 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 5 16 ff d0 0 0 33 b4 77 0 0 f3 1 ec 1 8c 1 e8 1

イケそうである。次回はレポートデスクリプタに沿って解析してみる。

libudev IOCTRL

ioctl.h