今日は某氏が取り仕切るMaker Faire Tokyo 2015 に遊びに行きました。大いに啓蒙されたので、帰宅後、俺も何かやろうということで、「Raspberry Pi Mouseはどの言語でも動かせるアピール」の一環としてHaskellでRaspberry Pi Mouseを動かしてみました。
Raspberry Pi Mouseというのは日経Linuxの連載「Raspberry Piで始めるかんたんロボット製作 」で作っているロボットです。よく「パソコンのカーソル動かす方のマウス」と間違われるのですが、ここで言っている「マウス」というのはロボット競技のマイクロマウスの「マウス」です。
このロボットをHaskellで動かすわけですが、そのためにはセンサの読み込みとモータの動作を非同期で行う必要があります。私みたいな阿呆でも分かる解説を探していたところ、
Haskellでマルチスレッド処理 – Qiita
が大変分かりやすかったので参考にさせていただきました。
ちゃんとやるにはこの本↓も参考になるかもしれません。(amazonに飛びます。)
コードの前に動いたロボットの様子をお見せすると、
VIDEO
というように、前進して壁を検知したら止まるという動作ができました。たいしたことはやってませんが、滑らかに動きつつセンサの値を読んでいるのがミソです。
このムービーで使ったコードはミニマムなサンプルにして、GitHubにアップしてあります 。これで
シェルスクリプト(bash)
C/C++
Python
Haskell
と、自分がよく使う4大言語で動きました。デバイスファイルに字を出し入れするだけで動くので当たり前ですが・・・。たぶん、もうやらなくても十分アピールになるかなあ・・・。
Raspberry PiでHaskellを使うときはsudo apt-get install ghcします。
コード
コードは以下のような感じです。もう一度書いておくと、Haskellでマルチスレッド処理 – Qiita のコード(サンプルコードその2)の影響を色濃く受けています。
大雑把な説明ですが、モータを動かすforward関数と、センサ値を読み出すreadSensorという関数がロボットに直接関与しています(デバイスファイルを読み書きしている)。こいつらは両方最後に自分を呼び出していて、readSensorだけ、センサ値が閾値を超えると、その旨をputMVarという関数でrefに反映して処理を終わっています。
forwardとreadSensorを動かしているのはmain関数のにあるforkIOで、これが非同期に上記2つの関数を実行します。watchSensor関数はmainで呼ばれ、refの値を監視してTrueだったらforwardの処理を止め、そうでなかったら再度自分自身を呼び出しています。refの型はMVar Boolで、これは非同期で動くスレッド間で安全に読み書きできるそうです。間違ってたらアレなので、その筋の方のブログや書籍を参考にどうぞ。
import Control.Concurrent
import Control.Monad
import System.Posix.Unistd
-- 単純に前進する関数
forward :: IO ()
forward = do
-- 車輪を400Hzで0.1秒回す
writeFile "/dev/rtmotor0" "400 400 100"
-- 無限ループ(・・・という言い方は不適切か?)
forward
-- 距離センサを読み出す
readSensor :: MVar Bool -> IO ()
readSensor ref = do
-- センサを休める
threadDelay 200000
cs <- readFile "/dev/rtlightsensor0"
-- 4つのセンサの値を合計
let v = sum [ read w :: Int | w <- words cs ]
-- センサが反応していなかったら再度計測
if v > 1000 then putMVar ref True else readSensor ref
-- センサに何か反応したらモータのスレッドを止める
watchSensor :: MVar Bool -> ThreadId -> IO ()
watchSensor ref w = do
tf <- takeMVar ref
if tf then killThread w else watchSensor ref w
main = do
ref <- newMVar False
w <- forkIO $ forward
_ <- forkIO $ readSensor ref
watchSensor ref w
この部分だけ見ると、Haskellなのに手続き的なのですが、ここにもっと高度な処理を関数で書いていけるのならば、Haskellで書くメリットも出てくるかもしれません。個人的にはシミュレーションで書いたHaskellの関数が使るという確実なメリットが。
ということでHaskellでロボットが動いたので、後はどなたか関数型言語でロボットを動かす講義をやって欲しい・・・。私はやりません。
参考: Raspberry Pi Mouseって何?
こちらのページで。