ecu-0.0.0: src/ToVCD.hs
module Main (main) where
import Data.Bits
import qualified Data.ByteString.Lazy as B
import Data.IORef
import Data.VCD hiding (Bool)
import Data.Word
import System.Environment
import System.IO
import Text.Printf
main :: IO ()
main = do
args <- getArgs
case args of
('-' : _) : _ -> help
[dbc, "-"] -> do
dbc <- readFile dbc
cl <- B.getContents >>= return . B.unpack
buildVCD (parseDBC dbc) (parseCL cl) stdout
dbc : cls -> do
dbc <- readFile dbc
mapM_ (buildVCDFile $ parseDBC dbc) cls
_ -> help
help :: IO ()
help = putStrLn $ unlines
[ ""
, " NAME"
, " tovcd - converts dbc and cl files to vcd"
, ""
, " SYNOPSIS"
, " tovcd <dbc-file> ( - | { <cl-file> } )"
, ""
, " OPTIONS"
, " - Read cl from stdin, vcd written to stdout."
, ""
]
-- Generate VCD to a handle.
buildVCD :: DBC -> [(Word32, Word32, Word64)] -> Handle -> IO ()
buildVCD dbc cl h = do
vcd <- newVCD h MS
sample <- mapM (sampleMsg vcd) dbc
mapM_ (\ a -> sequence_ [ f a | f <- sample ]) cl
-- Sets up the VCD sampling functions for each field in each message.
sampleMsg :: VCDHandle -> Msg -> IO ((Word32, Word32, Word64) -> IO ())
sampleMsg vcd (Msg id fields) = do
sampleFields <- mapM (sampleField vcd) fields
t <- newIORef 0
return $ \ (time, id', payload) -> if id /= id' then return () else do
t' <- readIORef t
step vcd $ fromIntegral (time - t')
writeIORef t time
sequence_ [ f payload | f <- sampleFields ]
-- Sample an individual field from a payload.
sampleField :: VCDHandle -> Field -> IO (Word64 -> IO ())
sampleField vcd field = case field of
Bool name bit -> do
v <- var vcd name False
return $ \ payload -> v $ testBit payload $ 64 - bit
Float name bit width gain bias -> do
v <- var vcd name (0 :: Float)
return $ \ payload -> v $ fromIntegral (shiftR payload (71 - width - bit) .&. mask width) * gain + bias
where
mask :: Int -> Word64
mask n = if n <= 0 then 0 else 1 .|. shiftL (mask $ n - 1) 1
-- Generate a VCD to a file.
buildVCDFile :: DBC -> FilePath -> IO ()
buildVCDFile dbc clFile = do
putStrLn $ "converting " ++ clFile ++ " to " ++ vcdFile ++ " ..."
cl <- B.readFile clFile >>= return . B.unpack
h <- openFile vcdFile WriteMode
buildVCD dbc (parseCL cl) h
hClose h
where
vcdFile = if elem '.' clFile then reverse (dropWhile (/= '.') (reverse clFile)) ++ "vcd" else clFile ++ ".vcd"
-- DBC is a list of messages.
type DBC = [Msg]
-- A message is a is a CAN ID and a list of fields.
data Msg = Msg Word32 [Field]
instance Show Msg where
show (Msg id fields) = printf "Msg %08x\n" id ++ concat [ " " ++ show f ++ "\n" | f <- fields ]
-- A field is either a boolean (name, bit position) or a float (name, bit position, width, scaling, and offset).
data Field
= Bool String Int
| Float String Int Int Float Float
deriving Show
parseDBC :: String -> DBC
parseDBC = parseMsgs . lines
parseMsgs :: [String] -> [Msg]
parseMsgs [] = []
parseMsgs (a : b)
| not (null w) && head w == "BO_" = Msg (0x7fffffff .&. read (w !! 1)) fields : parseMsgs rest
| otherwise = parseMsgs b
where
w = words a
(fields, rest) = parseFields b
parseFields :: [String] -> ([Field], [String])
parseFields [] = ([], [])
parseFields (a : b)
| not (null w) && head w == "SG_" = (field : fields, rest)
| otherwise = ([], a : b)
where
w = words $ map (\ a -> if elem a "|@(,)" then ' ' else a) a
(fields, rest) = parseFields b
name = w !! 1
bit = read $ w !! 3
width = read $ w !! 4
gain = read $ w !! 6
bias = read $ w !! 7
field = case width of
1 -> Bool name bit
_ -> Float name bit width gain bias
parseCL :: [Word8] -> [(Word32, Word32, Word64)]
parseCL = f
where
f (a0:a1:a2:a3:a4:a5:a6:a7:a8:a9:aa:ab:ac:ad:ae:af:rest) =
(word32 (a3, a2, a1, a0), word32 (a7, a6, a5, a4), word64 (a8, a9, aa, ab, ac, ad, ae, af)) : f rest
f _ = []
-- | Assumes big endian. Flip order if data is little endian.
word32 :: (Word8, Word8, Word8, Word8) -> Word32
word32 (a0,a1,a2,a3) = shiftL (fromIntegral a0) 24
.|. shiftL (fromIntegral a1) 16
.|. shiftL (fromIntegral a2) 8
.|. (fromIntegral a3)
-- | Assumes big endian. Flip order if data is little endian.
word64 :: (Word8, Word8, Word8, Word8, Word8, Word8, Word8, Word8) -> Word64
word64 (a0,a1,a2,a3,a4,a5,a6,a7) = shiftL (fromIntegral a0) 56
.|. shiftL (fromIntegral a1) 48
.|. shiftL (fromIntegral a2) 40
.|. shiftL (fromIntegral a3) 32
.|. shiftL (fromIntegral a4) 24
.|. shiftL (fromIntegral a5) 16
.|. shiftL (fromIntegral a6) 8
.|. (fromIntegral a7)