{-| Module : Fit.Internal.FitFile Copyright : Copyright 2014-2015, Matt Giles License : Modified BSD License (see LICENSE file) Maintainer : matt.w.giles@gmail.com Stability : experimental -} module Fit.Internal.FitFile ( Fit(..), FitHeader(..), Message(..), msgLmt, MessageDefinition(..), FieldDef(..), Field(..), Value(..), Array(..), MessageHeader(..), LocalMessageType(..), mkLocalMessageType, unLocalMessageType, TimeOffset(..), mkTimeOffset, Timestamp(..), BaseType(..), btSize ) where import Fit.Internal.Architecture import Data.Bits (Bits, (.&.)) import Data.ByteString (ByteString) import Data.Int (Int8, Int16, Int32) import Data.Sequence (Seq) import Data.Text (Text) import Data.Word (Word8, Word16, Word32) -- | A FIT file consists of a header and a collection of messages. data Fit = Fit { fHeader :: FitHeader, fMessages :: [Message] } deriving (Show) -- | The FIT file header data FitHeader = FH { fhSize :: !Word8, -- ^ Size of the header in bytes. Will always be 12 or 14, -- based on presence of the CRC fhProtocolVersion :: !Word8, -- ^ Protocol version number fhProfileVersion :: !Word16, -- ^ Profile version number fhDataSize :: !Word32, -- ^ Combined length of the FIT messages, in bytes fhDataType :: ByteString, -- ^ File tag, should always be ".FIT" fhCrc :: !(Maybe Word16) -- ^ Optional checksum for header contents } deriving (Show) -- | There are two kinds of FIT messages: -- -- * __Definition messages__ set the structure for messages of a particular /local message type/ -- * __Data messages__ contain the actual information, according to the structure given by a -- definition message. data Message = DefM MessageDefinition | DataM !LocalMessageType !Int [Field] deriving (Show) msgLmt :: Message -> LocalMessageType msgLmt (DefM (MessageDef lmt _ _ _)) = lmt msgLmt (DataM lmt _ _) = lmt -- | A 'MessageDefinition' for a local message type (LMT) determines how future data messages with -- that LMT are decoded. LMTs can be re-used: a data message with LMT @n@ will use the /most recent/ -- message definition for LMT @n@. data MessageDefinition = MessageDef { defLocalType :: !LocalMessageType, -- ^ The local message type being defined defGlobalType :: !Int, -- ^ The /global/ message type this LMT will refer to. Must be a -- valid @mesg_num@ value from the FIT profile defArch :: !Arch, -- ^ The /architecture/ this messages with this LMT will use -- for multi-byte values (little- or big-endian) defFields :: [FieldDef] -- ^ Definitions for the fields messages with this LMT will contain } deriving (Show, Eq) -- | Defines the structure for a single field in a message data FieldDef = FieldDef { fdNum :: !Int, -- ^ The /field number/. The interpretation of the field number depends on the -- global message type and is found in the FIT profile fdSize :: !Int, -- ^ The size, in bytes, of the field's contents. This will be a multiple of -- the base type size. In a singleton field this size will be the same as the -- base type size. In an array field it will be some multiple of the base type -- size. fdBaseType :: !BaseType -- ^ The FIT base type of values in the field } deriving (Show, Eq) -- | A single field in a data message, containing the field number and the value(s) data Field = SingletonField !Int Value | ArrayField !Int Array deriving (Show) -- | Singleton values. There is a Value constructor for each BaseType constructor. The wrapped -- value in these constructors corresponds to the specific format used in the FIT file, for -- example an 'enum' in FIT is stored as an 8-bit unsigned int (ie a Word8). The primary exception -- to this is using 'Text' for string values. data Value = EnumValue !Word8 | SInt8Value !Int8 | UInt8Value !Word8 | SInt16Value !Int16 | UInt16Value !Word16 | SInt32Value !Int32 | UInt32Value !Word32 | StringValue Text -- ^ A 'string' in FIT is a null-terminated arrays of UTF-8 -- code units, but 'Text' is used here instead | Float32Value !Float | Float64Value !Double | UInt8ZValue !Word8 | UInt16ZValue !Word16 | UInt32ZValue !Word32 | ByteValue !Word8 deriving (Show) -- | Array values use similar constructors to singleton values. However, there is no constructor -- for arrays of strings, which seem to be unused in FIT. data Array = EnumArray (Seq Word8) | SInt8Array (Seq Int8) | UInt8Array (Seq Word8) | SInt16Array (Seq Int16) | UInt16Array (Seq Word16) | SInt32Array (Seq Int32) | UInt32Array (Seq Word32) | Float32Array (Seq Float) | Float64Array (Seq Double) | UInt8ZArray (Seq Word8) | UInt16ZArray (Seq Word16) | UInt32ZArray (Seq Word32) | ByteArray (Seq Word8) deriving (Show) -- | Each message has a header that primarily determines whether the message is a -- definition or data message. If the message uses a /compressed timestamp header/, -- the header also contains the compressed time offset. data MessageHeader = DefHeader !LocalMessageType | DataHeader !LocalMessageType | CTDataHeader !LocalMessageType !TimeOffset deriving (Show) -- | A local message type is a 4 bit unsigned integer newtype LocalMessageType = LMT { unLmt :: Int8 } deriving (Show, Eq) -- | Only the lower 4 bits of the integer are used to construct a 'LocalMessageType' mkLocalMessageType :: (Integral a, Bits a) => a -> LocalMessageType mkLocalMessageType = LMT . fromIntegral . ((.&.) 0xF) -- | Unwrap a 'LocalMessageType'. The resulting integer will be between 0 and 15 unLocalMessageType :: Integral a => LocalMessageType -> a unLocalMessageType = fromIntegral . unLmt -- | A time offset is 5 bits newtype TimeOffset = TO { unTo :: Word8 } deriving (Show) -- | Only the lower 5 bits of the number are used mkTimeOffset :: (Integral a, Bits a) => a -> TimeOffset mkTimeOffset = TO . fromIntegral . ((.&.) 0x1F) newtype Timestamp = Timestamp { unTimestamp :: Word32 } deriving (Show, Eq) -- | The different types that FIT uses for field values data BaseType = FitEnum | FitSInt8 | FitUInt8 | FitSInt16 | FitUInt16 | FitSInt32 | FitUInt32 | FitString | FitFloat32 | FitFloat64 | FitUInt8Z | FitUInt16Z | FitUInt32Z | FitByte deriving (Show, Eq) -- | Get the size in bytes for a single value of the given base type btSize :: BaseType -> Int btSize bt = case bt of FitEnum -> 1 FitSInt8 -> 1 FitUInt8 -> 1 FitSInt16 -> 2 FitUInt16 -> 2 FitSInt32 -> 4 FitUInt32 -> 4 FitString -> 1 FitFloat32 -> 4 FitFloat64 -> 8 FitUInt8Z -> 1 FitUInt16Z -> 2 FitUInt32Z -> 4 FitByte -> 1