ラズパイでラジコン(3):Pythonでdualshock3の入力を読み取る

前回までの活動によって、RaspberryPi2(Jessie Lite)とdualshock3をbluetooth経由でペアリングできるようになった。今回はPython3で、コントローラーからどんな入力があったのかを解析する方法を検討する。

Raspbian Jessie (Lite) の初期設定

PS3コントローラとBluetooth接続

ポイントとしては、bluetooth経由で接続完了後、dualshoc3の入力情報は、**デバイスファイル(/dev/input/js0)**にどんどん出力されているので、これを読み取れば良い。

以下のURLにあるJoystick APIにあるような出力フォーマットらしい。

Joystick API

struct js_event {
  __u32 time;     /* event timestamp in milliseconds */
  __s16 value;    /* value */
  __u8 type;      /* event type */
  __u8 number;    /* axis/button number */
};

python3のインストール

Raspbianにはデフォルトで、python2系がインストールされているが、python3系がないのでインストールする。

> sudo apt install python3.4 python3-dev python3-pip

python3用のシンボリックリンクを作成しておく

> cd /usr/bin
> sudo ln -s python3.4 python3

pythonにはstructというモジュールがあり、これはバイト列を成形して展開?してくれるらしい。バイト数とそのデータの型を並び順に定義すると、指定したバイト数だけ取り出して展開する便利なAPIになっている。

このAPIを頼りに、うまくいけば上記構造体(8バイト)のようなバイト列を、デバイスファイルから取得してみる。

#!/usr/bin/python3
import struct

device_path = "/dev/input/js0"

# unsigned long, short, unsigned char, unsigned char
EVENT_FORMAT = "LhBB";
EVENT_SIZE = struct.calcsize(EVENT_FORMAT)

with open(device_path, "rb") as device:
  event = device.read(EVENT_SIZE)
  while event:
    (ds3_time, ds3_val, ds3_type, ds3_num) = struct.unpack(EVENT_FORMAT, event)
    print( "{0}, {1}, {2}, {3}".format( ds3_time, ds3_val, ds3_type, ds3_num ) )
    
    event = device.read(EVENT_SIZE)

プログラムを実行して、デバイスファイルの内容が出力されることを確認する。

> sudo python3 js_watch.py

87016180, -26350, 2, 16
87016180, -15878, 2, 17
87016190, 1, 1, 14
87016190, -30742, 2, 16
87016190, -21283, 2, 17
87016190, -30404, 2, 18
87016200, 0, 1, 12
87016200, 0, 1, 13
87016200, -32767, 2, 16
87016200, -32767, 2, 17
87016200, -30742, 2, 18
87016210, -32767, 2, 18
87016220, 1, 1, 4
87016220, 0, 1, 14

1列目の値はタイムスタンプで、ボタンの同時押しなどをすると、同じ時間になる。

2列目の値は、ボタンなどの入力値になる。

3列目は入力タイプで、dualshock3の場合だとボタン操作ds3_type = 1とレバー入力やコントローラの傾きのようなアナログ操作ds3_type=2となる。

4列目はボタンやスティックに割り振られたユニーク番号になる。この番号を見ることにより、どんなボタンが押されたかなどが判定できる。

各入力の番号

各ボタンやスティックには、ds3_type=1の番号が必ず存在し、ds3_type=2の番号は一部のボタンやスティックに存在する。解析結果は以下の図になる。

image

例えば、L2ボタンの場合、ds3_type=1の番号は8であり、ds3_valueの値は1(pressed)と0(released)になってデジタル的な情報がわかる。同時にds3_type=2の番号は12であり、どこまで深く押したのかアナログ的な情報が取得できる。

レバーだけでなく、右側の4つのボタン(〇△□×)でも、アナログレベル(どの程度強く押しているか)の情報が取得できた。
図中の**[23][24][26]**は、コントローラの傾き(ロール、ピッチ、ヨー)の動きを表す。

また[25]は図中にないが、実際にプログラムを実行すると大量に出力される。これが何を表しているのは、よくわからなかった。あとなぜかキーだけアナログ出力がなかった。ひょっとすると使用したコントローラのキーが壊れているのかもしれない。(読み取る機会はなさそうだが)

ボタンのアナログレベル

前述のとおり、ds3_type=2でボタンの入力レベルが取得できる。以下の表にまとめた。

  • コントローラのボタン
    Dualshock3入力 `ds3_type=1` `ds3_type=2`
    `ds3_num` `ds3_value` `ds3_num` `ds3_value`
    リリース プレス リリース プレス 強プレス
    4 0 1 8 -32767 0 32767
    5 0 1 5 -32767 0 32767
    6 0 1 6 -32767 0 32767
    7 0 1 ? ? ? ?
    12 0 1 16 -32767 0 32767
    13 0 1 17 -32767 0 32767
    × 14 0 1 18 -32767 0 32767
    15 0 1 19 -32767 0 32767
    L1 10 0 1 14 -32767 0 32767
    L2 8 0 1 12 -32767 0 32767
    L3 1 0 1
    R1 11 0 1 15 -32767 0 32767
    R2 9 0 1 13 -32767 0 32767
    R3 2 0 1
    SELECT 0 0 1
    START 3 0 1
    PS 16 0 1
    • スティックの入力
      Dualshock3入力 `ds3_type=2`
      `ds3_num` `ds3_value`
      ニュートラル
      Left Stick (vertical) 1 -32767 0 32767
      Right Stick (vertical) 3 -32767 0 32767
      Dualshock3入力 `ds3_type=2`
      `ds3_num` `ds3_value`
      ニュートラル
      Left Stick (horizontal) 0 -32767 0 32767
      Right Stick (horizontal) 2 -32767 0 32767
      • コントローラーのロール・ピッチ
        ジャイロセンサの生の値だからなのか、値の本当のMin, Maxがよくわらず。おおよその実測値を掲載。(実際にはMinとMaxの絶対値は同じになりそう。必要になったらそろえて調整する必要がありそう)
        Dualshock3入力 `ds3_type=2`
        `ds3_num` `ds3_value`
        ニュートラル
        Controller (Roll) 23 4255 0 -5107
        Dualshock3入力 `ds3_type=2`
        `ds3_num` `ds3_value`
        ニュートラル
        Controller (Pitch) 24 4255 0 -5107
        Dualshock3入力 `ds3_type=2`
        `ds3_num` `ds3_value`
        ニュートラル
        Controller (Yow) 26 4255 0 -5107

        バッテリーの残量情報

        /sys/class/power_supply以下にsony_controller_battery_[MACアドレス]というフォルダがペアリングしたときに作成される。このディレクトリにあるueventを参照すると、バッテリーの状態が分かる。

        > cd /sys/class/power_supply/sony_controller_battery_00:21:xx:13:xx:xx
        > cat uevent
        POWER_SUPPLY_NAME=sony_controller_battery_00:21:xx:13:xx:xx
        POWER_SUPPLY_PRESENT=1
        POWER_SUPPLY_CAPACITY=100
        POWER_SUPPLY_SCOPE=Device
        POWER_SUPPLY_STATUS=Full
        

        一晩繋ぎっぱなしの状態で残量チェックすると、ちゃんと減っているのがわかる。POWER_SUPPLY_STATUSFullなのはおそらくUSBケーブルでラズパイに接続し、充電状態で満タンだったからで、ケーブルを外して残量が減ると、Dischargingになっている模様。

        > cat uevent
        POWER_SUPPLY_NAME=sony_controller_battery_00:21:xx:13:xx:xx
        POWER_SUPPLY_PRESENT=1
        POWER_SUPPLY_CAPACITY=75
        POWER_SUPPLY_SCOPE=Device
        POWER_SUPPLY_STATUS==Discharging
        

        これを逐一チェックすればとりあえず分かりそうだが、もっとスマートな方法があるかは不明。

        次回は、上記の結果を踏まえたpythonモジュールの作成か、ラジコン側の電源・モーター制御関連をやるかもしれない。