packages feed

ecu-0.0.8: src/CAN.hs

module CAN
  ( module CANData
  , Bus
  , initCAN
  , openBus
  , closeBus
  , recvMsg
  , recvMsgWait
  , sendMsg
  , busTime
  , busTime'
  , testBusStatus
  , testIFStatus
  , sendUntil
  , toPayload
  , fromPayload
  , flushRxQueue
  ) where

import Data.Bits
import Data.Char
import Data.Word

import CANData

foreign import ccall unsafe initCAN         :: IO ()
foreign import ccall unsafe canOpenExt_     :: Int -> IO Int
foreign import ccall unsafe canOpenStd_     :: Int -> IO Int
--foreign import ccall unsafe canOpenVirtual_ :: Int -> IO Int
foreign import ccall unsafe canClose_       :: Int -> IO ()
--foreign import ccall unsafe canRead_        :: Int -> IO Int
foreign import ccall unsafe canReadWait_    :: Int -> Int -> IO Int
foreign import ccall unsafe canReadId_      :: IO Int
foreign import ccall unsafe canReadTime_    :: IO Int
foreign import ccall unsafe canReadMsgLen_  :: IO Int
foreign import ccall unsafe canReadMsg_     :: Int -> IO Char
foreign import ccall unsafe canWriteByte_   :: Char -> Int -> IO ()
foreign import ccall unsafe canWrite_       :: Int -> Int -> Int -> IO ()
foreign import ccall unsafe canReadTimer_   :: Int -> IO Int
foreign import ccall unsafe canFlushReceiveQueue_ :: Int -> IO Int

newtype Bus = Bus Int

openBus :: Int -> BusType -> IO Bus
openBus channel busType = do
  h <- case busType of
    Standard -> canOpenStd_ channel
    Extended -> canOpenExt_ channel
  return $ Bus h

closeBus :: Bus -> IO ()
closeBus (Bus h) = canClose_ h

-- | Receive a message if a message is available.  Includes receive time (ms).  
recvMsg :: Bus -> IO (Maybe (Int, Msg))
recvMsg b = recvMsgWait b 0

-- | Waits a number of ms to receive a message.  Includes receive time (ms).  
recvMsgWait :: Bus -> Int -> IO (Maybe (Int, Msg))
recvMsgWait (Bus h) wait = do
  status <- canReadWait_ h wait
  id <- canReadId_
  if status /= 0 || id == 0 then return Nothing else do
    len  <- canReadMsgLen_
    msg  <- mapM canReadMsg_ [0 .. len - 1]
    t    <- canReadTime_
    return $ Just (t, Msg (fromIntegral id) (map (fromIntegral . ord) msg))

-- | Send a message.
sendMsg :: Bus -> Msg -> IO ()
sendMsg (Bus h) (Msg id payload') = do
  mapM_ (\ (b, i) -> canWriteByte_ (chr $ fromIntegral b) i) $ zip payload [0..]
  canWrite_ h (fromIntegral id) $ length payload
  where
  payload = take 8 payload'

flushRxQueue :: Bus -> IO ()
flushRxQueue (Bus h) = do
  canFlushReceiveQueue_ h
  return ()

-- | Current driver time (ms).
busTime :: Bus -> IO (Maybe Int)
busTime (Bus h) = do
  status <- canReadTimer_ h
  if status /= 0 then return Nothing else do
    t <- canReadTime_
    return $ Just t

busTime' :: Bus -> IO Int
busTime' bus = do
    t <- busTime bus
    case t of
      Nothing -> busTime' bus
      Just t  -> return t

testBusStatus :: Bus -> IO Int
testBusStatus (Bus h) = do
  status <- canReadWait_ h 500
  return status

testIFStatus :: Bus -> IO Int
testIFStatus (Bus h) = do
  status <- canReadTimer_ h
  return status

-- | Send a message periodically (in ms) until a certain message is received.
sendUntil :: Bus -> Int -> Msg -> (Msg -> Bool) -> IO Msg
sendUntil bus retryTime msg isAck = getStartTime >>= f1
  where
  f1 startTime = sendMsg bus msg >> f2 startTime startTime
  f2 startTime currentTime | currentTime - startTime > retryTime = f1 currentTime
  f2 startTime currentTime = do
    m <- recvMsgWait bus $ max 0 $ retryTime - (currentTime - startTime)
    case m of
      Nothing                 -> f1 currentTime
      Just (t, m) | isAck m   -> return m
                  | otherwise -> f2 startTime t

  getStartTime = do
    m <- recvMsg bus
    case m of
      Nothing -> getStartTime
      Just (t, _) -> return t

toPayload :: Word64 -> [Word8]
toPayload payload = [ fromIntegral (shiftR payload (b * 8) .&. 0xFF) | b <- reverse [0..7] ]

fromPayload :: [Word8] -> Word64
fromPayload payload = foldr1 (.|.) [ shiftL (fromIntegral x .&. 0xFF) (b * 8) | (x, b) <- zip payload (reverse [0..7]) ]