関連する投稿
ラズパイでラジコン(2):PS3コントローラとBluetooth接続
前回はとりあえずdualshock3をRaspberryPiに接続して、使えるようにした備忘録だったが、今回はRaspberry Pi2に最新のRaspbian Stretchをインストールしたので、再度セットアップしてもう少しだけ深堀りする。
そもそもdualshock3は不特定のホストとはペアリングできない。
Playstation本体とペアリングするときも最初は必ずUSBケーブルで接続しないとペアリングできない仕組みになっている。接続したときに何が行われているかは、sixpairのソースを見ると少しわかる。
このソースはLinux系OSにlibusb
、libusb-dev
などのUSB関連のライブラリとヘッダをインストールすると、ビルド&実行することができるUSBデバイス操作のプログラムになる。
sixpairの処理の流れ
- プログラム内部で
hcitool
コマンドを実行して、RaspberryPi本体にあるBluetoothデバイスのMACアドレスを取得する。 - dualshock3のベンダーID(0x054c)とプロダクトID(0x0268)を基にUSB接続されているdualshock3を特定する。またUSBのインタフェースも特定しておく。
- 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が点滅している間に、ホスト側からconnect
とtrust
コマンドを表示される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
が何ビット続いているかという内容になる。そのあとにInput
やOutput
が記載されており、入出力どちらのデータかわかる。
> 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ファイルへのアクセス方法を調べると、以下のようなサンプルプログラムが見つかる。
ビルドして、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