------------------------------------------------------------------------------- -- | -- Module : System.Hardware.Arduino.Comm -- Copyright : (c) Levent Erkok -- License : BSD3 -- Maintainer : erkokl@gmail.com -- Stability : experimental -- -- Basic serial communication routines ------------------------------------------------------------------------------- {-# LANGUAGE NamedFieldPuns #-} module System.Hardware.Arduino.Comm where import Control.Monad.State (modify, runStateT, when) import qualified Data.ByteString as B (pack, unpack, concat, length) import qualified System.Hardware.Serialport as S (withSerial, defaultSerialSettings, CommSpeed(CS57600), commSpeed, recv, send) import System.Hardware.Arduino.Data import System.Hardware.Arduino.Utils import System.Hardware.Arduino.Protocol import System.Hardware.Arduino.Firmata -- | Run the Haskell program to control the board: -- -- * The file path argument should point to the device file that is -- associated with the board. ('COM1' on Windows, -- '/dev/cu.usbmodemfd131' on Mac, etc.) -- -- * The boolean argument controls verbosity. It should remain -- 'False' unless you have communication issues. The print-out -- is typically less-than-useful, but it might point to the root -- cause of the problem. -- -- See "System.Hardware.Arduino.Examples.Blink" for a simple example. withArduino :: Bool -- ^ If 'True', debugging info will be printed -> FilePath -- ^ Path to the USB port -> Arduino () -- ^ The Haskell controller program to run -> IO () withArduino verbose fp program = do debugger <- mkDebugPrinter verbose debugger $ "Accessing arduino located at: " ++ show fp let Arduino controller = do (v1, v2, m) <- queryFirmware modify (\b -> b{firmataID = "Firmware v" ++ show v1 ++ "." ++ show v2 ++ "(" ++ m ++ ")"}) program S.withSerial fp S.defaultSerialSettings{S.commSpeed = S.CS57600} $ \port -> do _ <- runStateT controller (mkState debugger port) return () where mkState debugger port = ArduinoState debugger port "ID: Uninitialized" (Just (ArduinoChannel recvChan recvNChan sendChan)) where extract b = do let resp = unpackage b debugger $ "Received: " ++ show resp return resp recvChan = do debugger "Waiting for a Sysex response.." let skip = do b <- S.recv port 1 case B.unpack b of [] -> skip [0xF0] -> return () bs -> do debugger $ "Skipping bytes <" ++ unwords (map showByte bs) ++ ">" skip collect sofar = do b <- S.recv port 1 let rmsg = b : sofar if b == B.pack [0xF7] -- end message then return $ reverse rmsg else collect rmsg skip chunks <- collect [B.pack [0xF0]] extract $ B.concat chunks recvNChan n = do debugger $ "Waiting for a non-Sysex response of " ++ show n ++ " bytes" let go need sofar | need <= 0 = return sofar | True = do b <- S.recv port need case B.length b of 0 -> go need sofar l -> do when (need < l) $ debugger $ "Received partial response: <" ++ unwords (map showByte (B.unpack b)) ++ ">" go (need - l) (b : sofar) chunks <- go n [] extract $ B.concat $ reverse chunks sendChan msg = do let p = package msg lp = B.length p debugger $ "Sending: " ++ show msg ++ " <" ++ unwords (map showByte (B.unpack p)) ++ ">" sent <- S.send port p when (sent /= lp) (debugger $ "Send failed. Tried: " ++ show lp ++ "bytes, reported: " ++ show sent)