#初めに
任天堂スイッチのスプラトゥーン2をマウスで操作したいと思った。
巷では既に任天堂スイッチをマウスで動かせるコンバーターが市販されている。
しかし市販のコンバーターはマウスの動きでゲームコントローラーのスティックの動きを置き換えるものしかなかった(僕が見た範囲では)。
その方式だとうまく操作が出来ないと思った。
そこでマウスの動きでプロコントローラーのジャイロの動きを置き換える装置を作った。
しかし僕の技術力ではうまく操作が出来ない物になった。
マウスの動きでジャイロの動きを置き換える方式はうまく作れば実用に耐え得る物になると思っている。
現時点までにできた物をここで公開しようと思う。
#使い方
非常に参考にさせて頂いた記事
- スマホでNintendo Switchを操作する 〜 USB GadgetでPro Controllerをシミュレート 〜
- 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
と打ってください
#!/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コントローラーが送信し続けるデータ(値)に、マウスが送信したデータ(値)を加算しています。完全にデータを置き換えているわけではありません。
#!/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にて、パイソンコードを公開しており、特にライセンスはありませんので、ご自由に改変してください。
もしご協力して下さると大変嬉しく思います。
読んで頂きありがとうございました。
#参考にさせて頂いた記事リンク