fit-0.5: src/Fit/Messages.hs
-- | The Messages API abtracts over the structure of a FIT file slightly and presents
-- the FIT file as just the sequence of data messages in the file. The Messages API
-- also abstracts over the various FIT base types (for example, signed/unsigned integers
-- of different sizes) to give a simpler set of types to work with.
--
-- If you need to know about the very specifics of the FIT file structure, use the Raw API
-- instead. However, for pulling information out of a FIT file this API is much more
-- convenient.
module Fit.Messages (
readMessages,
readFileMessages,
parseMessages,
Messages(..),
Message(..),
Field(..),
Value(..),
SingletonValue(..),
ArrayValue(..)
) where
import qualified Fit.Internal.FitFile as Raw
import qualified Fit.Internal.Parse as Raw
import Control.Applicative ((<$>))
import Data.Attoparsec.ByteString (Parser)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B (readFile, pack)
import qualified Data.Foldable as F (toList)
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as Map (empty, insert)
import Data.Maybe (catMaybes)
import Data.Sequence (Seq)
import qualified Data.Sequence as S (fromList)
import Data.Text (Text)
import Data.Word (Word8)
-- | The collection of data messages from the FIT file.
newtype Messages = Messages {
_messages :: Seq Message
} deriving (Show)
-- | A FIT data message
data Message = Message {
_mNumber :: !Int, -- ^ The global message number, as found in the FIT profile
_mFields :: IntMap Field -- ^ The fields in the message, mapped from field number to @Field@
} deriving (Show)
-- | A single field in a FIT data message
data Field = Field {
_fNumber :: !Int, -- ^ The field number, as found in the FIT profile
_fValue :: Value
} deriving (Show)
-- | FIT values can either contain a single piece of data or an array. FIT arrays are homogenous
data Value = Singleton SingletonValue
| Array ArrayValue
deriving (Show)
-- | A singleton value. In the Messages API we abstract over the specific FIT base type of the field. For example, the FIT types uint8, sint8, uint16, etc. are all presented as an @IntValue@. FIT strings (ie. character arrays) are presented as singleton @TextValue@s. If you need to know the specific base type of a field you can use the Raw API.
data SingletonValue = IntValue !Int
| RealValue !Double
| ByteValue !Word8
| TextValue Text
deriving (Show)
-- | Array values. Like singleton values these ignore the specific FIT base type to present a simpler interface. Byte arrays are presented as strict @ByteString@s. There are no character arrays, since the singleton @TextValue@ handles that case.
data ArrayValue = IntArray (Seq Int)
| RealArray (Seq Double)
| ByteArray ByteString
deriving (Show)
-- | Parse a strict @ByteString@ containing the FIT data into its @Messages@
readMessages :: ByteString -> Either String Messages
readMessages bs = toMessages <$> Raw.readFitRaw bs
-- | Parse the given FIT file into its @Messages@
readFileMessages :: FilePath -> IO (Either String Messages)
readFileMessages fp = B.readFile fp >>= return . readMessages
-- | An Attoparsec parser for @Messages@
parseMessages :: Parser Messages
parseMessages = fmap toMessages Raw.parseFit
toMessages :: Raw.Fit -> Messages
toMessages rFit = Messages . S.fromList . catMaybes $ fmap toMessage (Raw.fMessages rFit)
toMessage :: Raw.Message -> Maybe Message
toMessage (Raw.DefM _) = Nothing
toMessage (Raw.DataM _ gmt fields) = Just $ Message gmt (foldr go Map.empty fields)
where go f@(Raw.SingletonField num _) fieldMap = Map.insert num (toField f) fieldMap
go f@(Raw.ArrayField num _) fieldMap = Map.insert num (toField f) fieldMap
toField :: Raw.Field -> Field
toField (Raw.SingletonField num value) = Field num . Singleton $ (fromSingletonValue value)
toField (Raw.ArrayField num array) = Field num . Array $ (fromArray array)
fromSingletonValue :: Raw.Value -> SingletonValue
fromSingletonValue v =
case v of
Raw.EnumValue i -> IntValue (fromIntegral i)
Raw.SInt8Value i -> IntValue (fromIntegral i)
Raw.UInt8Value i -> IntValue (fromIntegral i)
Raw.SInt16Value i -> IntValue (fromIntegral i)
Raw.UInt16Value i -> IntValue (fromIntegral i)
Raw.SInt32Value i -> IntValue (fromIntegral i)
Raw.UInt32Value i -> IntValue (fromIntegral i)
Raw.StringValue t -> TextValue t
Raw.Float32Value f -> RealValue (fromRational (toRational f))
Raw.Float64Value f -> RealValue f
Raw.UInt8ZValue i -> IntValue (fromIntegral i)
Raw.UInt16ZValue i -> IntValue (fromIntegral i)
Raw.UInt32ZValue i -> IntValue (fromIntegral i)
Raw.ByteValue b -> ByteValue b
fromArray :: Raw.Array -> ArrayValue
fromArray a =
case a of
Raw.EnumArray xs -> intArray xs
Raw.SInt8Array xs -> intArray xs
Raw.UInt8Array xs -> intArray xs
Raw.SInt16Array xs -> intArray xs
Raw.UInt16Array xs -> intArray xs
Raw.SInt32Array xs -> intArray xs
Raw.UInt32Array xs -> intArray xs
Raw.Float32Array xs -> realArray xs
Raw.Float64Array xs -> realArray xs
Raw.UInt8ZArray xs -> intArray xs
Raw.UInt16ZArray xs -> intArray xs
Raw.UInt32ZArray xs -> intArray xs
Raw.ByteArray bs -> ByteArray . B.pack $ F.toList bs
where intArray is = IntArray $ fmap fromIntegral is
realArray rs = RealArray $ fmap (fromRational . toRational) rs