マウスを任天堂スイッチのプロコンのジャイロに連動させる

#初めに
任天堂スイッチのスプラトゥーン2をマウスで操作したいと思った。
巷では既に任天堂スイッチをマウスで動かせるコンバーターが市販されている。
しかし市販のコンバーターはマウスの動きでゲームコントローラーのスティックの動きを置き換えるものしかなかった（僕が見た範囲では）。
その方式だとうまく操作が出来ないと思った。
そこでマウスの動きでプロコントローラーのジャイロの動きを置き換える装置を作った。

しかし僕の技術力ではうまく操作が出来ない物になった。
マウスの動きでジャイロの動きを置き換える方式はうまく作れば実用に耐え得る物になると思っている。
現時点までにできた物をここで公開しようと思う。

#こんなものが出来ています
YouTubeへ飛びます。
[![ビデオが開けません](https://img.youtube.com/vi/Y83Bb76M7FA/0.jpg)](https://youtu.be/Y83Bb76M7FA)

#使い方
非常に参考にさせて頂いた記事

- [スマホでNintendo Switchを操作する 〜 USB GadgetでPro Controllerをシミュレート 〜](https://mzyy94.com/blog/2020/03/20/nintendo-switch-pro-controller-usb-gadget/)
- https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering

####用意する物

- Nintendo Switch
- Nintendo Switch Proコントローラー 
- Raspberry Pi 4 Model B
- マウス
- キーボード
- ディスプレイ
- ケーブル類
- その他諸々

僕の使っているマウスは
https://www.elecom.co.jp/products/M-Y8UBXBK.html
多分これだと思います。マウスの送信するデータ形式が僕のマウスと同じなら、後に載せるパイソンコードの書き換えの手間を減らせると思います。

####手順
ラズパイをセットアップしておいてください（今回はOSは普通にRaspbianを選択しました）。
任天堂スイッチのコントローラーの設定で、有線接続をオンにします。
スプラトゥーン2の設定でジャイロをオンにします。
マウスのVendorID(VID)を調べておいてください。僕のELECOMのマウスのVIDは`04F3`でした
ラズパイのUSB Type-Aポートにマウス、キーボード、Proコントローラーを接続します。
ラズパイのHDMIのポート0をディスプレイに接続します。
接続したディスプレイは、ラズパイの画面を表示するためのものです。ゲーム画面を表示するディスプレイは任天堂スイッチのドックのHDMIポートに接続します。
任天堂スイッチが起動していることを確認してから、
Type-Cポートに任天堂スイッチを接続します（僕は任天堂スイッチドックのUSBポートに接続しました）。
するとラズパイが起動します。
以下ラズパイ上での操作です。
Raspbianでは、dwc2モジュールをロードしておくため、/boot/config.txtにdtoverlay=dwc2を、/etc/modulesにdwc2とlibcompositeを追記する。
ここで一旦ラズパイを再起動してください※2021/07/20追記

コマンドラインで`su`でスーパーユーザになれるようにし、なってください
以下に示すadd_procon_gadget.shというファイルを作り
コマンドラインで`source add_procon_gadget.sh`と打ってください

```add_procon_gadget.sh
#!/bin/bash

cd /sys/kernel/config/usb_gadget/
mkdir -p procon
cd procon
echo 0x057e > idVendor
echo 0x2009 > idProduct
echo 0x0200 > bcdDevice
echo 0x0200 > bcdUSB
echo 0x00 > bDeviceClass
echo 0x00 > bDeviceSubClass
echo 0x00 > bDeviceProtocol

mkdir -p strings/0x409
echo "000000000001" > strings/0x409/serialnumber
echo "Nintendo Co., Ltd." > strings/0x409/manufacturer
echo "Pro Controller" > strings/0x409/product

mkdir -p configs/c.1/strings/0x409
echo "Nintendo Switch Pro Controller" > configs/c.1/strings/0x409/configuration
echo 500 > configs/c.1/MaxPower
echo 0xa0 > configs/c.1/bmAttributes

mkdir -p functions/hid.usb0
echo 0 > functions/hid.usb0/protocol
echo 0 > functions/hid.usb0/subclass
echo 64 > functions/hid.usb0/report_length
echo 050115000904A1018530050105091901290A150025017501950A5500650081020509190B290E150025017501950481027501950281030B01000100A1000B300001000B310001000B320001000B35000100150027FFFF0000751095048102C00B39000100150025073500463B0165147504950181020509190F2912150025017501950481027508953481030600FF852109017508953F8103858109027508953F8103850109037508953F9183851009047508953F9183858009057508953F9183858209067508953F9183C0 | xxd -r -ps > functions/hid.usb0/report_desc

ln -s functions/hid.usb0 configs/c.1/

ls /sys/class/udc > UDC

```

先ほどの`source add_procon_gadget.sh`コマンドを打ったら
`/dev`に`hidg0`というファイルが出来ますが、ラズパイを再起動するとこのファイルが消えてしまうので先ほどのコマンドはラズパイを起動する度に再度打ってください。


コマンドラインで`exit`と打って普通のユーザに戻ってください。
コマンドラインで`sudo dmesg | grep -A7 057e`と打ってください。

```
pi@raspberrypi:~ $ sudo dmesg | grep -A7 057e
[ 7201.091044] usb 1-1.3: New USB device found, idVendor=057e, idProduct=2009, bcdDevice= 2.00
[ 7201.091060] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 7201.091074] usb 1-1.3: Product: Pro Controller
[ 7201.091087] usb 1-1.3: Manufacturer: Nintendo Co., Ltd.
[ 7201.091099] usb 1-1.3: SerialNumber: 000000000001
[ 7201.112454] input: Nintendo Co., Ltd. Pro Controller as /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.3/1-1.3:1.0/0003:057E:2009.0002/input/input1
[ 7201.114447] hid-generic 0003:057E:2009.0002: input,hidraw0: USB HID v1.11 Joystick [Nintendo Co., Ltd. Pro Controller] on usb-0000:01:00.0-1.3/input0
```
hidraw0と書かれているところがありますね。これはProコントローラーを表すファイルみたいなもので、hidrawの次の数字はお使いの環境それぞれで異なることもありますので、hidraw何番かを確認してください。

次にマウスがhidraw何番かを調べるので、コマンドラインで
`sudo dmesg | grep -A7 マウスのVID`の「マウスのVID」の部分をマウスのVIDに置き換えて打ってください。僕のELECOMのマウスのVIDは`04F3`でした。
　※追記：マウスのVIDを調べるには`lsusb`コマンドをコマンドラインに打つと調べることができたと思います。うろ覚えですが。
マウスがhidraw何番かを確認してください。

任天堂スイッチとProコントローラーでやり取りする通信の間にマウスの操作データを割り込ませるプログラムを作ります。以下に示すパイソンコードのファイルを作成してください。（非常に汚いコードで、必要の無い行などがあります。すみません）。
このプログラムがやっている事は正確に言うと、Proコントローラーが送信し続けるデータ（値）に、マウスが送信したデータ（値）を加算しています。完全にデータを置き換えているわけではありません。

```python:mouse_gyro.py
#!/usr/bin/env python3

import os
import threading
import time
import random

# Re-connect USB Gadget device
os.system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
os.system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')

time.sleep(0.5)

gadget = os.open('/dev/hidg0', os.O_RDWR | os.O_NONBLOCK)
procon = os.open('/dev/hidraw3', os.O_RDWR | os.O_NONBLOCK)
mouse  = os.open('/dev/hidraw2', os.O_RDWR | os.O_NONBLOCK)
mouse_int = bytes([0,0,0,0])
def mouse_input():
    global mouse_int
    while True:
        try:
            mouse_int = os.read(mouse, 128)
            #print('<<<', output_data.hex())
            #print(output_mouse.hex())
            #os.write(gadget, output_mouse)
        except BlockingIOError:
            pass
        except:
            os._exit(1)



def procon_input():
    while True:
        try:
            input_data = os.read(gadget, 128)
            #print('>>>', input_data.hex())
            os.write(procon, input_data)
        except BlockingIOError:
            pass
        except:
            os._exit(1)

def convert(ou_dt_i, mo_in_i, weight, reflect):
    mo_in_i = int.from_bytes(mo_in_i, byteorder='little', signed=True)
    ou_dt_i = int.from_bytes(ou_dt_i, byteorder='little', signed=True)
    if reflect == True:
        mo_in_i = mo_in_i * -1
        ou_dt_i = ou_dt_i * -1
    merged_gy = ou_dt_i + mo_in_i * weight
    if merged_gy > 32767:
        merged_gy = 32767
    elif merged_gy < -32768:
        merged_gy = -32768
    else:
        pass
    merged_gy = merged_gy.to_bytes(2, byteorder='little', signed=True)

    return merged_gy

def replace_mouse(output_data, mouse_int):
    #a = output_data[0:13]

    #mouse no click wo migi no button ni henkan
    ri_btn = 0
    if mouse_int[0] == 1:#hidari click
        ri_btn = 0x80#ZR button
    elif mouse_int[0] == 2:#migi click
        ri_btn = 0x40#R button
    elif mouse_int[0] == 4:#chuu click
        ri_btn = 0x08#A button
    ri_btn = (output_data[3] + ri_btn).to_bytes(1, byteorder='little')
    a = output_data[0:3] + ri_btn + output_data[4:13]

    #kasokudo sensor ni tekitou ni atai wo ire naito setsuzoku ga kireru
    if mouse_int[1] != 0:
        b = 127
    else:
        b=0
    if mouse_int[2] != 0:
        b = 127
    else:
        b = 0
    d = bytes([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])
    ac0 = bytes([255]) if output_data[14] + b > 255 else bytes([output_data[14] + b])
    ac1 = bytes([255]) if output_data[16] + b > 255 else bytes([output_data[16] + b])
    ac2 = bytes([255]) if output_data[18] + b > 255 else bytes([output_data[18] + b])
    ac0_1 = bytes([255]) if output_data[26] + b > 255 else bytes([output_data[26] + b])
    ac1_1 = bytes([255]) if output_data[28] + b > 255 else bytes([output_data[28] + b])
    ac2_1 = bytes([255]) if output_data[30] + b > 255 else bytes([output_data[30] + b])
    ac0_2 = bytes([255]) if output_data[38] + b > 255 else bytes([output_data[38] + b])
    ac1_2 = bytes([255]) if output_data[40] + b > 255 else bytes([output_data[40] + b])
    ac2_2 = bytes([255]) if output_data[42] + b > 255 else bytes([output_data[42] + b])

    #mouse no ugoki wo gyro no ugoki ni henkan
    gy0_0 = convert(output_data[19:21], mouse_int[1:2], 250, False)#
    gy1_0 = convert(output_data[21:23], mouse_int[2:3], 250, False)#
    gy2_0 = convert(output_data[23:25], mouse_int[2:3], 0, False)#

    gy0_1 = convert(output_data[31:33], mouse_int[1:2], 250, False)#
    gy1_1 = convert(output_data[33:35], mouse_int[2:3], 250, False)#
    gy2_1 = convert(output_data[35:37], mouse_int[2:3], 0, False)#

    gy0_2 = convert(output_data[43:45], mouse_int[1:2], 250, False)#
    gy1_2 = convert(output_data[45:47], mouse_int[2:3], 250, False)#
    gy2_2 = convert(output_data[47:49], mouse_int[2:3], 0, False)#

    e = a+output_data[13:14]+ac0+output_data[15:16]+ac1+output_data[17:18]+ac2 \
        +gy0_0+gy1_0+gy2_0 \
        +output_data[25:26]+ac0_1+output_data[27:28]+ac1_1+output_data[29:30]+ac2_1 \
        +gy0_1+gy1_1+gy2_1 \
        +output_data[37:38]+ac0_2+output_data[39:40]+ac1_2+output_data[41:42]+ac2_2 \
        +gy0_2+gy1_2+gy2_2 \
        +d

    print(int.from_bytes(gy1_0, byteorder='little'))
    #print(mouse_int[1])
    return e

def procon_output():
    global mouse_int
    while True:
        try:
            output_data = os.read(procon, 128)
            #output_mouse = os.read(mouse, 128)
            #print('<<<', output_data.hex())
            #print(output_data)
            e = replace_mouse(output_data, mouse_int)
            #print(e.hex())
            os.write(gadget, e)#output_data
            mouse_int = bytes([0,0,0,0])
        except BlockingIOError:
            pass
        except Exception as g:
            print(type(g))
            print(g)
            os._exit(1)

threading.Thread(target=procon_input).start()
threading.Thread(target=procon_output).start()
threading.Thread(target=mouse_input).start()
```

変数`procon`、 `mouse`に代入されるhidrawの次の数字は先ほど確認した数字に書き換えてください。
次にマウスが送る信号にパイソンコードを合わせるためにパイソンコードを書き換えるのですが
僕の使っているマウスは
https://www.elecom.co.jp/products/M-Y8UBXBK.html
多分これだと思うのですがこれを使えばコードを書き換える必要がないかもしれません。
お使いのマウスが送信するデータを調べるには`mouse_input()`関数内の`#print(output_mouse.hex())`をアンコメントして調べてみてください。その代わりに他の箇所の`print()`部分をコメントアウトした方が見やすいです。
このパイソンコードを実行するには`sudo python3 mouse_gyro.py`とコマンドラインで打ってください。

上手くいけば、上の方に載せた動画のようにマウスでジャイロをぎこちなく動かせることができると思います。

無限に実行し続けるプログラムなので、終了するにはコマンドライン上で`Ctrl + Z`を押してください。

途中でProコントローラーの接続が切れてしまうことがよくあります。スイッチの画面の指示に従ってみたり、コントローラーの側面の丸い小さなボタンを長押しして、コントローラーを登録しなおしてみてください。
そのさいhidrawの次の数字が変わってしまうことがあります。その時は、もう一度上に書いたhidrawの番号を調べるコマンドを打って調べて、パイソンコードの`procon`変数の代入部分を書き換えてください。

#終わりに
上記の動画を見た方や、実際に動作をお試しになった方ならお分かりになると思いますが、
マウスを動かしても、まともにジャイロが動いてくれません。
なんとか皆様のお力添えを頂きたく思います。
GitHubにて、パイソンコードを公開しており、特にライセンスはありませんので、ご自由に改変してください。
もしご協力して下さると大変嬉しく思います。

読んで頂きありがとうございました。

https://github.com/Bokuchin/SwitchProconGyroMouse

#参考にさせて頂いた記事リンク
- [スマホでNintendo Switchを操作する 〜 USB GadgetでPro Controllerをシミュレート 〜](https://mzyy94.com/blog/2020/03/20/nintendo-switch-pro-controller-usb-gadget/)
- https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering