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

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

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

＊Blutooth経由でWiiリモコンに繋ぎ
　Zeroシリーズは、USBが１ポートしかない為、無印ではなく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」でボタン状態がこんな感じに更新されます
![dqx_wii4.png](https://qiita-image-store.s3.amazonaws.com/0/216885/746ccc69-149c-d6fd-2418-d0d0a1b6a11c.png)

# 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リモコンを持っていき、しつこく「１」と「２」のボタンを押しまくると、繋がります
抜けるときは「＋」と「－」を押せば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リモコン振ってカメラリセットは、乱暴にXYZ３値を足しこんで、前データとの差の絶対値でボタンを発火するようにしてみたり
オリジナル要素として、２でジャンプ（長押しでカメラ起動）、－と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ページと
![dqx_wii2.png](https://qiita-image-store.s3.amazonaws.com/0/216885/02a17050-700b-9ff0-39d9-8675fcb20096.png)

PC版DQXに標準で定義されている「ドラゴンクエストX ゲームコントローラ for PC」で相関しました
![dqx_wii3.png](https://qiita-image-store.s3.amazonaws.com/0/216885/daedf6ae-986c-589c-f681-aab6fce65be4.png)

## 実行！
上記スクリプトを実行すると、まずは何も起きません
ラズパイの近くにWiiリモコンを持っていき、「１＋２」あるいは裏面Syncボタンで接続
接続後は、Wiiリモコンが震えた後、だーーっと６バイト１６進なデータがターミナルに流れます
同時に、６バイトバイナリを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

以上！