例のロボットを今度はHaskellで動かした
Sun Aug 2 17:23:08 JST 2015 (modified: Sat Sep 30 16:15:34 JST 2017)
views: 2139, keywords:Haskell,Raspberry この記事は最終更新日が7年以上前のものです。
今日は某氏が取り仕切るMaker Faire Tokyo 2015に遊びに行きました。大いに啓蒙されたので、帰宅後、俺も何かやろうということで、「Raspberry Pi Mouseはどの言語でも動かせるアピール」の一環としてHaskellでRaspberry Pi Mouseを動かしてみました。
Raspberry Pi Mouseというのは日経Linuxの連載「Raspberry Piで始めるかんたんロボット製作」で作っているロボットです。よく「パソコンのカーソル動かす方のマウス」と間違われるのですが、ここで言っている「マウス」というのはロボット競技のマイクロマウスの「マウス」です。
このロボットをHaskellで動かすわけですが、そのためにはセンサの読み込みとモータの動作を非同期で行う必要があります。私みたいな阿呆でも分かる解説を探していたところ、
が大変分かりやすかったので参考にさせていただきました。
ちゃんとやるにはこの本↓も参考になるかもしれません。(amazonに飛びます。)
コードの前に動いたロボットの様子をお見せすると、
というように、前進して壁を検知したら止まるという動作ができました。たいしたことはやってませんが、滑らかに動きつつセンサの値を読んでいるのがミソです。
このムービーで使ったコードはミニマムなサンプルにして、GitHubにアップしてあります。これで
- シェルスクリプト(bash)
- C/C++
- Python
- Haskell
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 ()
= do
forward -- 車輪を400Hzで0.1秒回す
writeFile "/dev/rtmotor0" "400 400 100"
-- 無限ループ(・・・という言い方は不適切か?)
forward
-- 距離センサを読み出す
readSensor :: MVar Bool -> IO ()
= do
readSensor ref -- センサを休める
200000
threadDelay <- readFile "/dev/rtlightsensor0"
cs -- 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 ()
= do
watchSensor ref w <- takeMVar ref
tf if tf then killThread w else watchSensor ref w
= do
main <- newMVar False
ref <- forkIO $ forward
w <- forkIO $ readSensor ref
_
watchSensor ref w
この部分だけ見ると、Haskellなのに手続き的なのですが、ここにもっと高度な処理を関数で書いていけるのならば、Haskellで書くメリットも出てくるかもしれません。個人的にはシミュレーションで書いたHaskellの関数が使るという確実なメリットが。
ということでHaskellでロボットが動いたので、後はどなたか関数型言語でロボットを動かす講義をやって欲しい・・・。私はやりません。