USB接続のゲームパッドのボタンを押したら、なんらかのコマンドを実行するということを簡単にできる仕組みが FreeBSD にはあります。X やコンソールなどと無関係に使えるので、画面のない状態で使うことの多いワンボードコンピュータ(RaspberryPi など)で特に使える仕組みだと思います。
今回使用するのは バッファローの BSGP801という結構昔に手に入れたスーファミっぽいタイプのゲームパッドです。同型はもう売ってないっぽいですデーモン起動の場合はコンソール出力が使えないので、echo とかの画面出力系ではないコマンドが、他のゲームパッドでも同じような手順で使えるはずです。
ゲームパッドつなぐ
FreeBSDマシンのUSB ポートにゲームパッドをつなぐと、以下のようなログが dmesg に出力されます。
ugen0.3: at usbus0
uhid0 on uhub0
uhid0: on usbus0
これで uhid0 というデバイスノードが /dev 以下に生えました。このデバイスノードをコマンド実行時に指定します。
ボタンの名前など情報を取得する
つないだゲームパッドの各ボタンには名前が割り振られます。イベントを取得するにはその名前が必要なのです。名前の付き方はゲームパッドによって色々なので、つないで動かして確認します。
情報やイベントを取得するには usbhidctl というコマンドを使います(man usbhidctl)。pkg/ports で取得する必要はなく、標準で入っています。
root 権限で、上でつないだ/dev/uhid0 に対して
# usbhidctl -f /dev/uhid0 -a
を実行すると、ボタンと現在のボタン状態が=で繋がれて表示されます。
Generic_Desktop:Joystick.Generic_Desktop:Pointer.Generic_Desktop:X=0
Generic_Desktop:Joystick.Generic_Desktop:Pointer.Generic_Desktop:Y=0
Generic_Desktop:Joystick.Button:Button_1=0
Generic_Desktop:Joystick.Button:Button_2=0
Generic_Desktop:Joystick.Button:Button_3=0
Generic_Desktop:Joystick.Button:Button_4=0
Generic_Desktop:Joystick.Button:Button_5=0
Generic_Desktop:Joystick.Button:Button_6=0
Generic_Desktop:Joystick.Button:Button_7=0
Generic_Desktop:Joystick.Button:Button_8=0
名前一覧が手に入っても具体的にどれがどのボタンかはまだわかりません。
# usbhidctl -f /dev/uhid0 -a -l
を実行すると、一覧表示がループ実行されます。この状態でボタンを押すと、=の右辺の数値が変化します。
写真のゲームパッドで Aボタンを押すと
〜
Generic_Desktop:Joystick.Button:Button_1=0
〜
が
〜
Generic_Desktop:Joystick.Button:Button_1=1
〜
に変化するので、Aボタンは「Generic_Desktop:Joystick.Button:Button_1」であることがわかりました。
また、十字キーは中立の場合が
Generic_Desktop:Joystick.Generic_Desktop:Pointer.Generic_Desktop:X=128
となっていて、左が「0」右が「255」と変化しました。
イベント取得するデーモンを設定
次はイベントを監視してコマンドを実行するデーモンの設定です。
上記の usbhidctl の出力を使って出来ないこともなさそうですが、ちゃんと専用のデーモンコマンドがあります。
これも標準で入っている usbhidaction というコマンドです(man usbhidaction)。
押すボタンと実行させたいコマンドを紐付ける設定ファイルを作成して、usbhidaction コマンドに渡せば、デーモンがイベント監視して、コマンドを実行してくれるようになります。
設定ファイルの書式は
(ボタン名) (値) (デバウンス値) (コマンド)
- ボタン名は usbhidctl使って上で取得した値です。
- イベント値も上で変化を確認した値で、その値に変化したときにイベントが発生します。
"*"指定でどの値でも引っかかるようになります。 - デバウンス値は"0"ならボタン押しっぱなしでもイベントが飛び続け、"1"ならばボタンを押した(離した)状態変化時のみイベントが飛びます。
- コマンドは実行させたいコマンドです。起動させるタイミングで(rcスクリプトやdevdなど)/usr/local/bin とかにパスが通ってないこともあるのでフルパス指定しておいたほうが無難。
設定ファイル例
Aボタンを押すと echo A が実行される設定を作成します。
Generic_Desktop:Joystick.Button:Button_1 1 1 /bin/echo A
というファイルを gamepad.conf という名前で作成して、root権限で
# usbhidaction -f /dev/uhid0 -c gamepad.conf -v
を実行します。-c オプションで設定ファイル指定します。通常オプション無指定ならばデーモンとしてバックグラウンドで実行されますが、実行結果を確かめるために -v オプションでフォアグラウンドで動作状況表示させるように指定しています。
この状態でAボタンを押すと、
system '/bin/echo A'
A
と echo A が実行されます。
次に設定ファルを
Generic_Desktop:Joystick.Button:Button_1 1 0 /bin/echo A
とデバウンス値を"0"に変えて実行すると、Aボタンを押しっぱなしにしたらコマンドが繰り返し実行され、イベントが飛び続けることがわかります。
また設定ファイルを変えて
Generic_Desktop:Joystick.Button:Button_1 * 1 /bin/echo A
と値に"*" を指定すると、押した時と離した時どちらでもイベントが飛ぶようになります。
普通は上記のようなイベントの飛び方なのですが、今回使用しているゲームパッドの十字キーの中立値 128 は、デバウンス値を 1 に設定しても常にイベントが飛ぶ動きとなっています。実際の動きはデバイスにつないでみないとわからない場合もあると思います。
動作を確認したら、デーモンとして起動します。上記コマンドで -v をとって実行するとバックグランドで起動します。
実行タイミング
起動直後から起動したい場合は、/usr/local/etc/rc.d にスクリプトを作成して置きます。ゲームパッドを USB に挿入したら起動するようにするには devd を設定します。そこら辺の手順については、今回は説明を省きます。
実際の動き
RaspberryPi 3B+ ゲームパッドをつないでやってみました。以下は Youtube に上げた動画のリンクです。
- Aボタン 英語で現在時刻読み上げ(audio/espeak コマンド使用)
- Bボタン 日本語で現在時刻読み上げ(openjtalk コマンドを野良ビルドして使用)
- Xボタン Yahoo!ニュースのヘッドラインを読み上げ
- 十字キー サーボを pwm で動かす
日本語読み上げはちょっと時間がかかってますね。
Yahoo!ニュースヘッドラインは、Web から
/usr/bin/fetch -o - https://news.yahoo.co.jp/ |
/usr/local/bin/xmllint --html - 2> /dev/null |
/usr/bin/grep 'data-ual-gotocontent' |
/usr/bin/sed -e 's/<[^>]*>//g' |
/usr/bin/grep -v '^$' |
/usr/bin/head -n 3
って感じで取得した文字列を openjtalk に渡しているので更に時間がかかっています。
キーボードともマウスとも違うインターフェース
こんな感じでうまいことコマンドを割り振って、キーボードともマウスとも違うインターフェースを追加することができます。NanoPi Neo のように、そもそも画面出力がないワンボードコンピュータもありますから、ゲームパッドとの組み合わせは相性がいいと思います。