eucaly が 2020年12月31日15時27分35秒 に編集
初版
タイトルの変更
Raspberry Pi Zeroで、Wiiリモコン→USBゲームパッドブリッジを作ってみる
タグの変更
Python
RaspberryPi
USB
wii
DQX
秋葉原2021
本文の変更
- この記事は、Qiitaからの転載です。 - が、古の面白デバイス「Wiiリモコン」の活用記事なので、転載してみました。 - 元URL: https://qiita.com/eucalyhome/items/3bb0d5be1e8b498ca741 # 目的 - ドラクエ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の癖で、、、多分軽くなるはず! ```sh:/boot/config.txt # Enable audio (loads snd_bcm2835) dtparam=audio=off # USB otg ( dwc2 ) enable dtoverlay=dwc2 ``` ```sh:/etc/modules echo "dwc2" | tee -a /etc/modules echo "libcomposite" | tee -a /etc/modules ``` で、リブート! ## 設定/起動スクリプト まあ、ファイルを生成&権限作成 ```sh:setup touch /usr/bin/isticktoit_usb chmod +x /usr/bin/isticktoit_usb ``` 上記URLを参考に、6バイト/スイッチ12個/POVハット1個/スティック4軸なゲームパッドデバイスを定義 ```sh:/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」でボタン状態がこんな感じに更新されます ![キャプションを入力できます](https://camo.elchika.com/5545d29adb42d092e4a47fe96777d7cdba5f3f3e/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f63356134313034622d616637392d343538322d623365662d393265323562656661383332/) # Wiiリモコンの接続 この辺を参照しました https://harukingworld.blog.fc2.com/blog-entry-462.html https://uepon.hatenadiary.com/entry/2016/04/08/123950 ```sh: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 ```python: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の改造なので、パケット構造は以下な感じ ```sh: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ページと ![キャプションを入力できます](https://camo.elchika.com/1e099ccd6f83850acc0dd84006fd6f907801a4de/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f36646334666230352d363665652d346162382d393264632d303531613336386431643237/) PC版DQXに標準で定義されている「ドラゴンクエストX ゲームコントローラ for PC」で相関しました ![キャプションを入力できます](https://camo.elchika.com/1dda2e84599b7dbfb35aa91ccae08cbc1db77051/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f38666637633237372d646465642d343333632d393736342d6162303436346433326631302f64343461656362622d316437652d343239382d613261302d383238353365383363346332/) ## 実行! 上記スクリプトを実行すると、まずは何も起きません ラズパイの近くにWiiリモコンを持っていき、「1+2」あるいは裏面Syncボタンで接続 接続後は、Wiiリモコンが震えた後、だーーっと6バイト16進なデータがターミナルに流れます 同時に、6バイトバイナリをUSBホストへ投げ続けます ゲームコントローラのプロパティや、ゲーム画面そのもので、動作を確認できますよ、と 「make outputbinary」のprint文を削除することで、ターミナル出力は抑制できます 「+」と「-」で終了! # サービス登録 まあ、USBドングル化、みたいな キック用スクリプトと、systemdへの登録をすれば、「PCに繋げば使える」リモコンアダプタに化けますよ ```sh:/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 ``` ```/etc/systemd/system/wiimotebridge.service [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 以上!