前回からの続きになります。
前回、Raspberry Piでジョイスティックの入力検出する jsinputd デーモンを作りました。
今回は jsinputd デーモンを使用し、ジョイスティックによりRaspberry Piに実際にアクションさせてみたいと思います。
jsinputd デーモン、及びスクリプトは全て /home/pi/local/bin/ に置いてあるという前提で、下記は記述しています。新しく作成するスクリプトも同ディレクトリに置きます。
◆ジョイスティックのキー入力判定とアクション
ジョイスティックのどのボタンが押された時に、どういうアクションを行なうかを書いたファイルが下記です。
jscmd.sh
#!/bin/bash
JSFILE=$1
update_input() {
js=(`cat ${JSFILE}`)
bt_left=${js[0]}
bt_down=${js[1]}
bt_right=${js[2]}
bt_up=${js[3]}
bt_L1=${js[4]}
bt_R1=${js[5]}
bt_L2=${js[6]}
bt_R2=${js[7]}
bt_select=${js[8]}
bt_start=${js[9]}
bt_L3=${js[10]}
bt_R3=${js[11]}
st_LH=${js[13]}
st_LV=${js[14]}
st_RH=${js[15]}
st_RV=${js[16]}
st_crossH=${js[17]}
st_crossV=${js[18]}
}
joystick_command() {
if [ ${bt_start} -eq 1 ] && [ ${bt_select} -eq 1 ] &&
[ ${bt_L1} -eq 1 ] && [ ${bt_R1} -eq 1 ] &&
[ ${bt_L2} -eq 1 ] && [ ${bt_R2} -eq 1 ]; then
/home/pi/local/bin/talk.sh "Shutdownします"
sudo shutdown -h now
exit 0
fi
if [ ${bt_start} -eq 1 ]; then
/home/pi/local/bin/jihou.sh &
fi
}
update_input
joystick_command
ソフトで1つ目に検出するボタンがジョイスティックの実際のどのボタンに対応するか(startボタンなのか、L1ボタンなのか)は、使用するジョイスティック毎に異なりますので、まずは update_input にてそれぞれ対応する変数に入れています。
joystick_command の部分が実際のアクション定義部分です。
上記例では、start+select+L1+L2+R1+R2 の同時押しで、Raspberry Piをシャットダウンします。
またその旨を音声出力しています。
もう一つのコマンドはstart ボタンだけを押した場合で、その時の時間をお知らせします。
talk.sh や jihou.sh は前々回に作成したスクリプトです。
この部分の追加、カスタマイズはお好きにどうぞ。
(拡張版を↓おまけ2に用意しました。)
# 最初 "#!/bin/sh" としていたのですが、
# "js=(`cat ${JSFILE}`)" という書き方が、"#!/bin/sh" だと駄目のようでした。
# とりあえずですが、#!/bin/bash 固定としておきます。
jsmonitor.sh
#!/bin/sh
JSDEV=$1
JSFILE=$2
while [ `cut -f1 -d. /proc/uptime` -lt 20 ]; do
sleep 1
done
/home/pi/local/bin/jsinputd ${JSDEV} ${JSFILE} &
sleep 0.5
while inotifywait -qq -e modify ${JSFILE}
do
/home/pi/local/bin/jscmd.sh ${JSFILE}
done
ジョイスティック入力検出のための jsinputd デーモンをバックグラウンド起動し、デーモンの出力ファイルに変更があった時(=キー入力に変更があった時)だけ jscmd.sh が呼ばれます。
キー入力に変化がない場合(無入力状態、またはキーを押しっぱなしの場合)は inotifywait により待ち状態となります。
jsinputd の実行の後に 0.5 秒の sleep を入れているのは、デーモンが動作し出力ファイルが作成されるのを待つためです。
一番最初の while の中の sleep は気にしないでください。(↓◆おまけ1で補足します。)
上記スクリプトを取ってきて、実行権限を与えます。
$ cd /home/pi/local/bin/実行してみます。
$ curl -O http://sstea.blog.jp/raspi/script/jscmd.sh
$ curl -O http://sstea.blog.jp/raspi/script/jsmonitor.sh
$ chmod 755 jscmd.sh jsmonitor.sh
$ ./jsmonitor.sh /dev/input/js0 /tmp/jsinput意図通りに動くか確認してみましょう。
jscmd.sh で jihou.sh をバックグラウンド実行させているので、start ボタンを連打しても、ちゃんと押した回数、時間報告してくれます。、
# 上記 jsmonitor.sh は Ctrl-C で終了しても、バックグラウンドで動いている jsinputd は終了しません。
# "killall jsinputd" してあげてください。
# もしくはUSBアダプタを抜いてやると終了します。
◆jsmonitor.shの自動起動
毎回、自分で jsmonitor.sh を実行するのは面倒なので自動的に起動させたいですが、起動時に自動実行させても起動時点でUSBアダプタが刺さっていない(起動後に挿す場合とか)と、どのみち自分で実行する必要があります。
幸いにも Linux には udev というデバイスのプラグアンドプレイに合わせて処理を行なわさせる機構が備わっています。(正直、udev はあまり詳しくないですが。。。)
jsevent.sh
#!/bin/shジョイスティックの抜き差し時に、上記 jsevent.sh を実行する udev のルールを追加してみましょう。
JSFILE=/tmp/jsinput
if [ $1 = "add" ]; then
/home/pi/local/bin/jsmonitor.sh ${DEVNAME} ${JSFILE} &
elif [ $1 = "remove" ]; then
rm -f ${JSFILE}
fi
/etc/udev/rules.d/99-joystick.rules
SUBSYSTEMS=="input", KERNEL=="js[0-9]", ACTION=="add", RUN="/home/pi/local/bin/jsevent.sh add"
SUBSYSTEMS=="input", KERNEL=="js[0-9]", ACTION=="remove", RUN="/home/pi/local/bin/jsevent.sh remove"
上記ルールを下記コマンドにより追加します。
$ curl -O http://sstea.blog.jp/raspi/script/jsevent.shudev をリスタートします。
$ chmod 755 jsevent.sh
$ curl -O http://sstea.blog.jp/raspi/script/99-joystick.rules
$ sudo mv 99-joystick.rules /etc/udev/rules.d/99-joystick.rules
$ sudo service udev restartこれで完成です。
USBアダプタのプラグアンドプレイに合わせて、jsmonitor.sh を実行できるようになりました。
# ちなみに jsevent.sh の DEVNAME ですが、環境変数として設定されるようです。
# おそらく "udevadm info --query=property --name=/dev/input/js0" で
# 表示されるパラメータが使用可能だと思います。
◆おまけ1
udev ルールでプラグアンドプレイに合わせてスクリプトを実行するようにしたわけですが、プラグアンドプレイ時は問題なかったのですが、当初は起動時からUSBアダプタが刺さっている場合に問題がありました。
jsinputd が /tmp/jsinput ファイルを作成するのですが、起動時点で既にUSBアダプタが刺さっている場合、なぜかファイル作成に失敗していたのです。
いろいろデバッグした結果、推測も混じりますが、以下の事が分かりました。
起動時に既に条件を満たしたudevルールがある場合、他の初期化が完了する前の結構早い段階で実行されるようです。(fstab のマウント等より先のようです。)
そのため、まだファイルシステムがread onlyの状態(remount前)に書き込みをしようとして失敗しているようでした。(remountが終わっても、/tmp を ramdisk 化しているため、tmpfs のマウント前の /tmp に書き出していた場合もありました。)
単純に sleep 10 とかを入れて、初期化完了待ちをしようかと思いましたが、そうするとプラグアンドプレイ時に、それだけ待たされてしまいます。
苦肉の策としてですが、起動時からの時間が20秒経っていなければ、20秒経つまで待ちにするという sleep を入れました。
その結果、起動時からUSBアダプタが刺さっている場合でも、プラグアンドプレイ時でも期待した動作をするようになりました。
◆おまけ2
上記の jscmd.sh ではボタンの押しっぱなしによる連続入力が検出できません。
それを検出できるようにした拡張版が↓です。
また mpc 関連の操作ができるようにコマンドを追加しました。
jscmd2.sh
#!/bin/bashupdate_input に関しては変更ありませんので、元スクリプトを参照。
JSFILE=$1
update_input() {
: (省略)
}
joystick_command() {
if [ ${bt_start} -eq 1 ] && [ ${bt_select} -eq 1 ] &&
[ ${bt_L1} -eq 1 ] && [ ${bt_R1} -eq 1 ] &&
[ ${bt_L2} -eq 1 ] && [ ${bt_R2} -eq 1 ]; then
/home/pi/local/bin/talk.sh "Shutdownします"
sudo shutdown -h now
exit 0
fi
if [ ${bt_L3} -eq 1 ] && [ ${bt_R3} -eq 1 ] &&
[ ${bt_L1} -eq 1 ] && [ ${bt_R1} -eq 1 ] &&
[ ${bt_L2} -eq 1 ] && [ ${bt_R2} -eq 1 ]; then
/home/pi/local/bin/talk.sh "Rebootします"
sudo reboot
exit 0
fi
if [ ${bt_start} -eq 1 ]; then
/home/pi/local/bin/jihou.sh &
WAIT=5
return 1
fi
if [ ${bt_right} -eq 1 ]; then
mpc toggle
exit 0
fi
if [ ${bt_down} -eq 1 ]; then
mpc stop
exit 0
fi
if [ ${bt_L1} -eq 1 ]; then
mpc prev
WAIT=5
return 1
fi
if [ ${bt_R1} -eq 1 ]; then
mpc next
WAIT=5
return 1
fi
if [ ${st_LH} -ne 0 ]; then
if [ ${st_LH} -lt 0 ]; then
mpc seek -1
else
mpc seek +1
fi
return 1
fi
if [ ${st_crossV} -ne 0 ]; then
if [ ${st_crossV} -lt 0 ]; then
mpc volume +10
else
mpc volume -10
fi
return 1
fi
return 0
}
while [ 1 ]; do
input=`cat ${JSFILE}`
if [ "${input}" != "${prev_input}" ]; then
prev_input=${input}
WAIT=0
fi
if [ ${WAIT} -ne 0 ]; then
WAIT=$((${WAIT} - 1))
sleep 0.1
continue
fi
update_input
joystick_command
if [ $? -eq 0 ]; then
break
fi
done
$ cd /home/pi/local/bin/
$ curl -O http://sstea.blog.jp/raspi/script/jscmd2.sh
$ mv jscmd2.sh jscmd.sh
$ chmod 755 jscmd.sh