3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

初めに

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

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

こんなものが出来ています

YouTubeへ飛びます。
ビデオが開けません

使い方

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

用意する物

  • 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コマンドを打ったら
/devhidg0というファイルが出来ますが、ラズパイを再起動するとこのファイルが消えてしまうので先ほどのコマンドはラズパイを起動する度に再度打ってください。

コマンドラインで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コントローラーが送信し続けるデータ(値)に、マウスが送信したデータ(値)を加算しています。完全にデータを置き換えているわけではありません。

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()

変数proconmouseに代入される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にて、パイソンコードを公開しており、特にライセンスはありませんので、ご自由に改変してください。
もしご協力して下さると大変嬉しく思います。

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

参考にさせて頂いた記事リンク

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
3
Help us understand the problem. What are the problem?