diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/sbp.cabal b/sbp.cabal
new file mode 100644
--- /dev/null
+++ b/sbp.cabal
@@ -0,0 +1,42 @@
+name:                  sbp
+version:               0.49.0
+synopsis:              SwiftNav's SBP Library
+homepage:              https://github.com/swift-nav/libsbp
+license:               MIT
+author:                Swift Navigation Inc.
+maintainer:            Mark Fine <dev@swiftnav.com>
+copyright:             Copyright (C) 2015 Swift Navigation, Inc.
+category:              Network
+build-type:            Simple
+cabal-version:         >= 1.10
+
+description:
+  SwiftNav's SBP Library.
+
+source-repository head
+  type:                git
+  location:            git@github.com:swift-nav/libsbp.git
+
+library
+  exposed-modules:     SwiftNav.SBP
+                     , SwiftNav.SBP.Acquisition
+                     , SwiftNav.SBP.Bootload
+                     , SwiftNav.SBP.ExtEvents
+                     , SwiftNav.SBP.FileIo
+                     , SwiftNav.SBP.Flash
+                     , SwiftNav.SBP.Logging
+                     , SwiftNav.SBP.Navigation
+                     , SwiftNav.SBP.Observation
+                     , SwiftNav.SBP.Piksi
+                     , SwiftNav.SBP.Settings
+                     , SwiftNav.SBP.System
+                     , SwiftNav.SBP.Tracking
+  default-language:    Haskell2010
+  hs-source-dirs:      src
+  ghc-options:         -Wall -fno-warn-unused-imports
+  build-depends:       base >= 4.7 && < 5
+                     , binary
+                     , bytestring
+                     , data-binary-ieee754
+                     , monad-loops
+  default-extensions:  RecordWildCards
diff --git a/src/SwiftNav/SBP.hs b/src/SwiftNav/SBP.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP.hs
@@ -0,0 +1,124 @@
+module SwiftNav.SBP where
+
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.Put
+import Data.ByteString
+import Data.Word
+import SwiftNav.SBP.Acquisition
+import SwiftNav.SBP.Bootload
+import SwiftNav.SBP.ExtEvents
+import SwiftNav.SBP.FileIo
+import SwiftNav.SBP.Flash
+import SwiftNav.SBP.Logging
+import SwiftNav.SBP.Navigation
+import SwiftNav.SBP.Observation
+import SwiftNav.SBP.Piksi
+import SwiftNav.SBP.Settings
+import SwiftNav.SBP.System
+import SwiftNav.SBP.Tracking
+
+msgPreamble :: Word8
+msgPreamble = 0x55
+
+data Msg = Msg
+  { msgSBPType    :: Word16
+  , msgSBPSender  :: Word16
+  , msgSBPLen     :: Word8
+  , msgSBPPayload :: ByteString
+  , msgSBPCrc     :: Word16
+  } deriving ( Show, Read, Eq )
+
+instance Binary Msg where
+  get = do
+    msgSBPType <- getWord16le
+    msgSBPSender <- getWord16le
+    msgSBPLen <- getWord8
+    msgSBPPayload <- getByteString $ fromIntegral msgSBPLen
+    msgSBPCrc <- getWord16le
+    return Msg {..}
+
+  put Msg {..} = do
+    putWord16le msgSBPType
+    putWord16le msgSBPSender
+    putWord8 msgSBPLen
+    putByteString msgSBPPayload
+    putWord16le msgSBPCrc
+
+getMsg :: Get Msg
+getMsg = do
+  preamble <- getWord8
+  if preamble /= msgPreamble then getMsg else get
+
+putMsg :: Msg -> Put
+putMsg msg = do
+  putWord8 msgPreamble
+  put msg
+
+data SBPMsg =
+     SBPMsgAcqResult MsgAcqResult
+   | SBPMsgAcqResultDepA MsgAcqResultDepA
+   | SBPMsgAlmanac MsgAlmanac
+   | SBPMsgBasePos MsgBasePos
+   | SBPMsgBaselineEcef MsgBaselineEcef
+   | SBPMsgBaselineNed MsgBaselineNed
+   | SBPMsgBootloaderHandshakeDepA MsgBootloaderHandshakeDepA
+   | SBPMsgBootloaderHandshakeReq MsgBootloaderHandshakeReq
+   | SBPMsgBootloaderHandshakeResp MsgBootloaderHandshakeResp
+   | SBPMsgBootloaderJumpToApp MsgBootloaderJumpToApp
+   | SBPMsgCwResults MsgCwResults
+   | SBPMsgCwStart MsgCwStart
+   | SBPMsgDops MsgDops
+   | SBPMsgEphemeris MsgEphemeris
+   | SBPMsgEphemerisDepA MsgEphemerisDepA
+   | SBPMsgEphemerisDepB MsgEphemerisDepB
+   | SBPMsgExtEvent MsgExtEvent
+   | SBPMsgFileioReadDirReq MsgFileioReadDirReq
+   | SBPMsgFileioReadDirResp MsgFileioReadDirResp
+   | SBPMsgFileioReadReq MsgFileioReadReq
+   | SBPMsgFileioReadResp MsgFileioReadResp
+   | SBPMsgFileioRemove MsgFileioRemove
+   | SBPMsgFileioWriteReq MsgFileioWriteReq
+   | SBPMsgFileioWriteResp MsgFileioWriteResp
+   | SBPMsgFlashDone MsgFlashDone
+   | SBPMsgFlashErase MsgFlashErase
+   | SBPMsgFlashProgram MsgFlashProgram
+   | SBPMsgFlashReadReq MsgFlashReadReq
+   | SBPMsgFlashReadResp MsgFlashReadResp
+   | SBPMsgGpsTime MsgGpsTime
+   | SBPMsgHeartbeat MsgHeartbeat
+   | SBPMsgIarState MsgIarState
+   | SBPMsgInitBase MsgInitBase
+   | SBPMsgM25FlashWriteStatus MsgM25FlashWriteStatus
+   | SBPMsgMaskSatellite MsgMaskSatellite
+   | SBPMsgNapDeviceDnaReq MsgNapDeviceDnaReq
+   | SBPMsgNapDeviceDnaResp MsgNapDeviceDnaResp
+   | SBPMsgObs MsgObs
+   | SBPMsgObsDepA MsgObsDepA
+   | SBPMsgPosEcef MsgPosEcef
+   | SBPMsgPosLlh MsgPosLlh
+   | SBPMsgPrint MsgPrint
+   | SBPMsgReset MsgReset
+   | SBPMsgResetFilters MsgResetFilters
+   | SBPMsgSetTime MsgSetTime
+   | SBPMsgSettingsReadByIndexDone MsgSettingsReadByIndexDone
+   | SBPMsgSettingsReadByIndexReq MsgSettingsReadByIndexReq
+   | SBPMsgSettingsReadByIndexResp MsgSettingsReadByIndexResp
+   | SBPMsgSettingsReadReq MsgSettingsReadReq
+   | SBPMsgSettingsReadResp MsgSettingsReadResp
+   | SBPMsgSettingsSave MsgSettingsSave
+   | SBPMsgSettingsWrite MsgSettingsWrite
+   | SBPMsgStartup MsgStartup
+   | SBPMsgStmFlashLockSector MsgStmFlashLockSector
+   | SBPMsgStmFlashUnlockSector MsgStmFlashUnlockSector
+   | SBPMsgStmUniqueIdReq MsgStmUniqueIdReq
+   | SBPMsgStmUniqueIdResp MsgStmUniqueIdResp
+   | SBPMsgThreadState MsgThreadState
+   | SBPMsgTrackingIq MsgTrackingIq
+   | SBPMsgTrackingState MsgTrackingState
+   | SBPMsgTrackingStateDepA MsgTrackingStateDepA
+   | SBPMsgTweet MsgTweet
+   | SBPMsgUartState MsgUartState
+   | SBPMsgVelEcef MsgVelEcef
+   | SBPMsgVelNed MsgVelNed
+  deriving ( Show, Read, Eq )
diff --git a/src/SwiftNav/SBP/Acquisition.hs b/src/SwiftNav/SBP/Acquisition.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Acquisition.hs
@@ -0,0 +1,60 @@
+module SwiftNav.SBP.Acquisition where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgAcqResult :: Word16
+msgAcqResult = 0x0014
+
+data MsgAcqResult = MsgAcqResult
+  { msgAcqResultSnr :: Float
+  , msgAcqResultCp  :: Float
+  , msgAcqResultCf  :: Float
+  , msgAcqResultSid :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgAcqResult where
+  get = do
+    msgAcqResultSnr <- getFloat32le
+    msgAcqResultCp <- getFloat32le
+    msgAcqResultCf <- getFloat32le
+    msgAcqResultSid <- getWord32le
+    return MsgAcqResult {..}
+
+  put MsgAcqResult {..} = do
+    putFloat32le msgAcqResultSnr
+    putFloat32le msgAcqResultCp
+    putFloat32le msgAcqResultCf
+    putWord32le msgAcqResultSid
+
+msgAcqResultDepA :: Word16
+msgAcqResultDepA = 0x0015
+
+data MsgAcqResultDepA = MsgAcqResultDepA
+  { msgAcqResultDepASnr :: Float
+  , msgAcqResultDepACp  :: Float
+  , msgAcqResultDepACf  :: Float
+  , msgAcqResultDepAPrn :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgAcqResultDepA where
+  get = do
+    msgAcqResultDepASnr <- getFloat32le
+    msgAcqResultDepACp <- getFloat32le
+    msgAcqResultDepACf <- getFloat32le
+    msgAcqResultDepAPrn <- getWord8
+    return MsgAcqResultDepA {..}
+
+  put MsgAcqResultDepA {..} = do
+    putFloat32le msgAcqResultDepASnr
+    putFloat32le msgAcqResultDepACp
+    putFloat32le msgAcqResultDepACf
+    putWord8 msgAcqResultDepAPrn
diff --git a/src/SwiftNav/SBP/Bootload.hs b/src/SwiftNav/SBP/Bootload.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Bootload.hs
@@ -0,0 +1,101 @@
+module SwiftNav.SBP.Bootload where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgBootloaderHandshakeReq :: Word16
+msgBootloaderHandshakeReq = 0x00B3
+
+data MsgBootloaderHandshakeReq = MsgBootloaderHandshakeReq
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgBootloaderHandshakeReq where
+  get =
+    return MsgBootloaderHandshakeReq
+
+  put MsgBootloaderHandshakeReq =
+    return ()
+
+msgBootloaderHandshakeResp :: Word16
+msgBootloaderHandshakeResp = 0x00B4
+
+data MsgBootloaderHandshakeResp = MsgBootloaderHandshakeResp
+  { msgBootloaderHandshakeRespFlags   :: Word32
+  , msgBootloaderHandshakeRespVersion :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgBootloaderHandshakeResp where
+  get = do
+    msgBootloaderHandshakeRespFlags <- getWord32le
+    msgBootloaderHandshakeRespVersion <- liftM toStrict getRemainingLazyByteString
+    return MsgBootloaderHandshakeResp {..}
+
+  put MsgBootloaderHandshakeResp {..} = do
+    putWord32le msgBootloaderHandshakeRespFlags
+    putByteString msgBootloaderHandshakeRespVersion
+
+msgBootloaderJumpToApp :: Word16
+msgBootloaderJumpToApp = 0x00B1
+
+data MsgBootloaderJumpToApp = MsgBootloaderJumpToApp
+  { msgBootloaderJumpToAppJump :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgBootloaderJumpToApp where
+  get = do
+    msgBootloaderJumpToAppJump <- getWord8
+    return MsgBootloaderJumpToApp {..}
+
+  put MsgBootloaderJumpToApp {..} = do
+    putWord8 msgBootloaderJumpToAppJump
+
+msgNapDeviceDnaReq :: Word16
+msgNapDeviceDnaReq = 0x00DE
+
+data MsgNapDeviceDnaReq = MsgNapDeviceDnaReq
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgNapDeviceDnaReq where
+  get =
+    return MsgNapDeviceDnaReq
+
+  put MsgNapDeviceDnaReq =
+    return ()
+
+msgNapDeviceDnaResp :: Word16
+msgNapDeviceDnaResp = 0x00DD
+
+data MsgNapDeviceDnaResp = MsgNapDeviceDnaResp
+  { msgNapDeviceDnaRespDna :: [Word8]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgNapDeviceDnaResp where
+  get = do
+    msgNapDeviceDnaRespDna <- replicateM 8 getWord8
+    return MsgNapDeviceDnaResp {..}
+
+  put MsgNapDeviceDnaResp {..} = do
+    mapM_ putWord8 msgNapDeviceDnaRespDna
+
+msgBootloaderHandshakeDepA :: Word16
+msgBootloaderHandshakeDepA = 0x00B0
+
+data MsgBootloaderHandshakeDepA = MsgBootloaderHandshakeDepA
+  { msgBootloaderHandshakeDepAHandshake :: [Word8]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgBootloaderHandshakeDepA where
+  get = do
+    msgBootloaderHandshakeDepAHandshake <- whileM (liftM not isEmpty) getWord8
+    return MsgBootloaderHandshakeDepA {..}
+
+  put MsgBootloaderHandshakeDepA {..} = do
+    mapM_ putWord8 msgBootloaderHandshakeDepAHandshake
diff --git a/src/SwiftNav/SBP/ExtEvents.hs b/src/SwiftNav/SBP/ExtEvents.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/ExtEvents.hs
@@ -0,0 +1,39 @@
+module SwiftNav.SBP.ExtEvents where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgExtEvent :: Word16
+msgExtEvent = 0x0101
+
+data MsgExtEvent = MsgExtEvent
+  { msgExtEventWn    :: Word16
+  , msgExtEventTow   :: Word32
+  , msgExtEventNs    :: Int32
+  , msgExtEventFlags :: Word8
+  , msgExtEventPin   :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgExtEvent where
+  get = do
+    msgExtEventWn <- getWord16le
+    msgExtEventTow <- getWord32le
+    msgExtEventNs <- liftM fromIntegral getWord32le
+    msgExtEventFlags <- getWord8
+    msgExtEventPin <- getWord8
+    return MsgExtEvent {..}
+
+  put MsgExtEvent {..} = do
+    putWord16le msgExtEventWn
+    putWord32le msgExtEventTow
+    putWord32le $ fromIntegral msgExtEventNs
+    putWord8 msgExtEventFlags
+    putWord8 msgExtEventPin
diff --git a/src/SwiftNav/SBP/FileIo.hs b/src/SwiftNav/SBP/FileIo.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/FileIo.hs
@@ -0,0 +1,147 @@
+module SwiftNav.SBP.FileIo where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgFileioReadReq :: Word16
+msgFileioReadReq = 0x00A8
+
+data MsgFileioReadReq = MsgFileioReadReq
+  { msgFileioReadReqSequence   :: Word32
+  , msgFileioReadReqOffset     :: Word32
+  , msgFileioReadReqChunkSize  :: Word8
+  , msgFileioReadReqFilename   :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFileioReadReq where
+  get = do
+    msgFileioReadReqSequence <- getWord32le
+    msgFileioReadReqOffset <- getWord32le
+    msgFileioReadReqChunkSize <- getWord8
+    msgFileioReadReqFilename <- liftM toStrict getRemainingLazyByteString
+    return MsgFileioReadReq {..}
+
+  put MsgFileioReadReq {..} = do
+    putWord32le msgFileioReadReqSequence
+    putWord32le msgFileioReadReqOffset
+    putWord8 msgFileioReadReqChunkSize
+    putByteString msgFileioReadReqFilename
+
+msgFileioReadResp :: Word16
+msgFileioReadResp = 0x00A3
+
+data MsgFileioReadResp = MsgFileioReadResp
+  { msgFileioReadRespSequence :: Word32
+  , msgFileioReadRespContents :: [Word8]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFileioReadResp where
+  get = do
+    msgFileioReadRespSequence <- getWord32le
+    msgFileioReadRespContents <- whileM (liftM not isEmpty) getWord8
+    return MsgFileioReadResp {..}
+
+  put MsgFileioReadResp {..} = do
+    putWord32le msgFileioReadRespSequence
+    mapM_ putWord8 msgFileioReadRespContents
+
+msgFileioReadDirReq :: Word16
+msgFileioReadDirReq = 0x00A9
+
+data MsgFileioReadDirReq = MsgFileioReadDirReq
+  { msgFileioReadDirReqSequence :: Word32
+  , msgFileioReadDirReqOffset   :: Word32
+  , msgFileioReadDirReqDirname  :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFileioReadDirReq where
+  get = do
+    msgFileioReadDirReqSequence <- getWord32le
+    msgFileioReadDirReqOffset <- getWord32le
+    msgFileioReadDirReqDirname <- liftM toStrict getRemainingLazyByteString
+    return MsgFileioReadDirReq {..}
+
+  put MsgFileioReadDirReq {..} = do
+    putWord32le msgFileioReadDirReqSequence
+    putWord32le msgFileioReadDirReqOffset
+    putByteString msgFileioReadDirReqDirname
+
+msgFileioReadDirResp :: Word16
+msgFileioReadDirResp = 0x00AA
+
+data MsgFileioReadDirResp = MsgFileioReadDirResp
+  { msgFileioReadDirRespSequence :: Word32
+  , msgFileioReadDirRespContents :: [Word8]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFileioReadDirResp where
+  get = do
+    msgFileioReadDirRespSequence <- getWord32le
+    msgFileioReadDirRespContents <- whileM (liftM not isEmpty) getWord8
+    return MsgFileioReadDirResp {..}
+
+  put MsgFileioReadDirResp {..} = do
+    putWord32le msgFileioReadDirRespSequence
+    mapM_ putWord8 msgFileioReadDirRespContents
+
+msgFileioRemove :: Word16
+msgFileioRemove = 0x00AC
+
+data MsgFileioRemove = MsgFileioRemove
+  { msgFileioRemoveFilename :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFileioRemove where
+  get = do
+    msgFileioRemoveFilename <- liftM toStrict getRemainingLazyByteString
+    return MsgFileioRemove {..}
+
+  put MsgFileioRemove {..} = do
+    putByteString msgFileioRemoveFilename
+
+msgFileioWriteReq :: Word16
+msgFileioWriteReq = 0x00AD
+
+data MsgFileioWriteReq = MsgFileioWriteReq
+  { msgFileioWriteReqSequence :: Word32
+  , msgFileioWriteReqOffset   :: Word32
+  , msgFileioWriteReqFilename :: ByteString
+  , msgFileioWriteReqData     :: [Word8]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFileioWriteReq where
+  get = do
+    msgFileioWriteReqSequence <- getWord32le
+    msgFileioWriteReqOffset <- getWord32le
+    msgFileioWriteReqFilename <- liftM toStrict getRemainingLazyByteString
+    msgFileioWriteReqData <- whileM (liftM not isEmpty) getWord8
+    return MsgFileioWriteReq {..}
+
+  put MsgFileioWriteReq {..} = do
+    putWord32le msgFileioWriteReqSequence
+    putWord32le msgFileioWriteReqOffset
+    putByteString msgFileioWriteReqFilename
+    mapM_ putWord8 msgFileioWriteReqData
+
+msgFileioWriteResp :: Word16
+msgFileioWriteResp = 0x00AB
+
+data MsgFileioWriteResp = MsgFileioWriteResp
+  { msgFileioWriteRespSequence :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFileioWriteResp where
+  get = do
+    msgFileioWriteRespSequence <- getWord32le
+    return MsgFileioWriteResp {..}
+
+  put MsgFileioWriteResp {..} = do
+    putWord32le msgFileioWriteRespSequence
diff --git a/src/SwiftNav/SBP/Flash.hs b/src/SwiftNav/SBP/Flash.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Flash.hs
@@ -0,0 +1,184 @@
+module SwiftNav.SBP.Flash where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgFlashProgram :: Word16
+msgFlashProgram = 0x00E6
+
+data MsgFlashProgram = MsgFlashProgram
+  { msgFlashProgramTarget     :: Word8
+  , msgFlashProgramAddrStart  :: [Word8]
+  , msgFlashProgramAddrLen    :: Word8
+  , msgFlashProgramData       :: [Word8]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFlashProgram where
+  get = do
+    msgFlashProgramTarget <- getWord8
+    msgFlashProgramAddrStart <- replicateM 3 getWord8
+    msgFlashProgramAddrLen <- getWord8
+    msgFlashProgramData <- whileM (liftM not isEmpty) getWord8
+    return MsgFlashProgram {..}
+
+  put MsgFlashProgram {..} = do
+    putWord8 msgFlashProgramTarget
+    mapM_ putWord8 msgFlashProgramAddrStart
+    putWord8 msgFlashProgramAddrLen
+    mapM_ putWord8 msgFlashProgramData
+
+msgFlashDone :: Word16
+msgFlashDone = 0x00E0
+
+data MsgFlashDone = MsgFlashDone
+  { msgFlashDoneResponse :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFlashDone where
+  get = do
+    msgFlashDoneResponse <- getWord8
+    return MsgFlashDone {..}
+
+  put MsgFlashDone {..} = do
+    putWord8 msgFlashDoneResponse
+
+msgFlashReadReq :: Word16
+msgFlashReadReq = 0x00E7
+
+data MsgFlashReadReq = MsgFlashReadReq
+  { msgFlashReadReqTarget     :: Word8
+  , msgFlashReadReqAddrStart  :: [Word8]
+  , msgFlashReadReqAddrLen    :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFlashReadReq where
+  get = do
+    msgFlashReadReqTarget <- getWord8
+    msgFlashReadReqAddrStart <- replicateM 3 getWord8
+    msgFlashReadReqAddrLen <- getWord8
+    return MsgFlashReadReq {..}
+
+  put MsgFlashReadReq {..} = do
+    putWord8 msgFlashReadReqTarget
+    mapM_ putWord8 msgFlashReadReqAddrStart
+    putWord8 msgFlashReadReqAddrLen
+
+msgFlashReadResp :: Word16
+msgFlashReadResp = 0x00E1
+
+data MsgFlashReadResp = MsgFlashReadResp
+  { msgFlashReadRespTarget     :: Word8
+  , msgFlashReadRespAddrStart  :: [Word8]
+  , msgFlashReadRespAddrLen    :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFlashReadResp where
+  get = do
+    msgFlashReadRespTarget <- getWord8
+    msgFlashReadRespAddrStart <- replicateM 3 getWord8
+    msgFlashReadRespAddrLen <- getWord8
+    return MsgFlashReadResp {..}
+
+  put MsgFlashReadResp {..} = do
+    putWord8 msgFlashReadRespTarget
+    mapM_ putWord8 msgFlashReadRespAddrStart
+    putWord8 msgFlashReadRespAddrLen
+
+msgFlashErase :: Word16
+msgFlashErase = 0x00E2
+
+data MsgFlashErase = MsgFlashErase
+  { msgFlashEraseTarget     :: Word8
+  , msgFlashEraseSectorNum  :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgFlashErase where
+  get = do
+    msgFlashEraseTarget <- getWord8
+    msgFlashEraseSectorNum <- getWord32le
+    return MsgFlashErase {..}
+
+  put MsgFlashErase {..} = do
+    putWord8 msgFlashEraseTarget
+    putWord32le msgFlashEraseSectorNum
+
+msgStmFlashLockSector :: Word16
+msgStmFlashLockSector = 0x00E3
+
+data MsgStmFlashLockSector = MsgStmFlashLockSector
+  { msgStmFlashLockSectorSector :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgStmFlashLockSector where
+  get = do
+    msgStmFlashLockSectorSector <- getWord32le
+    return MsgStmFlashLockSector {..}
+
+  put MsgStmFlashLockSector {..} = do
+    putWord32le msgStmFlashLockSectorSector
+
+msgStmFlashUnlockSector :: Word16
+msgStmFlashUnlockSector = 0x00E4
+
+data MsgStmFlashUnlockSector = MsgStmFlashUnlockSector
+  { msgStmFlashUnlockSectorSector :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgStmFlashUnlockSector where
+  get = do
+    msgStmFlashUnlockSectorSector <- getWord32le
+    return MsgStmFlashUnlockSector {..}
+
+  put MsgStmFlashUnlockSector {..} = do
+    putWord32le msgStmFlashUnlockSectorSector
+
+msgStmUniqueIdReq :: Word16
+msgStmUniqueIdReq = 0x00E8
+
+data MsgStmUniqueIdReq = MsgStmUniqueIdReq
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgStmUniqueIdReq where
+  get =
+    return MsgStmUniqueIdReq
+
+  put MsgStmUniqueIdReq =
+    return ()
+
+msgStmUniqueIdResp :: Word16
+msgStmUniqueIdResp = 0x00E5
+
+data MsgStmUniqueIdResp = MsgStmUniqueIdResp
+  { msgStmUniqueIdRespStmId  :: [Word8]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgStmUniqueIdResp where
+  get = do
+    msgStmUniqueIdRespStmId <- replicateM 12 getWord8
+    return MsgStmUniqueIdResp {..}
+
+  put MsgStmUniqueIdResp {..} = do
+    mapM_ putWord8 msgStmUniqueIdRespStmId
+
+msgM25FlashWriteStatus :: Word16
+msgM25FlashWriteStatus = 0x00F3
+
+data MsgM25FlashWriteStatus = MsgM25FlashWriteStatus
+  { msgM25FlashWriteStatusStatus :: [Word8]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgM25FlashWriteStatus where
+  get = do
+    msgM25FlashWriteStatusStatus <- replicateM 1 getWord8
+    return MsgM25FlashWriteStatus {..}
+
+  put MsgM25FlashWriteStatus {..} = do
+    mapM_ putWord8 msgM25FlashWriteStatusStatus
diff --git a/src/SwiftNav/SBP/Logging.hs b/src/SwiftNav/SBP/Logging.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Logging.hs
@@ -0,0 +1,42 @@
+module SwiftNav.SBP.Logging where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgPrint :: Word16
+msgPrint = 0x0010
+
+data MsgPrint = MsgPrint
+  { msgPrintText :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgPrint where
+  get = do
+    msgPrintText <- liftM toStrict getRemainingLazyByteString
+    return MsgPrint {..}
+
+  put MsgPrint {..} = do
+    putByteString msgPrintText
+
+msgTweet :: Word16
+msgTweet = 0x0012
+
+data MsgTweet = MsgTweet
+  { msgTweetTweet :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgTweet where
+  get = do
+    msgTweetTweet <- getByteString 140
+    return MsgTweet {..}
+
+  put MsgTweet {..} = do
+    putByteString msgTweetTweet
diff --git a/src/SwiftNav/SBP/Navigation.hs b/src/SwiftNav/SBP/Navigation.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Navigation.hs
@@ -0,0 +1,273 @@
+module SwiftNav.SBP.Navigation where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgGpsTime :: Word16
+msgGpsTime = 0x0100
+
+data MsgGpsTime = MsgGpsTime
+  { msgGpsTimeWn    :: Word16
+  , msgGpsTimeTow   :: Word32
+  , msgGpsTimeNs    :: Int32
+  , msgGpsTimeFlags :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgGpsTime where
+  get = do
+    msgGpsTimeWn <- getWord16le
+    msgGpsTimeTow <- getWord32le
+    msgGpsTimeNs <- liftM fromIntegral getWord32le
+    msgGpsTimeFlags <- getWord8
+    return MsgGpsTime {..}
+
+  put MsgGpsTime {..} = do
+    putWord16le msgGpsTimeWn
+    putWord32le msgGpsTimeTow
+    putWord32le $ fromIntegral msgGpsTimeNs
+    putWord8 msgGpsTimeFlags
+
+msgDops :: Word16
+msgDops = 0x0206
+
+data MsgDops = MsgDops
+  { msgDopsTow  :: Word32
+  , msgDopsGdop :: Word16
+  , msgDopsPdop :: Word16
+  , msgDopsTdop :: Word16
+  , msgDopsHdop :: Word16
+  , msgDopsVdop :: Word16
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgDops where
+  get = do
+    msgDopsTow <- getWord32le
+    msgDopsGdop <- getWord16le
+    msgDopsPdop <- getWord16le
+    msgDopsTdop <- getWord16le
+    msgDopsHdop <- getWord16le
+    msgDopsVdop <- getWord16le
+    return MsgDops {..}
+
+  put MsgDops {..} = do
+    putWord32le msgDopsTow
+    putWord16le msgDopsGdop
+    putWord16le msgDopsPdop
+    putWord16le msgDopsTdop
+    putWord16le msgDopsHdop
+    putWord16le msgDopsVdop
+
+msgPosEcef :: Word16
+msgPosEcef = 0x0200
+
+data MsgPosEcef = MsgPosEcef
+  { msgPosEcefTow      :: Word32
+  , msgPosEcefX        :: Double
+  , msgPosEcefY        :: Double
+  , msgPosEcefZ        :: Double
+  , msgPosEcefAccuracy :: Word16
+  , msgPosEcefNSats    :: Word8
+  , msgPosEcefFlags    :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgPosEcef where
+  get = do
+    msgPosEcefTow <- getWord32le
+    msgPosEcefX <- getFloat64le
+    msgPosEcefY <- getFloat64le
+    msgPosEcefZ <- getFloat64le
+    msgPosEcefAccuracy <- getWord16le
+    msgPosEcefNSats <- getWord8
+    msgPosEcefFlags <- getWord8
+    return MsgPosEcef {..}
+
+  put MsgPosEcef {..} = do
+    putWord32le msgPosEcefTow
+    putFloat64le msgPosEcefX
+    putFloat64le msgPosEcefY
+    putFloat64le msgPosEcefZ
+    putWord16le msgPosEcefAccuracy
+    putWord8 msgPosEcefNSats
+    putWord8 msgPosEcefFlags
+
+msgPosLlh :: Word16
+msgPosLlh = 0x0201
+
+data MsgPosLlh = MsgPosLlh
+  { msgPosLlhTow        :: Word32
+  , msgPosLlhLat        :: Double
+  , msgPosLlhLon        :: Double
+  , msgPosLlhHeight     :: Double
+  , msgPosLlhHAccuracy  :: Word16
+  , msgPosLlhVAccuracy  :: Word16
+  , msgPosLlhNSats      :: Word8
+  , msgPosLlhFlags      :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgPosLlh where
+  get = do
+    msgPosLlhTow <- getWord32le
+    msgPosLlhLat <- getFloat64le
+    msgPosLlhLon <- getFloat64le
+    msgPosLlhHeight <- getFloat64le
+    msgPosLlhHAccuracy <- getWord16le
+    msgPosLlhVAccuracy <- getWord16le
+    msgPosLlhNSats <- getWord8
+    msgPosLlhFlags <- getWord8
+    return MsgPosLlh {..}
+
+  put MsgPosLlh {..} = do
+    putWord32le msgPosLlhTow
+    putFloat64le msgPosLlhLat
+    putFloat64le msgPosLlhLon
+    putFloat64le msgPosLlhHeight
+    putWord16le msgPosLlhHAccuracy
+    putWord16le msgPosLlhVAccuracy
+    putWord8 msgPosLlhNSats
+    putWord8 msgPosLlhFlags
+
+msgBaselineEcef :: Word16
+msgBaselineEcef = 0x0202
+
+data MsgBaselineEcef = MsgBaselineEcef
+  { msgBaselineEcefTow      :: Word32
+  , msgBaselineEcefX        :: Int32
+  , msgBaselineEcefY        :: Int32
+  , msgBaselineEcefZ        :: Int32
+  , msgBaselineEcefAccuracy :: Word16
+  , msgBaselineEcefNSats    :: Word8
+  , msgBaselineEcefFlags    :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgBaselineEcef where
+  get = do
+    msgBaselineEcefTow <- getWord32le
+    msgBaselineEcefX <- liftM fromIntegral getWord32le
+    msgBaselineEcefY <- liftM fromIntegral getWord32le
+    msgBaselineEcefZ <- liftM fromIntegral getWord32le
+    msgBaselineEcefAccuracy <- getWord16le
+    msgBaselineEcefNSats <- getWord8
+    msgBaselineEcefFlags <- getWord8
+    return MsgBaselineEcef {..}
+
+  put MsgBaselineEcef {..} = do
+    putWord32le msgBaselineEcefTow
+    putWord32le $ fromIntegral msgBaselineEcefX
+    putWord32le $ fromIntegral msgBaselineEcefY
+    putWord32le $ fromIntegral msgBaselineEcefZ
+    putWord16le msgBaselineEcefAccuracy
+    putWord8 msgBaselineEcefNSats
+    putWord8 msgBaselineEcefFlags
+
+msgBaselineNed :: Word16
+msgBaselineNed = 0x0203
+
+data MsgBaselineNed = MsgBaselineNed
+  { msgBaselineNedTow        :: Word32
+  , msgBaselineNedN          :: Int32
+  , msgBaselineNedE          :: Int32
+  , msgBaselineNedD          :: Int32
+  , msgBaselineNedHAccuracy  :: Word16
+  , msgBaselineNedVAccuracy  :: Word16
+  , msgBaselineNedNSats      :: Word8
+  , msgBaselineNedFlags      :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgBaselineNed where
+  get = do
+    msgBaselineNedTow <- getWord32le
+    msgBaselineNedN <- liftM fromIntegral getWord32le
+    msgBaselineNedE <- liftM fromIntegral getWord32le
+    msgBaselineNedD <- liftM fromIntegral getWord32le
+    msgBaselineNedHAccuracy <- getWord16le
+    msgBaselineNedVAccuracy <- getWord16le
+    msgBaselineNedNSats <- getWord8
+    msgBaselineNedFlags <- getWord8
+    return MsgBaselineNed {..}
+
+  put MsgBaselineNed {..} = do
+    putWord32le msgBaselineNedTow
+    putWord32le $ fromIntegral msgBaselineNedN
+    putWord32le $ fromIntegral msgBaselineNedE
+    putWord32le $ fromIntegral msgBaselineNedD
+    putWord16le msgBaselineNedHAccuracy
+    putWord16le msgBaselineNedVAccuracy
+    putWord8 msgBaselineNedNSats
+    putWord8 msgBaselineNedFlags
+
+msgVelEcef :: Word16
+msgVelEcef = 0x0204
+
+data MsgVelEcef = MsgVelEcef
+  { msgVelEcefTow      :: Word32
+  , msgVelEcefX        :: Int32
+  , msgVelEcefY        :: Int32
+  , msgVelEcefZ        :: Int32
+  , msgVelEcefAccuracy :: Word16
+  , msgVelEcefNSats    :: Word8
+  , msgVelEcefFlags    :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgVelEcef where
+  get = do
+    msgVelEcefTow <- getWord32le
+    msgVelEcefX <- liftM fromIntegral getWord32le
+    msgVelEcefY <- liftM fromIntegral getWord32le
+    msgVelEcefZ <- liftM fromIntegral getWord32le
+    msgVelEcefAccuracy <- getWord16le
+    msgVelEcefNSats <- getWord8
+    msgVelEcefFlags <- getWord8
+    return MsgVelEcef {..}
+
+  put MsgVelEcef {..} = do
+    putWord32le msgVelEcefTow
+    putWord32le $ fromIntegral msgVelEcefX
+    putWord32le $ fromIntegral msgVelEcefY
+    putWord32le $ fromIntegral msgVelEcefZ
+    putWord16le msgVelEcefAccuracy
+    putWord8 msgVelEcefNSats
+    putWord8 msgVelEcefFlags
+
+msgVelNed :: Word16
+msgVelNed = 0x0205
+
+data MsgVelNed = MsgVelNed
+  { msgVelNedTow        :: Word32
+  , msgVelNedN          :: Int32
+  , msgVelNedE          :: Int32
+  , msgVelNedD          :: Int32
+  , msgVelNedHAccuracy  :: Word16
+  , msgVelNedVAccuracy  :: Word16
+  , msgVelNedNSats      :: Word8
+  , msgVelNedFlags      :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgVelNed where
+  get = do
+    msgVelNedTow <- getWord32le
+    msgVelNedN <- liftM fromIntegral getWord32le
+    msgVelNedE <- liftM fromIntegral getWord32le
+    msgVelNedD <- liftM fromIntegral getWord32le
+    msgVelNedHAccuracy <- getWord16le
+    msgVelNedVAccuracy <- getWord16le
+    msgVelNedNSats <- getWord8
+    msgVelNedFlags <- getWord8
+    return MsgVelNed {..}
+
+  put MsgVelNed {..} = do
+    putWord32le msgVelNedTow
+    putWord32le $ fromIntegral msgVelNedN
+    putWord32le $ fromIntegral msgVelNedE
+    putWord32le $ fromIntegral msgVelNedD
+    putWord16le msgVelNedHAccuracy
+    putWord16le msgVelNedVAccuracy
+    putWord8 msgVelNedNSats
+    putWord8 msgVelNedFlags
diff --git a/src/SwiftNav/SBP/Observation.hs b/src/SwiftNav/SBP/Observation.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Observation.hs
@@ -0,0 +1,444 @@
+module SwiftNav.SBP.Observation where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+data ObsGPSTime = ObsGPSTime
+  { obsGPSTimeTow :: Word32
+  , obsGPSTimeWn  :: Word16
+  } deriving ( Show, Read, Eq )
+
+instance Binary ObsGPSTime where
+  get = do
+    obsGPSTimeTow <- getWord32le
+    obsGPSTimeWn <- getWord16le
+    return ObsGPSTime {..}
+
+  put ObsGPSTime {..} = do
+    putWord32le obsGPSTimeTow
+    putWord16le obsGPSTimeWn
+
+data CarrierPhase = CarrierPhase
+  { carrierPhaseI :: Int32
+  , carrierPhaseF :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary CarrierPhase where
+  get = do
+    carrierPhaseI <- liftM fromIntegral getWord32le
+    carrierPhaseF <- getWord8
+    return CarrierPhase {..}
+
+  put CarrierPhase {..} = do
+    putWord32le $ fromIntegral carrierPhaseI
+    putWord8 carrierPhaseF
+
+data ObservationHeader = ObservationHeader
+  { observationHeaderT     :: ObsGPSTime
+  , observationHeaderNObs  :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary ObservationHeader where
+  get = do
+    observationHeaderT <- get
+    observationHeaderNObs <- getWord8
+    return ObservationHeader {..}
+
+  put ObservationHeader {..} = do
+    put observationHeaderT
+    putWord8 observationHeaderNObs
+
+data PackedObsContent = PackedObsContent
+  { packedObsContentP    :: Word32
+  , packedObsContentL    :: CarrierPhase
+  , packedObsContentCn0  :: Word8
+  , packedObsContentLock :: Word16
+  , packedObsContentSid  :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary PackedObsContent where
+  get = do
+    packedObsContentP <- getWord32le
+    packedObsContentL <- get
+    packedObsContentCn0 <- getWord8
+    packedObsContentLock <- getWord16le
+    packedObsContentSid <- getWord32le
+    return PackedObsContent {..}
+
+  put PackedObsContent {..} = do
+    putWord32le packedObsContentP
+    put packedObsContentL
+    putWord8 packedObsContentCn0
+    putWord16le packedObsContentLock
+    putWord32le packedObsContentSid
+
+msgObs :: Word16
+msgObs = 0x0043
+
+data MsgObs = MsgObs
+  { msgObsHeader :: ObservationHeader
+  , msgObsObs    :: [PackedObsContent]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgObs where
+  get = do
+    msgObsHeader <- get
+    msgObsObs <- whileM (liftM not isEmpty) get
+    return MsgObs {..}
+
+  put MsgObs {..} = do
+    put msgObsHeader
+    mapM_ put msgObsObs
+
+msgBasePos :: Word16
+msgBasePos = 0x0044
+
+data MsgBasePos = MsgBasePos
+  { msgBasePosLat    :: Double
+  , msgBasePosLon    :: Double
+  , msgBasePosHeight :: Double
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgBasePos where
+  get = do
+    msgBasePosLat <- getFloat64le
+    msgBasePosLon <- getFloat64le
+    msgBasePosHeight <- getFloat64le
+    return MsgBasePos {..}
+
+  put MsgBasePos {..} = do
+    putFloat64le msgBasePosLat
+    putFloat64le msgBasePosLon
+    putFloat64le msgBasePosHeight
+
+msgEphemeris :: Word16
+msgEphemeris = 0x0047
+
+data MsgEphemeris = MsgEphemeris
+  { msgEphemerisTgd      :: Double
+  , msgEphemerisCRs      :: Double
+  , msgEphemerisCRc      :: Double
+  , msgEphemerisCUc      :: Double
+  , msgEphemerisCUs      :: Double
+  , msgEphemerisCIc      :: Double
+  , msgEphemerisCIs      :: Double
+  , msgEphemerisDn       :: Double
+  , msgEphemerisM0       :: Double
+  , msgEphemerisEcc      :: Double
+  , msgEphemerisSqrta    :: Double
+  , msgEphemerisOmega0   :: Double
+  , msgEphemerisOmegadot :: Double
+  , msgEphemerisW        :: Double
+  , msgEphemerisInc      :: Double
+  , msgEphemerisIncDot   :: Double
+  , msgEphemerisAf0      :: Double
+  , msgEphemerisAf1      :: Double
+  , msgEphemerisAf2      :: Double
+  , msgEphemerisToeTow   :: Double
+  , msgEphemerisToeWn    :: Word16
+  , msgEphemerisTocTow   :: Double
+  , msgEphemerisTocWn    :: Word16
+  , msgEphemerisValid    :: Word8
+  , msgEphemerisHealthy  :: Word8
+  , msgEphemerisSid      :: Word32
+  , msgEphemerisIode     :: Word8
+  , msgEphemerisIodc     :: Word16
+  , msgEphemerisReserved :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgEphemeris where
+  get = do
+    msgEphemerisTgd <- getFloat64le
+    msgEphemerisCRs <- getFloat64le
+    msgEphemerisCRc <- getFloat64le
+    msgEphemerisCUc <- getFloat64le
+    msgEphemerisCUs <- getFloat64le
+    msgEphemerisCIc <- getFloat64le
+    msgEphemerisCIs <- getFloat64le
+    msgEphemerisDn <- getFloat64le
+    msgEphemerisM0 <- getFloat64le
+    msgEphemerisEcc <- getFloat64le
+    msgEphemerisSqrta <- getFloat64le
+    msgEphemerisOmega0 <- getFloat64le
+    msgEphemerisOmegadot <- getFloat64le
+    msgEphemerisW <- getFloat64le
+    msgEphemerisInc <- getFloat64le
+    msgEphemerisIncDot <- getFloat64le
+    msgEphemerisAf0 <- getFloat64le
+    msgEphemerisAf1 <- getFloat64le
+    msgEphemerisAf2 <- getFloat64le
+    msgEphemerisToeTow <- getFloat64le
+    msgEphemerisToeWn <- getWord16le
+    msgEphemerisTocTow <- getFloat64le
+    msgEphemerisTocWn <- getWord16le
+    msgEphemerisValid <- getWord8
+    msgEphemerisHealthy <- getWord8
+    msgEphemerisSid <- getWord32le
+    msgEphemerisIode <- getWord8
+    msgEphemerisIodc <- getWord16le
+    msgEphemerisReserved <- getWord32le
+    return MsgEphemeris {..}
+
+  put MsgEphemeris {..} = do
+    putFloat64le msgEphemerisTgd
+    putFloat64le msgEphemerisCRs
+    putFloat64le msgEphemerisCRc
+    putFloat64le msgEphemerisCUc
+    putFloat64le msgEphemerisCUs
+    putFloat64le msgEphemerisCIc
+    putFloat64le msgEphemerisCIs
+    putFloat64le msgEphemerisDn
+    putFloat64le msgEphemerisM0
+    putFloat64le msgEphemerisEcc
+    putFloat64le msgEphemerisSqrta
+    putFloat64le msgEphemerisOmega0
+    putFloat64le msgEphemerisOmegadot
+    putFloat64le msgEphemerisW
+    putFloat64le msgEphemerisInc
+    putFloat64le msgEphemerisIncDot
+    putFloat64le msgEphemerisAf0
+    putFloat64le msgEphemerisAf1
+    putFloat64le msgEphemerisAf2
+    putFloat64le msgEphemerisToeTow
+    putWord16le msgEphemerisToeWn
+    putFloat64le msgEphemerisTocTow
+    putWord16le msgEphemerisTocWn
+    putWord8 msgEphemerisValid
+    putWord8 msgEphemerisHealthy
+    putWord32le msgEphemerisSid
+    putWord8 msgEphemerisIode
+    putWord16le msgEphemerisIodc
+    putWord32le msgEphemerisReserved
+
+msgEphemerisDepA :: Word16
+msgEphemerisDepA = 0x001A
+
+data MsgEphemerisDepA = MsgEphemerisDepA
+  { msgEphemerisDepATgd      :: Double
+  , msgEphemerisDepACRs      :: Double
+  , msgEphemerisDepACRc      :: Double
+  , msgEphemerisDepACUc      :: Double
+  , msgEphemerisDepACUs      :: Double
+  , msgEphemerisDepACIc      :: Double
+  , msgEphemerisDepACIs      :: Double
+  , msgEphemerisDepADn       :: Double
+  , msgEphemerisDepAM0       :: Double
+  , msgEphemerisDepAEcc      :: Double
+  , msgEphemerisDepASqrta    :: Double
+  , msgEphemerisDepAOmega0   :: Double
+  , msgEphemerisDepAOmegadot :: Double
+  , msgEphemerisDepAW        :: Double
+  , msgEphemerisDepAInc      :: Double
+  , msgEphemerisDepAIncDot   :: Double
+  , msgEphemerisDepAAf0      :: Double
+  , msgEphemerisDepAAf1      :: Double
+  , msgEphemerisDepAAf2      :: Double
+  , msgEphemerisDepAToeTow   :: Double
+  , msgEphemerisDepAToeWn    :: Word16
+  , msgEphemerisDepATocTow   :: Double
+  , msgEphemerisDepATocWn    :: Word16
+  , msgEphemerisDepAValid    :: Word8
+  , msgEphemerisDepAHealthy  :: Word8
+  , msgEphemerisDepAPrn      :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgEphemerisDepA where
+  get = do
+    msgEphemerisDepATgd <- getFloat64le
+    msgEphemerisDepACRs <- getFloat64le
+    msgEphemerisDepACRc <- getFloat64le
+    msgEphemerisDepACUc <- getFloat64le
+    msgEphemerisDepACUs <- getFloat64le
+    msgEphemerisDepACIc <- getFloat64le
+    msgEphemerisDepACIs <- getFloat64le
+    msgEphemerisDepADn <- getFloat64le
+    msgEphemerisDepAM0 <- getFloat64le
+    msgEphemerisDepAEcc <- getFloat64le
+    msgEphemerisDepASqrta <- getFloat64le
+    msgEphemerisDepAOmega0 <- getFloat64le
+    msgEphemerisDepAOmegadot <- getFloat64le
+    msgEphemerisDepAW <- getFloat64le
+    msgEphemerisDepAInc <- getFloat64le
+    msgEphemerisDepAIncDot <- getFloat64le
+    msgEphemerisDepAAf0 <- getFloat64le
+    msgEphemerisDepAAf1 <- getFloat64le
+    msgEphemerisDepAAf2 <- getFloat64le
+    msgEphemerisDepAToeTow <- getFloat64le
+    msgEphemerisDepAToeWn <- getWord16le
+    msgEphemerisDepATocTow <- getFloat64le
+    msgEphemerisDepATocWn <- getWord16le
+    msgEphemerisDepAValid <- getWord8
+    msgEphemerisDepAHealthy <- getWord8
+    msgEphemerisDepAPrn <- getWord8
+    return MsgEphemerisDepA {..}
+
+  put MsgEphemerisDepA {..} = do
+    putFloat64le msgEphemerisDepATgd
+    putFloat64le msgEphemerisDepACRs
+    putFloat64le msgEphemerisDepACRc
+    putFloat64le msgEphemerisDepACUc
+    putFloat64le msgEphemerisDepACUs
+    putFloat64le msgEphemerisDepACIc
+    putFloat64le msgEphemerisDepACIs
+    putFloat64le msgEphemerisDepADn
+    putFloat64le msgEphemerisDepAM0
+    putFloat64le msgEphemerisDepAEcc
+    putFloat64le msgEphemerisDepASqrta
+    putFloat64le msgEphemerisDepAOmega0
+    putFloat64le msgEphemerisDepAOmegadot
+    putFloat64le msgEphemerisDepAW
+    putFloat64le msgEphemerisDepAInc
+    putFloat64le msgEphemerisDepAIncDot
+    putFloat64le msgEphemerisDepAAf0
+    putFloat64le msgEphemerisDepAAf1
+    putFloat64le msgEphemerisDepAAf2
+    putFloat64le msgEphemerisDepAToeTow
+    putWord16le msgEphemerisDepAToeWn
+    putFloat64le msgEphemerisDepATocTow
+    putWord16le msgEphemerisDepATocWn
+    putWord8 msgEphemerisDepAValid
+    putWord8 msgEphemerisDepAHealthy
+    putWord8 msgEphemerisDepAPrn
+
+msgEphemerisDepB :: Word16
+msgEphemerisDepB = 0x0046
+
+data MsgEphemerisDepB = MsgEphemerisDepB
+  { msgEphemerisDepBTgd      :: Double
+  , msgEphemerisDepBCRs      :: Double
+  , msgEphemerisDepBCRc      :: Double
+  , msgEphemerisDepBCUc      :: Double
+  , msgEphemerisDepBCUs      :: Double
+  , msgEphemerisDepBCIc      :: Double
+  , msgEphemerisDepBCIs      :: Double
+  , msgEphemerisDepBDn       :: Double
+  , msgEphemerisDepBM0       :: Double
+  , msgEphemerisDepBEcc      :: Double
+  , msgEphemerisDepBSqrta    :: Double
+  , msgEphemerisDepBOmega0   :: Double
+  , msgEphemerisDepBOmegadot :: Double
+  , msgEphemerisDepBW        :: Double
+  , msgEphemerisDepBInc      :: Double
+  , msgEphemerisDepBIncDot   :: Double
+  , msgEphemerisDepBAf0      :: Double
+  , msgEphemerisDepBAf1      :: Double
+  , msgEphemerisDepBAf2      :: Double
+  , msgEphemerisDepBToeTow   :: Double
+  , msgEphemerisDepBToeWn    :: Word16
+  , msgEphemerisDepBTocTow   :: Double
+  , msgEphemerisDepBTocWn    :: Word16
+  , msgEphemerisDepBValid    :: Word8
+  , msgEphemerisDepBHealthy  :: Word8
+  , msgEphemerisDepBPrn      :: Word8
+  , msgEphemerisDepBIode     :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgEphemerisDepB where
+  get = do
+    msgEphemerisDepBTgd <- getFloat64le
+    msgEphemerisDepBCRs <- getFloat64le
+    msgEphemerisDepBCRc <- getFloat64le
+    msgEphemerisDepBCUc <- getFloat64le
+    msgEphemerisDepBCUs <- getFloat64le
+    msgEphemerisDepBCIc <- getFloat64le
+    msgEphemerisDepBCIs <- getFloat64le
+    msgEphemerisDepBDn <- getFloat64le
+    msgEphemerisDepBM0 <- getFloat64le
+    msgEphemerisDepBEcc <- getFloat64le
+    msgEphemerisDepBSqrta <- getFloat64le
+    msgEphemerisDepBOmega0 <- getFloat64le
+    msgEphemerisDepBOmegadot <- getFloat64le
+    msgEphemerisDepBW <- getFloat64le
+    msgEphemerisDepBInc <- getFloat64le
+    msgEphemerisDepBIncDot <- getFloat64le
+    msgEphemerisDepBAf0 <- getFloat64le
+    msgEphemerisDepBAf1 <- getFloat64le
+    msgEphemerisDepBAf2 <- getFloat64le
+    msgEphemerisDepBToeTow <- getFloat64le
+    msgEphemerisDepBToeWn <- getWord16le
+    msgEphemerisDepBTocTow <- getFloat64le
+    msgEphemerisDepBTocWn <- getWord16le
+    msgEphemerisDepBValid <- getWord8
+    msgEphemerisDepBHealthy <- getWord8
+    msgEphemerisDepBPrn <- getWord8
+    msgEphemerisDepBIode <- getWord8
+    return MsgEphemerisDepB {..}
+
+  put MsgEphemerisDepB {..} = do
+    putFloat64le msgEphemerisDepBTgd
+    putFloat64le msgEphemerisDepBCRs
+    putFloat64le msgEphemerisDepBCRc
+    putFloat64le msgEphemerisDepBCUc
+    putFloat64le msgEphemerisDepBCUs
+    putFloat64le msgEphemerisDepBCIc
+    putFloat64le msgEphemerisDepBCIs
+    putFloat64le msgEphemerisDepBDn
+    putFloat64le msgEphemerisDepBM0
+    putFloat64le msgEphemerisDepBEcc
+    putFloat64le msgEphemerisDepBSqrta
+    putFloat64le msgEphemerisDepBOmega0
+    putFloat64le msgEphemerisDepBOmegadot
+    putFloat64le msgEphemerisDepBW
+    putFloat64le msgEphemerisDepBInc
+    putFloat64le msgEphemerisDepBIncDot
+    putFloat64le msgEphemerisDepBAf0
+    putFloat64le msgEphemerisDepBAf1
+    putFloat64le msgEphemerisDepBAf2
+    putFloat64le msgEphemerisDepBToeTow
+    putWord16le msgEphemerisDepBToeWn
+    putFloat64le msgEphemerisDepBTocTow
+    putWord16le msgEphemerisDepBTocWn
+    putWord8 msgEphemerisDepBValid
+    putWord8 msgEphemerisDepBHealthy
+    putWord8 msgEphemerisDepBPrn
+    putWord8 msgEphemerisDepBIode
+
+data PackedObsContentDepA = PackedObsContentDepA
+  { packedObsContentDepAP    :: Word32
+  , packedObsContentDepAL    :: CarrierPhase
+  , packedObsContentDepACn0  :: Word8
+  , packedObsContentDepALock :: Word16
+  , packedObsContentDepAPrn  :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary PackedObsContentDepA where
+  get = do
+    packedObsContentDepAP <- getWord32le
+    packedObsContentDepAL <- get
+    packedObsContentDepACn0 <- getWord8
+    packedObsContentDepALock <- getWord16le
+    packedObsContentDepAPrn <- getWord8
+    return PackedObsContentDepA {..}
+
+  put PackedObsContentDepA {..} = do
+    putWord32le packedObsContentDepAP
+    put packedObsContentDepAL
+    putWord8 packedObsContentDepACn0
+    putWord16le packedObsContentDepALock
+    putWord8 packedObsContentDepAPrn
+
+msgObsDepA :: Word16
+msgObsDepA = 0x0045
+
+data MsgObsDepA = MsgObsDepA
+  { msgObsDepAHeader :: ObservationHeader
+  , msgObsDepAObs    :: [PackedObsContentDepA]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgObsDepA where
+  get = do
+    msgObsDepAHeader <- get
+    msgObsDepAObs <- whileM (liftM not isEmpty) get
+    return MsgObsDepA {..}
+
+  put MsgObsDepA {..} = do
+    put msgObsDepAHeader
+    mapM_ put msgObsDepAObs
diff --git a/src/SwiftNav/SBP/Piksi.hs b/src/SwiftNav/SBP/Piksi.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Piksi.hs
@@ -0,0 +1,231 @@
+module SwiftNav.SBP.Piksi where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgAlmanac :: Word16
+msgAlmanac = 0x0069
+
+data MsgAlmanac = MsgAlmanac
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgAlmanac where
+  get =
+    return MsgAlmanac
+
+  put MsgAlmanac =
+    return ()
+
+msgSetTime :: Word16
+msgSetTime = 0x0068
+
+data MsgSetTime = MsgSetTime
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgSetTime where
+  get =
+    return MsgSetTime
+
+  put MsgSetTime =
+    return ()
+
+msgReset :: Word16
+msgReset = 0x00B2
+
+data MsgReset = MsgReset
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgReset where
+  get =
+    return MsgReset
+
+  put MsgReset =
+    return ()
+
+msgCwResults :: Word16
+msgCwResults = 0x00C0
+
+data MsgCwResults = MsgCwResults
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgCwResults where
+  get =
+    return MsgCwResults
+
+  put MsgCwResults =
+    return ()
+
+msgCwStart :: Word16
+msgCwStart = 0x00C1
+
+data MsgCwStart = MsgCwStart
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgCwStart where
+  get =
+    return MsgCwStart
+
+  put MsgCwStart =
+    return ()
+
+msgResetFilters :: Word16
+msgResetFilters = 0x0022
+
+data MsgResetFilters = MsgResetFilters
+  { msgResetFiltersFilter :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgResetFilters where
+  get = do
+    msgResetFiltersFilter <- getWord8
+    return MsgResetFilters {..}
+
+  put MsgResetFilters {..} = do
+    putWord8 msgResetFiltersFilter
+
+msgInitBase :: Word16
+msgInitBase = 0x0023
+
+data MsgInitBase = MsgInitBase
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgInitBase where
+  get =
+    return MsgInitBase
+
+  put MsgInitBase =
+    return ()
+
+msgThreadState :: Word16
+msgThreadState = 0x0017
+
+data MsgThreadState = MsgThreadState
+  { msgThreadStateName       :: ByteString
+  , msgThreadStateCpu        :: Word16
+  , msgThreadStateStackFree  :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgThreadState where
+  get = do
+    msgThreadStateName <- getByteString 20
+    msgThreadStateCpu <- getWord16le
+    msgThreadStateStackFree <- getWord32le
+    return MsgThreadState {..}
+
+  put MsgThreadState {..} = do
+    putByteString msgThreadStateName
+    putWord16le msgThreadStateCpu
+    putWord32le msgThreadStateStackFree
+
+data UARTChannel = UARTChannel
+  { uARTChannelTxThroughput    :: Float
+  , uARTChannelRxThroughput    :: Float
+  , uARTChannelCrcErrorCount   :: Word16
+  , uARTChannelIoErrorCount    :: Word16
+  , uARTChannelTxBufferLevel   :: Word8
+  , uARTChannelRxBufferLevel   :: Word8
+  } deriving ( Show, Read, Eq )
+
+instance Binary UARTChannel where
+  get = do
+    uARTChannelTxThroughput <- getFloat32le
+    uARTChannelRxThroughput <- getFloat32le
+    uARTChannelCrcErrorCount <- getWord16le
+    uARTChannelIoErrorCount <- getWord16le
+    uARTChannelTxBufferLevel <- getWord8
+    uARTChannelRxBufferLevel <- getWord8
+    return UARTChannel {..}
+
+  put UARTChannel {..} = do
+    putFloat32le uARTChannelTxThroughput
+    putFloat32le uARTChannelRxThroughput
+    putWord16le uARTChannelCrcErrorCount
+    putWord16le uARTChannelIoErrorCount
+    putWord8 uARTChannelTxBufferLevel
+    putWord8 uARTChannelRxBufferLevel
+
+data Latency = Latency
+  { latencyAvg     :: Int32
+  , latencyLmin    :: Int32
+  , latencyLmax    :: Int32
+  , latencyCurrent :: Int32
+  } deriving ( Show, Read, Eq )
+
+instance Binary Latency where
+  get = do
+    latencyAvg <- liftM fromIntegral getWord32le
+    latencyLmin <- liftM fromIntegral getWord32le
+    latencyLmax <- liftM fromIntegral getWord32le
+    latencyCurrent <- liftM fromIntegral getWord32le
+    return Latency {..}
+
+  put Latency {..} = do
+    putWord32le $ fromIntegral latencyAvg
+    putWord32le $ fromIntegral latencyLmin
+    putWord32le $ fromIntegral latencyLmax
+    putWord32le $ fromIntegral latencyCurrent
+
+msgUartState :: Word16
+msgUartState = 0x0018
+
+data MsgUartState = MsgUartState
+  { msgUartStateUartA     :: UARTChannel
+  , msgUartStateUartB     :: UARTChannel
+  , msgUartStateUartFtdi  :: UARTChannel
+  , msgUartStateLatency   :: Latency
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgUartState where
+  get = do
+    msgUartStateUartA <- get
+    msgUartStateUartB <- get
+    msgUartStateUartFtdi <- get
+    msgUartStateLatency <- get
+    return MsgUartState {..}
+
+  put MsgUartState {..} = do
+    put msgUartStateUartA
+    put msgUartStateUartB
+    put msgUartStateUartFtdi
+    put msgUartStateLatency
+
+msgIarState :: Word16
+msgIarState = 0x0019
+
+data MsgIarState = MsgIarState
+  { msgIarStateNumHyps  :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgIarState where
+  get = do
+    msgIarStateNumHyps <- getWord32le
+    return MsgIarState {..}
+
+  put MsgIarState {..} = do
+    putWord32le msgIarStateNumHyps
+
+msgMaskSatellite :: Word16
+msgMaskSatellite = 0x001B
+
+data MsgMaskSatellite = MsgMaskSatellite
+  { msgMaskSatelliteMask :: Word8
+  , msgMaskSatelliteSid  :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgMaskSatellite where
+  get = do
+    msgMaskSatelliteMask <- getWord8
+    msgMaskSatelliteSid <- getWord32le
+    return MsgMaskSatellite {..}
+
+  put MsgMaskSatellite {..} = do
+    putWord8 msgMaskSatelliteMask
+    putWord32le msgMaskSatelliteSid
diff --git a/src/SwiftNav/SBP/Settings.hs b/src/SwiftNav/SBP/Settings.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Settings.hs
@@ -0,0 +1,116 @@
+module SwiftNav.SBP.Settings where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgSettingsSave :: Word16
+msgSettingsSave = 0x00A1
+
+data MsgSettingsSave = MsgSettingsSave
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgSettingsSave where
+  get =
+    return MsgSettingsSave
+
+  put MsgSettingsSave =
+    return ()
+
+msgSettingsWrite :: Word16
+msgSettingsWrite = 0x00A0
+
+data MsgSettingsWrite = MsgSettingsWrite
+  { msgSettingsWriteSetting :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgSettingsWrite where
+  get = do
+    msgSettingsWriteSetting <- liftM toStrict getRemainingLazyByteString
+    return MsgSettingsWrite {..}
+
+  put MsgSettingsWrite {..} = do
+    putByteString msgSettingsWriteSetting
+
+msgSettingsReadReq :: Word16
+msgSettingsReadReq = 0x00A4
+
+data MsgSettingsReadReq = MsgSettingsReadReq
+  { msgSettingsReadReqSetting :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgSettingsReadReq where
+  get = do
+    msgSettingsReadReqSetting <- liftM toStrict getRemainingLazyByteString
+    return MsgSettingsReadReq {..}
+
+  put MsgSettingsReadReq {..} = do
+    putByteString msgSettingsReadReqSetting
+
+msgSettingsReadResp :: Word16
+msgSettingsReadResp = 0x00A5
+
+data MsgSettingsReadResp = MsgSettingsReadResp
+  { msgSettingsReadRespSetting :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgSettingsReadResp where
+  get = do
+    msgSettingsReadRespSetting <- liftM toStrict getRemainingLazyByteString
+    return MsgSettingsReadResp {..}
+
+  put MsgSettingsReadResp {..} = do
+    putByteString msgSettingsReadRespSetting
+
+msgSettingsReadByIndexReq :: Word16
+msgSettingsReadByIndexReq = 0x00A2
+
+data MsgSettingsReadByIndexReq = MsgSettingsReadByIndexReq
+  { msgSettingsReadByIndexReqIndex :: Word16
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgSettingsReadByIndexReq where
+  get = do
+    msgSettingsReadByIndexReqIndex <- getWord16le
+    return MsgSettingsReadByIndexReq {..}
+
+  put MsgSettingsReadByIndexReq {..} = do
+    putWord16le msgSettingsReadByIndexReqIndex
+
+msgSettingsReadByIndexResp :: Word16
+msgSettingsReadByIndexResp = 0x00A7
+
+data MsgSettingsReadByIndexResp = MsgSettingsReadByIndexResp
+  { msgSettingsReadByIndexRespIndex   :: Word16
+  , msgSettingsReadByIndexRespSetting :: ByteString
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgSettingsReadByIndexResp where
+  get = do
+    msgSettingsReadByIndexRespIndex <- getWord16le
+    msgSettingsReadByIndexRespSetting <- liftM toStrict getRemainingLazyByteString
+    return MsgSettingsReadByIndexResp {..}
+
+  put MsgSettingsReadByIndexResp {..} = do
+    putWord16le msgSettingsReadByIndexRespIndex
+    putByteString msgSettingsReadByIndexRespSetting
+
+msgSettingsReadByIndexDone :: Word16
+msgSettingsReadByIndexDone = 0x00A6
+
+data MsgSettingsReadByIndexDone = MsgSettingsReadByIndexDone
+  deriving ( Show, Read, Eq )
+
+instance Binary MsgSettingsReadByIndexDone where
+  get =
+    return MsgSettingsReadByIndexDone
+
+  put MsgSettingsReadByIndexDone =
+    return ()
diff --git a/src/SwiftNav/SBP/System.hs b/src/SwiftNav/SBP/System.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/System.hs
@@ -0,0 +1,42 @@
+module SwiftNav.SBP.System where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+msgStartup :: Word16
+msgStartup = 0xFF00
+
+data MsgStartup = MsgStartup
+  { msgStartupReserved :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgStartup where
+  get = do
+    msgStartupReserved <- getWord32le
+    return MsgStartup {..}
+
+  put MsgStartup {..} = do
+    putWord32le msgStartupReserved
+
+msgHeartbeat :: Word16
+msgHeartbeat = 0xFFFF
+
+data MsgHeartbeat = MsgHeartbeat
+  { msgHeartbeatFlags :: Word32
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgHeartbeat where
+  get = do
+    msgHeartbeatFlags <- getWord32le
+    return MsgHeartbeat {..}
+
+  put MsgHeartbeat {..} = do
+    putWord32le msgHeartbeatFlags
diff --git a/src/SwiftNav/SBP/Tracking.hs b/src/SwiftNav/SBP/Tracking.hs
new file mode 100644
--- /dev/null
+++ b/src/SwiftNav/SBP/Tracking.hs
@@ -0,0 +1,114 @@
+module SwiftNav.SBP.Tracking where
+
+import Control.Monad
+import Control.Monad.Loops
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.IEEE754
+import Data.Binary.Put
+import Data.ByteString
+import Data.ByteString.Lazy hiding ( ByteString )
+import Data.Int
+import Data.Word
+
+data TrackingChannelState = TrackingChannelState
+  { trackingChannelStateState :: Word8
+  , trackingChannelStateSid   :: Word32
+  , trackingChannelStateCn0   :: Float
+  } deriving ( Show, Read, Eq )
+
+instance Binary TrackingChannelState where
+  get = do
+    trackingChannelStateState <- getWord8
+    trackingChannelStateSid <- getWord32le
+    trackingChannelStateCn0 <- getFloat32le
+    return TrackingChannelState {..}
+
+  put TrackingChannelState {..} = do
+    putWord8 trackingChannelStateState
+    putWord32le trackingChannelStateSid
+    putFloat32le trackingChannelStateCn0
+
+msgTrackingState :: Word16
+msgTrackingState = 0x0013
+
+data MsgTrackingState = MsgTrackingState
+  { msgTrackingStateStates :: [TrackingChannelState]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgTrackingState where
+  get = do
+    msgTrackingStateStates <- whileM (liftM not isEmpty) get
+    return MsgTrackingState {..}
+
+  put MsgTrackingState {..} = do
+    mapM_ put msgTrackingStateStates
+
+data TrackingChannelCorrelation = TrackingChannelCorrelation
+  { trackingChannelCorrelationI :: Int32
+  , trackingChannelCorrelationQ :: Int32
+  } deriving ( Show, Read, Eq )
+
+instance Binary TrackingChannelCorrelation where
+  get = do
+    trackingChannelCorrelationI <- liftM fromIntegral getWord32le
+    trackingChannelCorrelationQ <- liftM fromIntegral getWord32le
+    return TrackingChannelCorrelation {..}
+
+  put TrackingChannelCorrelation {..} = do
+    putWord32le $ fromIntegral trackingChannelCorrelationI
+    putWord32le $ fromIntegral trackingChannelCorrelationQ
+
+msgTrackingIq :: Word16
+msgTrackingIq = 0x001C
+
+data MsgTrackingIq = MsgTrackingIq
+  { msgTrackingIqChannel :: Word8
+  , msgTrackingIqSid     :: Word32
+  , msgTrackingIqCorrs   :: [TrackingChannelCorrelation]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgTrackingIq where
+  get = do
+    msgTrackingIqChannel <- getWord8
+    msgTrackingIqSid <- getWord32le
+    msgTrackingIqCorrs <- replicateM 3 get
+    return MsgTrackingIq {..}
+
+  put MsgTrackingIq {..} = do
+    putWord8 msgTrackingIqChannel
+    putWord32le msgTrackingIqSid
+    mapM_ put msgTrackingIqCorrs
+
+data TrackingChannelStateDepA = TrackingChannelStateDepA
+  { trackingChannelStateDepAState :: Word8
+  , trackingChannelStateDepAPrn   :: Word8
+  , trackingChannelStateDepACn0   :: Float
+  } deriving ( Show, Read, Eq )
+
+instance Binary TrackingChannelStateDepA where
+  get = do
+    trackingChannelStateDepAState <- getWord8
+    trackingChannelStateDepAPrn <- getWord8
+    trackingChannelStateDepACn0 <- getFloat32le
+    return TrackingChannelStateDepA {..}
+
+  put TrackingChannelStateDepA {..} = do
+    putWord8 trackingChannelStateDepAState
+    putWord8 trackingChannelStateDepAPrn
+    putFloat32le trackingChannelStateDepACn0
+
+msgTrackingStateDepA :: Word16
+msgTrackingStateDepA = 0x0016
+
+data MsgTrackingStateDepA = MsgTrackingStateDepA
+  { msgTrackingStateDepAStates :: [TrackingChannelStateDepA]
+  } deriving ( Show, Read, Eq )
+
+instance Binary MsgTrackingStateDepA where
+  get = do
+    msgTrackingStateDepAStates <- whileM (liftM not isEmpty) get
+    return MsgTrackingStateDepA {..}
+
+  put MsgTrackingStateDepA {..} = do
+    mapM_ put msgTrackingStateDepAStates
