LinuxでWiiリモコンを使う方法

Wiiリモコン PC/デジモノ

リサイクルショップなどで安価に入手可能なWiiリモコンをLinuxPCに接続してリモコンのボタンをおすことで任意のスクリプトを実行できるようにする方法を紹介します。
Wiiリモコンは中古品が市場に大量に出回っていて500円程度で入手できます。ゲーム用のコントローラーなだけあって操作性もよく、マウス・キーボードに次ぐ入力デバイスとして活用できます。

この方法のポイント

今回は、マウスやキーボードのような汎用的な用途ではなく自分で決めた任意のスクリプトを実行するということを目標にします。
これを実現するためには2つのポイントがあります。

  1. WiiリモコンとPCの接続を確実に行う
  2. Wiiリモコンからの入力をシステムから横取りする

まず1.についてです。
通常のBluetooth機器ならば一度ペアリングをしてまえば、双方の電源が入っていれば自動的に接続が完了します。しかしながら、Wiiリモコンの場合にはペアリングをしただけでは再立ち上げ時に接続をしてくれません(※)。ですので、PCが常に対象のWiiリモコンがいないかを探して、Wiiリモコンの電源が入ったら接続コマンドを出す、という動作が必要です。これが1つ目のポイントです。

次に2.についてです。
LinuxマシンとWiiリモコンを接続するとWiiリモコンは不完全なキーボードとして認識されるようです。全て確認したわけではありませんが、矢印キー等は効いているようです。今回はWiiリモコンのボタンを押すことで任意のスクリプトが実行できるようにしたいので、OSの制御をフックして奪う必要があります。これが2つ目のポイントです。

※Bluetoothの接続仕様についてはよくわかりませんが、結果としてそのような挙動になっています。

接続方法

検証環境

今回は以下の環境で検証と動作確認を行いました。

RaspberryPi 3B
OS:2021-01-11-raspios-buster-armhf-lite

PCとWiiリモコンはBluetoothで接続しますので、RaspberrypiでなくともBluetoothを搭載したPCであれば大丈夫です。実際にx86マシンにインストールしたubuntu20.04でも動作を確認しています。

準備

まず最初に、接続したいWiiリモコンのMACアドレス(ハードウェアアドレス)を調べてください。
ここではAA:BB:CC:DD:EE:FFとします。以下適宜ソースコードを読み替えてください。

LinuxとPCの接続設定(接続確認)

以下のコマンドを実行して、PCとWiiリモコンが接続できるか確認します。bluezに関しては、RaspberryPiOSであれば初期状態でインストールされているかと思います。
ペアリングする際は、電池ボックス内の赤ボタンを押すか、①+②ボタンを同時押ししてペアリングモードにしてください。

$sudo apt install bluez
$sudo usermod -a -G bluetooth pi
$sudo reboot
$bluetoothctl
>power on
>sacn on
>pair AA:BB:CC:DD:EE:FF
>connect AA:BB:CC:DD:EE:FF
>trust AA:BB:CC:DD:EE:FF
>scan off

接続出来たら、動作確認をします。
先に述べたようにWiiリモコンは部分的にキーボードとしてふるまうので、Wiiリモコンの矢印ボタンを押してみてキーボードの矢印キーと同じように動けばOKです。

自動接続設定

本来であればBluezでペアリングすれば以降は自動的に接続されるはずなのですが、Wiiリモコンではそうならないので都度PC側から自動で接続をさせるようにします。
以下、エディタはemacsを使用していますが、nanoでもvimでも何でも良いです。
ユーザーpiのホームディレクトリ下にwiiというディレクトリを作り、その中にblac.shというBashスクリプトを作成します。blac.shは先の接続確認で行った一連のbluetoothctlコマンドをまとめなおしただけのものです。

$cd /home/pi
$mkdir wii
$cd wii
$emacs blac.sh
#!/bin/bash

MACADDR='AA:BB:CC:DD:EE:FF'

function connect() {
echo 'power on' \
&& sleep 1 \
&& echo 'connect '${MACADDR} \
&& sleep 10 \
&& echo 'quit'
}

connect | bluetoothctl

次のコマンドでスクリプトに実行権限を付与するのを忘れないようにしてください。

$chmod 777 blac.sh

ここまで出来たら、動作確認を行います。PCとWiiリモコンの接続が切れていることを確認してから、Wiiリモコンをペアリングモードにして次のコマンドを実行してください。

$sudo ./blac.sh

これでWiiリモコンが接続されるはずです。
これでは1回切りですので、cronさんに定期的に見張ってもらうことにします。

$sudo crontab -e
*/1 * * * * /home/pi/wii/blac.sh
*/1 * * * * sleep 30; /home/pi/wii/blac.sh

1分ごとにwiiリモコンを探索するなら1行目だけ、30秒ごとにするなら2行目も追加してください。
(該当のWiiリモコンが見つからなければ空振りで終わるだけです。)
タイミングによってはうまく接続してくれない場合もありますが、何度かトライすれば接続できるはずです。以下のページを参考にさせてもらいました。

bluetoothctl で自動接続 (常時接続) をする方法 - Qiita
はじめにbluetoothctl を使って、Linux マシンにペアリングした Bluetooth デバイスを自動接続・常時接続する方法を紹介します。なお、自動接続・常時接続の際に blueto…

しかし、この方法では一度実行したプロセスが終了しないので、cronが実行されるたびにプロセスがたまっていってしまうので別の方法を使います。(もっと良い方法があったらコメント欄へ!)

Wiiリモコンの追加を監視する

Wiiリモコンのボタンを押されたときに任意のスクリプトを実行させるにはWiiリモコンのイベントを監視しなければなりません。

Wiiリモコンなどの入力機器が接続されると/dev/input以下にイベントファイル(event0、event1・・・)が作成されるのですが、末尾の番号は環境や状況(ほかに接続されているキーボードやマウスなど)によって変わってしまいます。

調べてみると”ルールをつくってWiiリモコンのイベントが特定の名前で作成されるようにする”というのが鉄板のようなのですがLinux初心者の私には理解しがたかったので他の方法を考えました。

具体的には、inotyfyで/dev/input以下を監視して、イベントファイルが作成されたら都度詳細を調べて、wiiリモコンであった場合はその番号のデバイスの制御を奪取する、という方法です。
まずはinotifyをインストールし、スクリプトを作ります。

$sudo apt install inotify-tools
$emacs wmom.sh

wmom.shの中身は次のようにします。スクリプト内で呼び出しているプログラムwmomはこの次に作りますのでここではとりあえずこのままにしておいてください。
cat /proc/bus/input/devicesでデバイスの詳細を取得して、grepで無理やりWiiリモコンに対応するevent番号を取得しています。Wiiリモコン+などデバイス名が違う場合には対応できないかもしれません。その場合は3行目のgrepで指定するキーワードを修正してください。

cat /proc/bus/input/devicesの出力を見ながらWiiリモコンのイベント番号を取得する方法を考えてみてください。ここで紹介している方法は力技です。もっとスマートな方法がある方はぜひコメント欄へ。
#!/bin/bash
function searchWiiRimocon(){
RESULT=`cat /proc/bus/input/devices | grep -A 4 '"Nintendo Wii Remote"' | grep "Handlers"`
RESULT=`echo $RESULT | grep -o event[0-9]*`
echo $RESULT
}
function connectWiiRimocon(){
INPUTDEV=`searchWiiRimocon`
if [ -n "$INPUTDEV" ];then
WIIDEV="/dev/input/"$INPUTDEV
./wmom $WIIDEV
wait
fi
}

SCRIPT_DIR=`dirname $0`
cd $SCRIPT_DIR
connectWiiRimocon
while true
do
inotifywait /dev/input
wait
connectWiiRimocon
done
$chmod 777 wmom.sh

/dev/input以下を監視して、追加されたイベントファイルが”Nintendo Wii Remote”だったら、そのファイル名をイベント処理用のプログラム”wmom”に引き渡します。

Wiiリモコンの制御をとる

Wiiリモコンの接続を検知したスクリプトから呼び出されるスクリプトを作成します。この部分はC言語(gcc)を使います。

$emacs wmom.c

wmom.cの中身は次のようになります。

#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc,char *argv[]) {

char *INPUT_DEVICE =argv[1];
int input = open(INPUT_DEVICE, O_RDWR);
ioctl(input, EVIOCGRAB, 1);

for(;;){
struct input_event event;
if (read(input, &event, sizeof(event)) != sizeof(event)) {
break;
}
if(event.type ==1){
if(event.code==103 && event.value==1){
printf("up\n");
}
else if(event.code==108 && event.value==1){
printf("down\n");
}
else if(event.code==105 && event.value==1){
printf("left\n");
}
else if(event.code==106 && event.value==1){
printf("right\n");
}
else if(event.code==304 && event.value==1){
printf("A\n");
}
else if(event.code==305 && event.value==1){
printf("B\n");
}
else if(event.code==412 && event.value==1){
printf("-\n");
}
else if(event.code==407 && event.value==1){
printf("+\n");
}
else if(event.code==316 && event.value==1){
printf("home\n");
}
else if(event.code==257 && event.value==1){
printf("1\n");
}
else if(event.code==258 && event.value==1){
printf("2\n");
}
}
}

ioctl(input, EVIOCGRAB, 0);
return 0;
}

重要なポイントはここです

ioctl(input, EVIOCGRAB, 1);

この部分はなくても動きます。ただし、wiiリモコンの矢印キーだけは標準ドライバでキーボードの矢印キーとしてふるまうので、見えないところで端末のキーボードが押されている状況になります。エンターキーがないので実害はないのですが、気持ち悪いので無効にするためのものです。
このおまじない一つで標準ドライバーを無効化できます。プログラムが終了すると普通のキーボードとしてふるまいます。
ioctl(input, EVIOCGRAB, 1);が効いている間は入力された情報をOSに渡さず、このプログラム内のみで処理します。この部分がないときは、プログラム内での処理が終わった後に入力した情報がOSに引き渡されます。
また、event.value==1はボタンが押されたとき、event.value==0はボタンが離されたときに対応していますのでifの部分は適宜書き換えて煮るなり焼くなりしてください。
C言語内の記述で済むようであれば直接処理を書いても良いですし、別のスクリプトを用意してそれを呼び出すこともできます。
コンパイルは以下のようにしてください。

$gcc wmom.c -o wmom

エラーなくコンパイルが終了したら、実行して動作確認をしましょう。Wiiリモコンが接続されている状態にしたうえで以下のコマンドを実行します。

$./wmom

上記のプログラムのままであれば、押したWiiリモコンのボタンに合わせて文字が表示されます、何かのスクリプトを実行するようにしていたのであれば、そのスクリプトが動作するはずです。

制御プログラムの自動起動

スクリプト(wmom.sh)はシステム立ち上げ時に自動的に起動しておいてほしいので、サービスとして登録をしておきます。

$sudo emacs /etc/systemd/system/wmom.service
[Unit]
After=network-online.target

[Service]
User=root
ExecStart=/home/pi/wii/wmom.sh

[Install]
WantedBy=multi-user.target
$sudo systemctl enable wmom.service

これでエラーが出なければOKです。

まとめ

いかがだったでしょうか。Wiiリモコンで何をさせるかは各自のアイデア次第かと思います。

・用意しておいた画像が画面に表示する

・用意しておいた音楽を再生する

といったことが考えられますね。ボタンごとに画像や音楽を登録しておけば展示やプレゼンのようなものでも活用できるかもしれません。

・定型文のメールを送信する

というのもアリですね。お留守番中の子供や遠隔地の両親に持たせて、何かあったらこのボタンを押すと定型文が自分に送信されるというシステムを作ってみるのもいいかもしれません。

コメント