eucalyのアイコン画像
eucaly 2020年12月31日作成 (2020年12月31日更新)
製作品 製作品 閲覧数 3234
eucaly 2020年12月31日作成 (2020年12月31日更新) 製作品 製作品 閲覧数 3234

Raspberry Pi Zeroで、Wiiリモコン→USBゲームパッドブリッジを作ってみる

Raspberry Pi Zeroで、Wiiリモコン→USBゲームパッドブリッジを作ってみる

目的

  • ドラクエ10のPC版で、古のWiiリモコン+ヌンチャク操作を実現してみたい
  • PC側にドライバ入れたりとか、ゲーム改造したりとかせずに!
  • あら、ちょうどいいとこに、ラズパイさんが!
  • これ、使ってみるべ

やりざま

* Raspberry piをUSBデバイスとしてPCに認識させ
 USBデバイス動作ができるRaspberry piが必要なので、無印ではなくZeroシリーズが必要!

*Blutooth経由でWiiリモコンに繋ぎ
 Zeroシリーズは、USBが1ポートしかない為、無印ではなくWが必要!

*データを一部整形して、中継する感じで
 ヌンチャク振ってジャンプ!とか、加速度センサ→ボタンへの読み替えをラズパイ側でしたいなあと

よういするもの

  • Raspberry Pi Zero W(H) (ヘッダ使わないのでWでいいんだけど、WHしか売ってないよね最近は )
  • Wiiリモコン ( Foxxconn FCCID:UMB-WCF7版 / WiiリモコンPlus FCCID:POO-WC62でも動いたよ! )
  • ヌンチャク
  • SDカードとかUSB MicroBケーブルとかHDMIアダプタとか

初期セットアップ

基本的に以下全てroot作業です
今回はUSBをデバイス側として動作させるため、USBシリアルやEthernetを使わずにセットアップしたほうが、ハマらないと思う
つーわけで、普通にRaspbianのLite版をダウンロードし、そのままRufusでSDカードに書き込み
・OSバージョン
 2018-11-13-raspbian-stretch-lite.img

sshの御呪いとか何もせず、HDMIコンソールとキーボード繋いで起動、raspi-conrigで以下設定な感じ
・Wifi
 ネットワークはWifi経由で、カントリーをJPに、SSIDとパスワード入れて接続
・Localization Options
 HDMIコンソール使っているので、フォントがめんどくさい予感、なのでUSのままで
 Timezoneは一応Asia/Tokyoに
・Interface
 SSHをEnableに

raspi-configを抜け、ip addrでIPアドレスを確認、wlan0にIPアドレスが振られていたらOK!
一旦リブート後、PCからSSH経由で接続し、アップデート一式を実行
以下URL参照な感じで
https://qiita.com/tanuwo/items/04df160281153c0e24bc

デバイス下層周りを使うため、今回は念のためファームウェアも最新にしておきました

USBデバイス化と、ゲームパット生成

使用したRaspbianはLinux Kernel 4.4なので、compositeなUSBデバイスがモダンでいい感じかも知れないなと

USBゲームパット生成自体は、以下URLを
https://qiita.com/toyoshim/items/59c1af01919cb3494512
compositeなUSBデバイス周りは、以下URLを参照しました
https://qiita.com/exthnet/items/98aa9b6d6a606f8f2cf8

サブクラス周りで悩んだけれども、ゲームパッドはprotocol0 subclass0でいいみたい
このへん参照しました
https://sites.google.com/site/toriaezunomemo/home/communication-device-class/class-specific-codes

dwc2モジュール読み込み時に、ついでにaudioをoffに
MATRIXLEDの癖で、、、多分軽くなるはず!

/boot/config.txt

# Enable audio (loads snd_bcm2835) dtparam=audio=off # USB otg ( dwc2 ) enable dtoverlay=dwc2

/etc/modules

echo "dwc2" | tee -a /etc/modules echo "libcomposite" | tee -a /etc/modules

で、リブート!

設定/起動スクリプト

まあ、ファイルを生成&権限作成

setup

touch /usr/bin/isticktoit_usb chmod +x /usr/bin/isticktoit_usb

上記URLを参考に、6バイト/スイッチ12個/POVハット1個/スティック4軸なゲームパッドデバイスを定義

/usr/bin/isticktoit_usb

#!/bin/bash cd /sys/kernel/config/usb_gadget/ mkdir -p isticktoit cd isticktoit echo 0x1d6b > idVendor # Linux Foundation echo 0x0104 > idProduct # Multifunction Composite Gadget echo 0x0100 > bcdDevice # v1.0.0 echo 0x0200 > bcdUSB # USB2 mkdir -p strings/0x409 echo "fedcba9876543210" > strings/0x409/serialnumber echo "eucalyptus." > strings/0x409/manufacturer echo "Wiimote Bridge" > strings/0x409/product mkdir -p configs/c.1/strings/0x409 echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration echo 250 > configs/c.1/MaxPower # Add functions here mkdir -p functions/hid.usb0 echo 0 > functions/hid.usb0/protocol echo 0 > functions/hid.usb0/subclass echo 6 > functions/hid.usb0/report_length echo -ne \\x05\\x01\\x09\\x05\\xa1\\x01\\x05\\x09\\x15\\x00\\x25\\x01\\x19\\x01\\x29\\x0c\\x75\\x01\\x95\\x0c\\x81\\x02\\x05\\x01\\x09\\x39\\x25\\x07\\x35\\x00\\x46\\x3b\\x01\\x75\\x04\\x95\\x01\\x65\\x14\\x81\\x42\\x09\\x01\\x15\\x81\\x25\\x7f\\x35\\x81\\x45\\x7f\\xa1\\x00\\x09\\x30\\x09\\x31\\x09\\x33\\x09\\x34\\x75\\x08\\x95\\x04\\x81\\x02\\xc0\\xc0 > functions/hid.usb0/report_desc ln -s functions/hid.usb0 configs/c.1/ # End functions ls /sys/class/udc > UDC

起動と認識、終了

ここで一度shutdown、御手持ちのMicroUSB-BケーブルでPCと接続(PWR側じゃなくてUSB側をね)
PCからのUSB電源で起動するので、セルフパワーなUSBHUBをかましたりすることを推奨な感じで

起動は「/usr/bin/isticktoit_usb」
上手くいけば、PC側デバイスマネージャにゲームパッドが現れます
終了は「rm /sys/kernel/config/usb_gadget/isticktoit/configs/c.1/hid.usb0」
テストは、コントロールパネル→デバイスとプリンター→つないだゲームパッド(上記だとWiimote Bride)を選択し右クリックからの「ゲームコントローラーの設定」→プロパティ で行えます
但し!、このプロパティ、ウィンドウがアクティブな時じゃないと描画更新してくれません
echoコマンドでボタン状態弄れますが、更新されん!、とかなり悩んじゃいました・・・
例えば、「echo -ne "\x01\x20\x00\x00\x00\x00" > /dev/hidg0」でボタン状態がこんな感じに更新されます

キャプションを入力できます

Wiiリモコンの接続

この辺を参照しました
https://harukingworld.blog.fc2.com/blog-entry-462.html
https://uepon.hatenadiary.com/entry/2016/04/08/123950

setup

apt-get install git cd /opt git clone https://github.com/the-raspberry-pi-guy/Wiimote.git cd Wiimote sh setup.sh

まあ、今更のWiiリモコンなので、ソースが古い感じではありますが
cwiidを使う感じなのですが、WiiリモコンPlusもちゃんと認識しましたよ、と

セットアップが終わったら、Example下の「wiimote.py」を実行してみる感じで
ラズパイの近くにWiiリモコンを持っていき、しつこく「1」と「2」のボタンを押しまくると、繋がります
抜けるときは「+」と「-」を押せばOK!
「Home」ボタンで、加速度センサの値がゲットできますよと

Wiiリモコンブリッジの作製

ここまできたら、あとは上記ExampleなPythonの小改造でなんとかなりそうな感じです
ヌンチャク周りは、以下URLを参照してみたりしてみました
https://github.com/Haven-Lau/Wiimote-for-Raspberry-Pi-Python/blob/master/wiimotetest2.py

wiimotebridge.py

import cwiid, time import codecs import os, sys button_delay = 0.005 acc_threshold = 96 stick_duplex = 2 time.sleep(1) while True: # wait connection... try: wii=cwiid.Wiimote() except RuntimeError: time.sleep(0.2) continue # connect and work wii.rpt_mode = cwiid.RPT_BTN | cwiid.RPT_ACC | cwiid.RPT_EXT wii.rumble = 1 time.sleep(0.5) wii.rumble = 0 prevaccremcalc = 65535 prevaccnuncalc = 65535 reload(sys) outputusb = open('/dev/hidg0', 'rb+') while True: buttons = wii.state['buttons'] nunchukbuttons = wii.state['nunchuk']['buttons'] accrem = wii.state['acc'] accnun = wii.state['nunchuk']['acc'] sticknun = wii.state['nunchuk']['stick'] # Detects whether + and - are held down and if they are it quits the program if (buttons - cwiid.BTN_PLUS - cwiid.BTN_MINUS == 0): wii.rumble = 1 time.sleep(0.5) wii.rumble = 0 outputusb.close() break # stick position stickposx = sticknun[0] - 128 stickposy = 128 - sticknun[1] stickposx *= stick_duplex if stickposx > 127: stickposx = 127 if stickposx < -127: stickposx = -127 stickposy *= stick_duplex if stickposy > 127: stickposy = 127 if stickposy < -127: stickposy = -127 stickposx = '{:02x}'.format(stickposx & 0xff) stickposy = '{:02x}'.format(stickposy & 0xff) stickposzx = 0 stickposzy = 0 # make buttionbits outputbit0 = 0 outputbit1 = 0 jumpdetect = 0 # nunchuk Z ( autorun ) if nunchukbuttons == 1: outputbit0 += 16 # nunchuk C ( main command window ) if nunchukbuttons == 2: outputbit0 += 1 # nunchuk Z+C if nunchukbuttons == 3: outputbit0 += 17 # remote 1 ( map window ) if (buttons & cwiid.BTN_1): outputbit0 += 8 # remote 2 ( jump / camera ) if (buttons & cwiid.BTN_2): jumpdetect = 1 # remote + ( communication window ) if (buttons & cwiid.BTN_PLUS): outputbit1 += 2 # remote - ( camera L ) if (buttons & cwiid.BTN_MINUS): outputbit0 += 64 # remote home ( camera R ) if (buttons & cwiid.BTN_HOME): outputbit0 += 128 # remote A ( multipurpose ) if (buttons & cwiid.BTN_A): outputbit0 += 2 # remote B ( cancel ) if (buttons & cwiid.BTN_B): outputbit0 += 4 # make HATSWITCH HATDATA = 0 if (buttons & cwiid.BTN_LEFT): HATDATA += 1 stickposzx = 127 if(buttons & cwiid.BTN_RIGHT): HATDATA += 2 stickposzx = -127 if (buttons & cwiid.BTN_UP): HATDATA += 4 stickposzy = 127 if (buttons & cwiid.BTN_DOWN): HATDATA += 8 stickposzy = -127 if HATDATA == 1: outputbit1 += 96 elif HATDATA == 2: outputbit1 += 32 elif HATDATA == 4: pass elif HATDATA == 5: outputbit1 += 112 elif HATDATA == 6: outputbit1 += 16 elif HATDATA == 8: outputbit1 += 64 elif HATDATA == 9: outputbit1 += 80 elif HATDATA == 10: outputbit1 += 48 else: outputbit1 += 128 stickposzx = '{:02x}'.format(stickposzx & 0xff) stickposzy = '{:02x}'.format(stickposzy & 0xff) # shake wiiremote ( camera reset ) accremcalc = accrem[0] + accrem[1] + accrem[2] if prevaccremcalc != 65535: accremtry = abs(accremcalc - prevaccremcalc) if accremtry > acc_threshold: outputbit1 += 8 prevaccremcalc = accremcalc # shake nunchack ( jump ) accnuncalc = accnun[0] + accnun[1] + accnun[2] if prevaccnuncalc != 65535: accnuntry = abs(accnuncalc - prevaccnuncalc) if accnuntry > acc_threshold: outputbit0 += 32 elif jumpdetect == 1: outputbit0 += 32 prevaccnuncalc = accnuncalc # make outputbinary outputbit0 = '{:02x}'.format(outputbit0) outputbit1 = '{:02x}'.format(outputbit1) print str(outputbit0) + str(outputbit1) + str(stickposx) + str(stickposy) + str(stickposzx) + str(stickposzy) outputdata = codecs.decode(str(outputbit0) + str(outputbit1) + str(stickposx) + str(stickposy) + str(stickposzx) + str(stickposzy),'hex_codec') outputusb.write(outputdata) outputusb.flush() time.sleep(button_delay)

ハマりどころは、POV HAT周りの実装と、あと16進変換周りかな・・・
変換周りは以下な感じ
*手持ちのヌンチャクのスティックデータがどうもパッとしないので、値を逓倍する感じで
*ヌンチャク振ってジャンプ、Wiiリモコン振ってカメラリセットは、乱暴にXYZ3値を足しこんで、前データとの差の絶対値でボタンを発火するようにしてみたり
オリジナル要素として、2でジャンプ(長押しでカメラ起動)、-とHOMEでコミュニケーションウインドウのタブ送り(とカメラ回転)ができます。
あと、DQXコンフィグでカメラ用アナログスティックを、十字キー操作と同時に割り当てることもできたりして。

キーバインディング

上記URLの改造なので、パケット構造は以下な感じ

packet

+---------+-----+-----+-----+-----+-----+-----+-----+-----+ |Byte Bits| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | 10 calc | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | +---------+-----+-----+-----+-----+-----+-----+-----+-----+ | +0 |btn08|btn07|btn06|btn05|btn04|btn03|btn02|btn01| +---------+-----+-----+-----+-----+-----+-----+-----+-----+ | +1 | Hat switch |btn12|btn11|btn10|btn09| +---------+-----------------------+-----+-----+-----+-----+ | +2 | X axis | +---------+-----------------------------------------------+ | +3 | Y axis | +---------+-----------------------------------------------+ | +4 | XR axis | +---------+-----------------------------------------------+ | +5 | YR axis | +---------+-----------------------------------------------+

まあ、1、2、4、8、16・・・を足していく、かなり乱暴なやり口で
キーの割り当ては、archiveに残ってたDQXページと
キャプションを入力できます

PC版DQXに標準で定義されている「ドラゴンクエストX ゲームコントローラ for PC」で相関しました

キャプションを入力できます

実行!

上記スクリプトを実行すると、まずは何も起きません
ラズパイの近くにWiiリモコンを持っていき、「1+2」あるいは裏面Syncボタンで接続
接続後は、Wiiリモコンが震えた後、だーーっと6バイト16進なデータがターミナルに流れます
同時に、6バイトバイナリをUSBホストへ投げ続けます
ゲームコントローラのプロパティや、ゲーム画面そのもので、動作を確認できますよ、と
「make outputbinary」のprint文を削除することで、ターミナル出力は抑制できます
「+」と「-」で終了!

サービス登録

まあ、USBドングル化、みたいな
キック用スクリプトと、systemdへの登録をすれば、「PCに繋げば使える」リモコンアダプタに化けますよ

/opt/wiimotebridgekicker.sh

#!/bin/bash while : do rm /sys/kernel/config/usb_gadget/isticktoit/configs/c.1/hid.usb0 > /dev/null 2>&1 /bin/sleep 2 /usr/bin/isticktoit_usb > dev/null 2>&1 /bin/sleep 2 /usr/bin/python /opt/wiimotebridge.py > /dev/null 2>&1 done
[Unit]
Description = wiimotebridge
After=local-fs.target
ConditionPathExists=/opt/

[Service]
ExecStart=/opt/wiimotebridgekicker.sh
Restart=no
Type=simple

[Install]
WantedBy=multi-user.target

で、「systemctl enable wiimotebridge」で有効化な感じで

さいごに

  • 無事実装できた!
  • 軽い改造で、ボタンの同時押しや、マクロ的なのも実装できるかと思う感じ
  • systemdに仕込むことで、ブリッジ専用ラズパイが作りやすいかなと

なお、相手はPCじゃなくてもいい感じです
例えば、以下URLに、「ポッ拳用HORIゲームパッド」のdescriptionがあるので、これを使ってやれば、Nintendo SwitchなんかにWiiリモコンつなぐこともできるかもね
https://github.com/progmem/Switch-Fightstick/blob/master/HORI_Descriptors

以上!

eucalyのアイコン画像
いつも、てきとうです
ログインしてコメントを投稿する