MEMS(ECU)モニタ開発状況 GHCのバグ?環境整備の問題?

インジェクション・ローバーミニの車載コンピュータのモニタリング,Macではstack環境下で順調に開発・運用していますが,ラズパイでの運用は未完。stackでのコンパイルがまだできないので,ghcで直接コンパイルをしてみたのですが...

現状把握:何が起こっているか

使用している serialport パッケージ内の関数で引数の型として指定されている ByteString の参照先が Mac でstackを使ってコンパイル(stack 2.1.3) した場合と RaspberryPi で直接GHCでコンパイル(GHC 8.0.1 on Raspberian 4.14.79-V7+)した場合とで異なる。ラズパイ上ではなぜかbytestringのインターナルモジュール内の定義を参照し,呼び出し側は,モジュール内で明示的に指定した公開ライブラリを見にいっています。

具体的には,serialportパッケージのsend関数の第2引数の型が合いません。serialportの中では

> import qualified Data.ByteString.Char8 as B
>(中略)
> send :: SerialPort -> B.ByteString -> IO Int

と定義されています。一方,自作のモジュール内では,

> import qualified Data.ByteString.Char8 as BS
> (中略)
> send p $ BS.singleton (chr 0x0a)

みたいな感じで呼び出しています。

引数の型は合っているはず。Data.ByteString.Char8 (以下,「D.B.C」と省略)でのsingletonの定義は,

> singleton :: Char -> ByteString

です(尤も,D.B.C内のByteStringの定義は,後述の通り,Internalモジュールからインポートしていますが)。

実際,macOSでstack buildをした場合,問題なく通りますし,運用もできます。ところがraspberianで ghc でのコンパイルをすると,BS.ByteString は Data.ByteString.Internal で定義されているbytestring-0.10.8.1:Data.ByteString.Internal.ByteStringと合わないというわけで,型の不一致とされてしまいます。うーむ。

原因分析:何が原因か

serialportのソースをおっかけてみると,まずByteStringの定義は前述の通り,D.B.Cを参照しています。また,sendの第2引数は,途中でWord8に変換するため,B.unpackをかけており,その先のD.B.Cのほうでは,unpackは結局,内部関数のunpackAppendCharsLazyを束縛しています。

> unpackChars :: ByteString -> [Char]
> unpackChars bs = unpackAppendCharsLazy bs []
> (後略)

そこでunpackAppendCharsLazyを見ると,内部のByteStringの定義を用いて関数が定義

> unpackAppendCharsLazy :: ByteString -> [Char] -> [Char]
> unpackAppendCharsLazy (PS fp off len) cs
> (後略)

されています。

InternalモジュールでのByteStringの定義は
> data ByteString = PS {-# UNPACK #-} !(ForeignPtr Word8) -- payload
> {-# UNPACK #-} !Int -- offset
> {-# UNPACK #-} !Int -- length
> deriving (Typeable)

となっています。これは外部公開していないモジュールのはずですから,ここを参照すべきではないでしょうし,D.B.Cのほうで,ちゃんとByteStringをエクスポートしています。

> module Data.ByteString.Char8 (
>
> -- * The @ByteString@ type
> ByteString, -- abstract, instances: Eq, Ord, Show, Read, Data, Typeable, Monoid
> (後略)

ですので,D.B.Cを指定したByteStringで型は一致するはずなのですが...

真因追求:本当の原因として考えられること

  • raspberian 上で整えた GHC の環境がおかしい? ... 冒頭で述べたように,stackでの開発がラズパイ上ではできていません。cabal ライブラリのコンパイルをはじめて,大きなライブラリのコンパイルでメモリが不足して止まってしまう(一応,仮想メモリとして4GBを確保してあるのですが)。stackの環境構築がこのように中途半端になっているので,それがローカルなライブラリDBを構成するなどの悪さをしている?
  • 環境・構成に依存するGHCの挙動の違い? ... GHC のバージョンや,Stackを使った場合と生で呼び出した場合のGHCに渡されるオプションや構成の違いが真因の可能性も。

考えられる対処

いずれにしても,検証するには環境を合わせることから始める必要がありそうです。そもそも stack ・ GHC 直接という違いもあり,macOS と raspberian とでは GHC のバージョンも違うため,そこらへんを合わせてみることが必要なのかもしれません。

思い切って,ラズパイ用は go言語で組み直してみる?手続き型言語の冗長さと,代数データ型やモナドが使えないなどの面倒さにつきあうのはためらいがありますが...。

蛇足

こんなデータを0.5秒おきにとっています。開発目的は,時々発生するECU気絶に由来すると思われるエンジン停止や回転数低下の原因を特定したいためと,IoTや関数型言語でのプログラミングの勉強。

下記画像では,右端の部分で時々横線が入っているように見える部分が,そこが,ECUのデータ読み出しができなかった瞬間です。左側の数値が羅列されている部分でいうと,文字列が並んでいる行です。

気が付かれる方もあるかもしれませんが,魚野の車はマニュアル車です。ですが,ECUが原因かという検証のため,修理業者さんが別の車のオートマ用ECUをつないでくれたために,オートマと表示しています。ECUが変わっても,瞬停や回転数落ちはまだ発生していますので,ECUそのものが原因ではないと思っています。

Rover Mini ECU(MEMS) Monitor CSV Data

Rover Mini ECU(MEMS) Monitor CSV Data