目的
- ドラクエ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の癖で、、、多分軽くなるはず!
# Enable audio (loads snd_bcm2835)
dtparam=audio=off
# USB otg ( dwc2 ) enable
dtoverlay=dwc2
echo "dwc2" | tee -a /etc/modules
echo "libcomposite" | tee -a /etc/modules
で、リブート!
設定/起動スクリプト
まあ、ファイルを生成&権限作成
touch /usr/bin/isticktoit_usb
chmod +x /usr/bin/isticktoit_usb
上記URLを参考に、6バイト/スイッチ12個/POVハット1個/スティック4軸なゲームパッドデバイスを定義
#!/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
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
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の改造なので、パケット構造は以下な感じ
+---------+-----+-----+-----+-----+-----+-----+-----+-----+
|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に繋げば使える」リモコンアダプタに化けますよ
#!/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
以上!