obd-0.1.0.0: app/Terminal.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Main where
import Control.Exception (bracket)
import Control.Monad (join)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask)
import Control.Monad.Trans.Class (lift)
import Data.List (isSuffixOf)
import qualified Data.ByteString.Char8 as Char8
import Options.Applicative (Parser, execParser,
flag, strOption,
help, long, value,
info, progDesc,
(<>))
import Options.Applicative.Extra (helper)
import System.Console.Haskeline (InputT, MonadException, RunIO(..),
controlIO, runInputT,
defaultSettings, getInputLine)
import System.Hardware.ELM327.Connection (Con, recv, sendString)
import System.Hardware.ELM327.Simulator (defaultSimulator)
import System.Hardware.ELM327.Simulator.OBDBus.VWPolo2007 (stoppedCarBus)
import qualified System.Hardware.ELM327.Connection as Connection
import qualified System.Hardware.ELM327 as ELM327
import qualified System.Hardware.ELM327.Simulator as Simulator
newtype Term a = Term { runTerm :: ReaderT Con IO a }
deriving (Functor, Applicative, Monad, MonadIO, MonadReader Con)
instance MonadException Term where
controlIO f = join . liftIO $ f (RunIO return)
data ConnectionType = ConnectionTypeActualDevice FilePath
| ConnectionTypeSimulator
connectionType :: Parser ConnectionType
connectionType = flag ConnectionTypeActualDevice (const ConnectionTypeSimulator) m <*> serialPort
where m = long "simulator" <> help "Connect to the simulator instead of a serial port"
serialPort :: Parser FilePath
serialPort = strOption m
where m = long "port" <> value "/dev/ttyUSB0" <> help "The serial port to connect to"
main :: IO ()
main = execParser i >>= runTerminal
where i = info p $ progDesc "Connect a terminal to an ELM327 device or simulator."
p = helper <*> connectionType
connect :: ConnectionType -> IO Con
connect ConnectionTypeSimulator = Simulator.connect $ defaultSimulator stoppedCarBus
connect (ConnectionTypeActualDevice dev) = ELM327.connect dev
runTerminal :: ConnectionType -> IO ()
runTerminal ct = bracket (connect ct)
(\c -> putStrLn "Closing connection..." >> Connection.close c)
(liftIO . runTerminalWithConnection)
runTerminalWithConnection :: Con -> IO ()
runTerminalWithConnection = runReaderT (runTerm readEvalPrint)
readEvalPrint :: Term ()
readEvalPrint = runInputT defaultSettings loop
where
loop :: InputT Term ()
loop = do
maybeLine <- getInputLine "% "
case maybeLine of Nothing -> return ()
Just ":exit" -> return ()
Just x -> handleLine x >> loop
handleLine :: String -> InputT Term ()
handleLine cmd = do
con <- lift ask
liftIO $ sendString con $ cmd ++ "\r"
printAll
printAll :: InputT Term ()
printAll = do
maybeBS <- lift ask >>= liftIO . recv
case maybeBS of Nothing -> return ()
Just b -> liftIO . putStrLn $ convertResponse b
where
convertResponse = stripPrompt . filter (/= '\0') . Char8.unpack
stripPrompt :: String -> String
stripPrompt x = if "\n>" `isSuffixOf` x then take (length x - 1) x else x