Raspberry Pi に Xbox のコントローラーを Bluetooth 接続して Python で使う

Xbox のコントローラーを Raspberry Pi と Bluetooth 接続して Python から使う方法をまとめます。Python 実行時は pygame を使用しますが GUI は使用せずに実行可能です。本プログラム内では十字キーと B ボタンにのみ反応するようになっています。

まず Xbox のコントローラーを [ペアリングモード](https://support.xbox.com/ja-JP/help/hardware-network/controller/connect-xbox-wireless-controller-to-pc) にして Raspberry Pi の Bluetooth のアイコンをクリックし Add Device... をクリックします。

![bluetooth1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360970/83495157-15d1-d7d3-aa86-5fb4cc12e91c.png)

Xbox Wireless Controller と表示されるので選択して Piar ボタンを押します。Raspberry Pi と Xbox コントローラーが Bluetooth で無事接続されると Connection successfully と表示されるので OK ボタンを押します。

![bluetooth2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360970/9a0a33bc-f4b2-034c-0876-e8a30f5ac5f0.png)

再度 Bluetooth アイコンをクリックすると Xbox Wireless Controller の左にチェックマークが表示され現在接続中であることが確認できます。

![bluetooth3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/360970/5ff88612-06c4-1f81-3880-84201e62de52.png)

CLI で接続されていることを確認するには bluetoothctl コマンドを使います。Bluetooth 接続自体も CLI で行いたい場合は [ここ](https://qiita.com/noraworld/items/55c0cb1eb52cf8dccc12#:~:text=bluetoothctl%20%E3%81%A8%E3%81%AF,-GNU%2FLinux%20%E3%81%A7&text=Bluetooth%20%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB,%E3%81%8C%E6%9C%80%E5%88%9D%E3%81%8B%E3%82%89%E4%BD%BF%E3%81%88%E3%82%8B%E3%81%AF%E3%81%9A%E3%80%82) や [ここ](https://pimylifeup.com/xbox-controllers-raspberry-pi/) などを参照してください。因みに後者では xboxdrv や joystick のインストールを行っていますが Raspberry Pi OS (bullseye) を使用し且つ Python の pygame から使用する範囲内では特にインストールせずに動作するようです。

```
$ bluetoothctl paired-devices
Device C8:3F:26:60:22:6A Xbox Wireless Controller
```

次に pygame がインストールされているか確認します。以下のコマンドの実行結果から該当の環境では pygame 1.9.6 がインストールされていることが確認できます。インストール方法は [本家](https://www.pygame.org/wiki/GettingStarted) などを参照してください。

```
$ python3 -c "import pygame; print(pygame.version.ver)"
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
1.9.6
```

pygame を使用した Python の例は以下のようになります。

```
import sys, os

os.environ["SDL_VIDEODRIVER"] = "dummy"
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"

import pygame

def joy_event(joy):

    hat = joy.get_hat(0) # Get status of cross key

    if hat[0] == 0 and hat[1] == 1: # Up key pressed
        print("up")
    elif hat[0] == 0 and hat[1] == -1: # Down key pressed
       print("down")
    elif hat[0] == -1 and hat[1] == 0: # Left key pressed
       print("left")
    elif hat[0] == 1 and hat[1] == 0: # Right key pressed
       print("right")


def main():

    pygame.init()

    try:
        joy = pygame.joystick.Joystick(0)
        joy.init()
        print('Joystick Name: ', joy.get_name())
        print('Number of buttons: ', joy.get_numbuttons())

    except pygame.error:
        print('Joystick 0 is not connected.')
        sys.exit(1)

    try:
        while True:
            event = pygame.event.wait() # Wait until getting event from queue

            btn_b = joy.get_button(1) # B button to exit
            if btn_b == 1:
                break

            joy_event(joy)

            #print(event)

    except KeyboardInterrupt:
        print("Program interrupted by user")

    joy.quit()
    pygame.quit()

    print("Exit")

    return

if __name__=="__main__":
    main()
```

プログラムを実行するとまずコントローラーの名前とボタンの数が表示されます。続いて Xbox コントローラーの十字キーを押すとそれに応じた出力がターミナル上に表示されます。B ボタンを押すとプログラムが終了します。

```
$ python3 ./test.py
Joystick Name:  Xbox Wireless Controller
Number of buttons:  15
up
left
right
down
Exit
```

pygame.event.wait() でイベント (ここではコントローラのボタンが押される) を待ちイベントが発生した場合 joy_event() を呼び出しています。joy_event() 内で joy.get_hat(0) を呼び出し十字キーのステータスを取得しています。joy.get_hat() は 2 つの要素を持つタプルを返し例えば上が押された場合は (0, 1)、なにも押されていない場合は (0, 0) を返します。

print(event) をコメントから外すと以下のようにキーに対応した番号が取得できます。例えば A を押した際に以下のような出力となり 0 が対応していることがわかります。

```
<Event(10-JoyButtonDown {'joy': 0, 'button': 0})>
<Event(11-JoyButtonUp {'joy': 0, 'button': 0})>
```

アナログスティックは joy.get_axis() を呼び出すと x 軸方向や y 軸方向などの傾きに対応した値を -1.0 から 1.0 の範囲で得られるようです。詳しくは  [ここ](http://westplain.sakura.ne.jp/translate/pygame/Joystick.cgi) などを参照してください。例はいろいろと web に転がっていますが最近では [ChatGPT 先生](https://openai.com/blog/chatgpt/) に聞くのが良いかもしれません。。

```
<Event(7-JoyAxisMotion {'joy': 0, 'axis': 0, 'value': 0.9060335093234047})>
```

一点要調査なのは Ctrl-C で以下の個所が思った通りに動かないことです。。pygame.event.wait() 内で待ってるからだと思いますが ChatGPT 先生に聞いても今のところ答えは分かりませんでした。。なのでプログラムは B ボタンを押して終了する必要があります。

```
    except KeyboardInterrupt:
        print("Program interrupted by user")
```

アナログスティックの傾きの値を得る場合はこんな感じ。

```
def joy_event(joy):

    du = joy.get_axis(0) # Joy stick Down(+1.0)/Up(-1.0)
    rl = joy.get_axis(1) # Joy stick Right(+1.0)/Left(-1.0)

    print("D(+1.0)/U(-1.0): " + str(du) + ", R(+1.0)/L(-1.0): " + str(rl))
```