packages feed

gnss-converters 0.1.11 → 0.1.12

raw patch · 8 files changed

+235/−165 lines, 8 filesdep +exceptionsdep +monad-controldep +mtldep −criteriondep ~base

Dependencies added: exceptions, monad-control, mtl, transformers-base

Dependencies removed: criterion

Dependency ranges changed: base

Files

− bench/Bench.hs
@@ -1,7 +0,0 @@-import BasicPrelude-import Criterion.Main--main :: IO ()-main = defaultMain-  [-  ]
gnss-converters.cabal view
@@ -1,5 +1,5 @@ name:                  gnss-converters-version:               0.1.11+version:               0.1.12 synopsis:              GNSS Converters. description:           Haskell bindings for GNSS converters. homepage:              http://github.com/swift-nav/gnss-converters@@ -18,16 +18,22 @@ library   hs-source-dirs:      src   exposed-modules:     Data.RTCM3.SBP+                     , Data.RTCM3.SBP.Types                      , SwiftNav.SBP.RTCM3   ghc-options:         -Wall   build-depends:       base >= 4.7 && < 5                      , basic-prelude                      , conduit-extra+                     , exceptions                      , extra                      , lens+                     , monad-control+                     , mtl+                     , resourcet                      , rtcm                      , sbp                      , time+                     , transformers-base                      , unordered-containers   default-language:    Haskell2010   default-extensions:  NoImplicitPrelude@@ -64,32 +70,20 @@   hs-source-dirs:      test   main-is:             Test.hs   other-modules:       Test.Data.RTCM3.SBP-  build-depends:       base+  build-depends:       HUnit-approx+                     , base                      , basic-prelude                      , binary-conduit                      , bytestring                      , conduit                      , conduit-extra                      , gnss-converters-                     , HUnit-approx                      , lens                      , resourcet                      , sbp                      , tasty                      , tasty-hunit                      , unordered-containers-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall-  default-language:    Haskell2010-  default-extensions:  NoImplicitPrelude--benchmark bench-  type:                exitcode-stdio-1.0-  hs-source-dirs:      bench-  main-is:             Bench.hs-  build-depends:       base-                     , basic-prelude-                     , criterion-                     , gnss-converters   ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall   default-language:    Haskell2010   default-extensions:  NoImplicitPrelude
main/RTCM32SBP.hs view
@@ -8,20 +8,22 @@ -- -- RTCM3 to SBP tool. -import BasicPrelude-import Control.Monad.Trans.Resource-import Data.Conduit-import Data.Conduit.Binary-import qualified Data.Conduit.List as CL-import Data.Conduit.Serialization.Binary-import Data.RTCM3.SBP-import System.IO+import           BasicPrelude+import           Control.Monad.Trans.Resource+import           Data.Conduit+import           Data.Conduit.Binary+import qualified Data.Conduit.List                 as CL+import           Data.Conduit.Serialization.Binary+import           Data.RTCM3.SBP+import           Data.RTCM3.SBP.Types+import           System.IO  main :: IO ()-main = runResourceT $-  sourceHandle stdin   =$=-  conduitDecode        =$=-  CL.mapMaybeM convert =$=-  CL.concat            =$=-  conduitEncode        $$-  sinkHandle stdout+main = do+  s <- liftIO $ newStore+  runResourceT $ runConvertT s $+    sourceHandle stdin    =$=+    conduitDecode         =$=+    CL.concatMapM convert =$=+    conduitEncode         $$+    sinkHandle stdout
src/Data/RTCM3/SBP.hs view
@@ -12,15 +12,17 @@  module Data.RTCM3.SBP where -import BasicPrelude-import Control.Lens-import Data.Bits-import qualified Data.HashMap.Strict as H-import Data.List.Extra hiding (concat, map)-import Data.Time-import Data.Word-import Data.RTCM3-import SwiftNav.SBP+import           BasicPrelude+import           Control.Lens+import           Data.Bits+import qualified Data.HashMap.Strict  as M+import           Data.IORef+import           Data.List.Extra      hiding (concat, map)+import           Data.RTCM3+import           Data.RTCM3.SBP.Types+import           Data.Time+import           Data.Word+import           SwiftNav.SBP   --------------------------------------------------------------------------------@@ -104,17 +106,20 @@  -- | Map L2 codes to SBP GnssSignal codes ---l2codeToSBPSignalCode :: H.HashMap Word8 Word8-l2codeToSBPSignalCode = H.fromList [(0, l2CMSidCode), (1, l2PSidCode)]+l2codeToSBPSignalCode :: HashMap Word8 Word8+l2codeToSBPSignalCode = M.fromList+  [ (codeIndicator_L2C,  l2CMSidCode)+  , (codeIndicator_L2PD, l2PSidCode)+  ]  -- | Maximum number of packed observations to allow in a single SBP message. -- maxObsPerMessage :: Int-maxObsPerMessage = floor $ (maxPayloadSize - headerSize) / packedObsSize+maxObsPerMessage = (maxPayloadSize - headerSize) `div` packedObsSize   where     maxPayloadSize = 255     headerSize     = 7-    packedObsSize  = 16.0+    packedObsSize  = 16   --------------------------------------------------------------------------------@@ -124,12 +129,12 @@ fromEcefVal :: Int64 -> Double fromEcefVal x = fromIntegral x / 10000 -toGPSTime :: MonadIO m => GpsObservationHeader -> m ObsGPSTime+toGPSTime :: MonadStore e m => GpsObservationHeader -> m ObsGPSTime toGPSTime hdr = do-  today <- utctDay <$> liftIO getCurrentTime+  wn <- view storeWn >>= liftIO . readIORef   return ObsGPSTime     { _obsGPSTime_tow = hdr ^. gpsObservationHeader_tow-    , _obsGPSTime_wn  = fromIntegral $ div (diffDays today (fromGregorian 1980 1 6)) 7+    , _obsGPSTime_wn  = wn     }  -- | Construct metric pseudorange (meters!) from L1 RTCM observation.@@ -169,7 +174,7 @@     lm = p + phaseRangeRes * fromIntegral (l1 ^. gpsL1Observation_carrierMinusCode)     l = lm / (lightSpeedMS / l1frequency)     li :: Int32-    li = floor (l)+    li = floor l     lf :: Word8     lf = truncate ((l - fromIntegral li) * q32Width) @@ -181,7 +186,7 @@        -> GpsL2Observation        -> GpsL2ExtObservation        -> CarrierPhase-toL_L2 l1 l1e l2 l2e = CarrierPhase+toL_L2 l1 l1e l2 _l2e = CarrierPhase   { _carrierPhase_i = fromIntegral li   , _carrierPhase_f = fromIntegral lf   } where@@ -192,7 +197,7 @@     lm = p + phaseRangeRes * fromIntegral (l2 ^. gpsL2Observation_carrierMinusCode)     l = lm / (lightSpeedMS / l2frequency)     li :: Int32-    li = floor (l)+    li = floor l     lf :: Word8     lf = truncate ((l - fromIntegral li) * q32Width) @@ -210,7 +215,7 @@  -- | Construct sequenced SBP observation header ---fromGpsObservationHeader :: MonadIO m+fromGpsObservationHeader :: MonadStore e m                          => Word8                -- ^ Total messages                          -> Word8                -- ^ Message in sequence                          -> GpsObservationHeader -- ^ RTCM observation header@@ -234,41 +239,52 @@ fromL1SatelliteObservation sat l1 l1e =   -- Checks GPS L1 code indicator for RTCM message 1002.   -- See DF016, pg. 3-17 of the RTCM3 spec.-  if l1 ^. gpsL1Observation_code then-    PackedObsContent-      { _packedObsContent_P    = toP_L1 l1 l1e-      , _packedObsContent_L    = toL_L1 l1 l1e-      , _packedObsContent_cn0  = toCn0_L1 l1e-      , _packedObsContent_lock = toLock_L1 l1-      , _packedObsContent_sid  = GnssSignal-        { _gnssSignal_sat      = fromIntegral $ sat - 1-        , _gnssSignal_code     = l1PSidCode-        , _gnssSignal_reserved = 0-        }+  PackedObsContent+    { _packedObsContent_P    = toP_L1 l1 l1e+    , _packedObsContent_L    = toL_L1 l1 l1e+    , _packedObsContent_cn0  = toCn0_L1 l1e+    , _packedObsContent_lock = toLock_L1 l1+    , _packedObsContent_sid  = GnssSignal+      { _gnssSignal_sat      = fromIntegral $ sat - 1+      , _gnssSignal_code     = if l1 ^. gpsL1Observation_code then l1PSidCode else l1CSidCode+      , _gnssSignal_reserved = 0       }-  else-    PackedObsContent-      { _packedObsContent_P    = toP_L1 l1 l1e-      , _packedObsContent_L    = toL_L1 l1 l1e-      , _packedObsContent_cn0  = toCn0_L1 l1e-      , _packedObsContent_lock = toLock_L1 l1-      , _packedObsContent_sid  = GnssSignal-        { _gnssSignal_sat      = fromIntegral $ sat - 1-        , _gnssSignal_code     = l1CSidCode-        , _gnssSignal_reserved = 0-        }+    }++-- | Construct an L2 SBP PackedObsContent an RTCM satellite vehicle observation+--+fromL2SatelliteObservation :: Word8                  -- ^ Satellite PRN+                           -> GpsL1Observation+                           -> GpsL1ExtObservation+                           -> GpsL2Observation+                           -> GpsL2ExtObservation+                           -> Maybe PackedObsContent+fromL2SatelliteObservation sat l1 l1e l2 l2e = do+  -- Checks GPS L2 code indicator.+  -- See DF016, pg. 3-17 of the RTCM3 spec.+  code <- M.lookup (l2 ^. gpsL2Observation_code) l2codeToSBPSignalCode+  return PackedObsContent+    { _packedObsContent_P    = toP_L2 l1 l1e l2+    , _packedObsContent_L    = toL_L2 l1 l1e l2 l2e+    , _packedObsContent_cn0  = toCn0_L2 l2e+    , _packedObsContent_lock = toLock_L2 l2+    , _packedObsContent_sid  = GnssSignal+      { _gnssSignal_sat      = fromIntegral $ sat - 1+      , _gnssSignal_code     = code+      , _gnssSignal_reserved = 0       }+    }  -- | Construct SBP GPS observation message (possibly chunked). ---chunkToMsgObs :: MonadIO m-              => Msg1004            -- ^ RTCM 1004 observation message-              -> Word8              -- ^ Total messages-              -> Word8              -- ^ Message in sequence+chunkToMsgObs :: MonadStore e m+              => GpsObservationHeader -- ^ RTCM observation header+              -> Word8                -- ^ Total messages+              -> Word8                -- ^ Message in sequence               -> [PackedObsContent]               -> m MsgObs-chunkToMsgObs m totalMsgs n packed = do-  header <- fromGpsObservationHeader totalMsgs n $ m ^. msg1004_header+chunkToMsgObs hdr totalMsgs n packed = do+  header <- fromGpsObservationHeader totalMsgs n hdr   return MsgObs     { _msgObs_header = header     , _msgObs_obs    = packed@@ -287,72 +303,63 @@  -- | Construct an L1 SBP PackedObsContent from an RTCM Msg 1002. ---fromObservation1002 :: Observation1002 -> Maybe PackedObsContent+fromObservation1002 :: Observation1002 -> [PackedObsContent] fromObservation1002 obs =   -- Only lower set of PRN numbers (1-32) are supported-  if sat > maxSats then Nothing else Just (fromL1SatelliteObservation sat l1 l1e)+  if sat > maxSats then mempty else [fromL1SatelliteObservation sat l1 l1e]     where       sat = obs ^. observation1002_sat       l1  = obs ^. observation1002_l1       l1e = obs ^. observation1002_l1e + -- | Convert an RTCM L1 1002 observation into an SBP MsgObs. ---fromMsg1002 :: MonadIO m => Msg1002 -> m MsgObs+-- This chunking takes places because the number of observations in a given 1002+-- may very well exceed the maximum SBP supported payload size of 255 bytes.+fromMsg1002 :: MonadStore e m => Msg1002 -> m [MsgObs] fromMsg1002 m = do-  header <- fromGpsObservationHeader 1 0 (m ^. msg1002_header)-  return MsgObs-    { _msgObs_header = header-    , _msgObs_obs    = mapMaybe fromObservation1002 $ m ^. msg1002_observations-    }+  let hdr       = m ^. msg1002_header+      obs       = concatMap fromObservation1002 $ m ^. msg1002_observations+      chunks    = zip [0..] $ chunksOf maxObsPerMessage obs+      totalMsgs = fromIntegral $ length chunks+  forM chunks $ uncurry $ chunkToMsgObs hdr totalMsgs  -- | Construct an L1/L2 SBP PackedObsContent from an RTCM Msg 1004. ---fromObservation1004 :: Observation1004 -> Maybe [PackedObsContent]+fromObservation1004 :: Observation1004 -> [PackedObsContent] fromObservation1004 obs =   -- Only lower set of PRN numbers (1-32) are supported-  if sat > maxSats then Nothing else mapM id [obs1, obs2]+  if sat > maxSats then mempty else+    maybe [obs1] (: [obs1]) obs2     where       sat = obs ^. observation1004_sat       l1  = obs ^. observation1004_l1       l1e = obs ^. observation1004_l1e       -- Checks GPS L1 code indicator for RTCM message 1004       -- See DF016, pg. 3-17 of the RTCM3 spec.-      obs1 = Just (fromL1SatelliteObservation sat l1 l1e)+      obs1 = fromL1SatelliteObservation sat l1 l1e       l2   = obs ^. observation1004_l2       l2e  = obs ^. observation1004_l2e-      code = l2 ^. gpsL2Observation_code       -- Checks GPS L2 code indicator.       -- See DF016, pg. 3-17 of the RTCM3 spec.-      obs2 = if H.member code l2codeToSBPSignalCode then-               Just PackedObsContent-                 { _packedObsContent_P    = toP_L2 l1 l1e l2-                 , _packedObsContent_L    = toL_L2 l1 l1e l2 l2e-                 , _packedObsContent_cn0  = toCn0_L2 l2e-                 , _packedObsContent_lock = toLock_L2 l2-                 , _packedObsContent_sid  = GnssSignal-                   { _gnssSignal_sat      = fromIntegral $ sat - 1-                   , _gnssSignal_code     = l2codeToSBPSignalCode H.! code-                   , _gnssSignal_reserved = 0-                   }-                 }-             else-               Nothing+      obs2 = fromL2SatelliteObservation sat l1 l1e l2 l2e  -- | Convert an RTCM L1+L2 1004 observation into multiple SBP MsgObs. -- -- This chunking takes places because the number of observations in a given 1004 -- may very well exceed the maximum SBP supported payload size of 255 bytes.-fromMsg1004 :: MonadIO m => Msg1004 -> m [MsgObs]+fromMsg1004 :: MonadStore e m => Msg1004 -> m [MsgObs] fromMsg1004 m = do-  let obs       = concat $ mapMaybe fromObservation1004 $ m ^. msg1004_observations+  let hdr       = m ^. msg1004_header+      obs       = concatMap fromObservation1004 $ m ^. msg1004_observations       chunks    = zip [0..] $ chunksOf maxObsPerMessage obs       totalMsgs = fromIntegral $ length chunks-  mapM (\(n, packed) -> chunkToMsgObs m totalMsgs n packed) chunks+  forM chunks $ uncurry $ chunkToMsgObs hdr totalMsgs  -- | Convert an RTCM 1005 antenna reference position message into an SBP -- MsgBasePosEcef.-fromMsg1005 :: MonadIO m => Msg1005 -> m MsgBasePosEcef+fromMsg1005 :: MonadStore e m => Msg1005 -> m MsgBasePosEcef fromMsg1005 m =   return MsgBasePosEcef     { _msgBasePosEcef_x = fromEcefVal $ m ^. msg1005_reference ^. antennaReference_ecef_x@@ -362,7 +369,7 @@  -- | Convert an RTCM 1006 antenna reference position message into an SBP -- MsgBasePosEcef.-fromMsg1006 :: MonadIO m => Msg1006 -> m MsgBasePosEcef+fromMsg1006 :: MonadStore e m => Msg1006 -> m MsgBasePosEcef fromMsg1006 m =   return MsgBasePosEcef     { _msgBasePosEcef_x = fromEcefVal $ m ^. msg1006_reference ^. antennaReference_ecef_x@@ -372,22 +379,29 @@  -- | Convert an RTCM message into possibly multiple SBP messages. ---convert :: MonadIO m => RTCM3Msg -> m (Maybe [SBPMsg])+convert :: MonadStore e m => RTCM3Msg -> m [SBPMsg] convert = \case   (RTCM3Msg1002 m _rtcm3) -> do     let sender = m ^. msg1002_header ^. gpsObservationHeader_station     m' <- fromMsg1002 m-    return $ Just [SBPMsgObs m' $ toSBP m' $ toSender sender]+    return $ flip fmap m' $ \x -> SBPMsgObs x $ toSBP x $ toSender sender   (RTCM3Msg1004 m _rtcm3) -> do     let sender = m ^. msg1004_header ^. gpsObservationHeader_station     m' <- fromMsg1004 m-    return $ Just (fmap (\x -> SBPMsgObs x $ toSBP x $ toSender sender) m')+    return $ flip fmap m' $ \x -> SBPMsgObs x $ toSBP x $ toSender sender   (RTCM3Msg1005 m _rtcm3) -> do     let sender =  m ^. msg1005_reference ^. antennaReference_station     m' <- fromMsg1005 m-    return $ Just [SBPMsgBasePosEcef m' $ toSBP m' $ toSender sender]+    return [SBPMsgBasePosEcef m' $ toSBP m' $ toSender sender]   (RTCM3Msg1006 m _rtcm3) -> do     let sender = m ^. msg1006_reference ^. antennaReference_station     m' <- fromMsg1006 m-    return $ Just [SBPMsgBasePosEcef m' $ toSBP m' $ toSender sender]-  _rtcm3Msg -> return Nothing+    return [SBPMsgBasePosEcef m' $ toSBP m' $ toSender sender]+  _rtcm3Msg -> return mempty++newStore :: IO Store+newStore = do+  day <- utctDay <$> getCurrentTime+  let wn = fromIntegral $ div (diffDays day (fromGregorian 1980 1 6)) 7+  Store <$> newIORef wn+
+ src/Data/RTCM3/SBP/Types.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE ConstraintKinds            #-}+{-# LANGUAGE FlexibleInstances          #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses      #-}+{-# LANGUAGE TemplateHaskell            #-}+{-# LANGUAGE TypeFamilies               #-}+{-# LANGUAGE UndecidableInstances       #-}++-- |+-- Module:      Data.RTCM3.SBP.Types+-- Copyright:   Copyright (C) 2015 Swift Navigation, Inc.+-- License:     LGPL-3+-- Maintainer:  Mark Fine <dev@swiftnav.com>+-- Stability:   experimental+-- Portability: portable+--+-- Types for RTCMv3 to SBP Conversions.++module Data.RTCM3.SBP.Types where++import BasicPrelude+import Control.Lens+import Control.Monad.Base+import Control.Monad.Catch+import Control.Monad.Reader+import Control.Monad.Trans.Control+import Control.Monad.Trans.Resource+import Data.IORef+import Data.Word++newtype ConvertT e m a = ConvertT { unConvertT :: ReaderT e m a }+  deriving+    ( Functor+    , Applicative+    , Monad+    , MonadIO+    , MonadTrans+    )++instance MonadThrow m => MonadThrow (ConvertT r m) where+    throwM = lift . throwM++instance MonadResource m => MonadResource (ConvertT r m) where+  liftResourceT = lift . liftResourceT++instance Monad m => MonadReader e (ConvertT e m) where+    ask     = ConvertT ask+    local f = ConvertT . local f . unConvertT+    reader  = ConvertT . reader++instance MonadTransControl (ConvertT r) where+    type StT (ConvertT r) a = StT (ReaderT r) a++    liftWith = defaultLiftWith ConvertT unConvertT+    restoreT = defaultRestoreT ConvertT++instance MonadBase b m => MonadBase b (ConvertT r m) where+    liftBase = liftBaseDefault++data Store = Store+  { _storeWn :: IORef Word16+  } deriving ( Eq )++$(makeClassy ''Store)++runConvertT :: HasStore e => e -> ConvertT e m a -> m a+runConvertT e (ConvertT m) = runReaderT m e++type MonadStore e m =+  ( MonadIO m+  , MonadReader e m+  , MonadThrow m+  , HasStore e+  )
src/SwiftNav/SBP/RTCM3.hs view
@@ -31,10 +31,10 @@  convert :: SBPMsg -> Maybe RTCM3Msg convert = \case-  (SBPMsgObs msg _sbp) -> Just $-    RTCM3Msg1002 msg' $ toRTCM3 msg' where-      msg' = fromMsgObs msg-  (SBPMsgBasePosEcef msg _sbp) -> Just $-    RTCM3Msg1005 msg' $ toRTCM3 msg' where-      msg' = fromMsgBasePosEcef msg+  (SBPMsgObs m _sbp) -> Just $+    RTCM3Msg1002 m' $ toRTCM3 m' where+      m' = fromMsgObs m+  (SBPMsgBasePosEcef m _sbp) -> Just $+    RTCM3Msg1005 m' $ toRTCM3 m' where+      m' = fromMsgBasePosEcef m   _sbpMsg -> Nothing
test/Test.hs view
@@ -1,6 +1,3 @@-{-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE OverloadedStrings #-}- -- | -- Module:      Test -- Copyright:   (c) 2016 Swift Navigation Inc.
test/Test/Data/RTCM3/SBP.hs view
@@ -1,8 +1,4 @@-{-# LANGUAGE FlexibleContexts    #-}-{-# LANGUAGE LambdaCase          #-}-{-# LANGUAGE NoImplicitPrelude   #-}-{-# LANGUAGE OverloadedStrings   #-}-{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE FlexibleContexts #-}  -- | -- Module:      Test.Data.RTCM3.SBP@@ -16,30 +12,30 @@   ( tests   ) where -import BasicPrelude-import Control.Lens-import Control.Monad.Trans.Resource-import Data.Conduit-import Data.Conduit.Binary-import Data.Conduit.Serialization.Binary-import Data.HashMap.Strict-import Data.RTCM3.SBP-import qualified Data.Conduit.List as CL-import Data.Word-import SwiftNav.SBP-import Test.HUnit.Approx-import Test.Tasty-import Test.Tasty.HUnit+import           BasicPrelude+import           Control.Lens+import           Control.Monad.Trans.Resource+import           Data.Conduit+import           Data.Conduit.Binary+import qualified Data.Conduit.List                 as CL+import           Data.Conduit.Serialization.Binary+import           Data.HashMap.Strict+import           Data.IORef+import           Data.RTCM3.SBP+import           Data.RTCM3.SBP.Types+import           Data.Word+import           SwiftNav.SBP+import           Test.HUnit.Approx+import           Test.Tasty+import           Test.Tasty.HUnit -decodeRTCMFile :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)-               => FilePath-               -> m [SBPMsg]-decodeRTCMFile filename =-  runResourceT . runConduit  $-    sourceFile filename     =$=-    conduitDecode           =$=-    CL.mapMaybeM convert    =$=-    CL.concat               $$+decodeRTCMFile :: FilePath -> IO [SBPMsg]+decodeRTCMFile filename = do+  wn <- liftIO $ newIORef 1906+  runResourceT $ runConvertT (Store wn) $ runConduit  $+    sourceFile filename   =$=+    conduitDecode         =$=+    CL.concatMapM convert $$     CL.consume  basePosition :: MsgBasePosEcef -> (Double, Double, Double)@@ -160,39 +156,39 @@         assertExpectedBase (msgs !! 5) expectedBasePos         -- Message 1         assertObsHeader (msgs !! 1) ObservationHeader {-            _observationHeader_t     = ObsGPSTime 86354000 1905+            _observationHeader_t     = ObsGPSTime 86354000 1906           , _observationHeader_n_obs = 32           }         assertMsgObsLength (msgs !! 1) 15         assertMsgObs (msgs !! 1)         -- Message 2         assertObsHeader (msgs !! 2) ObservationHeader {-            _observationHeader_t     = ObsGPSTime 86354000 1905+            _observationHeader_t     = ObsGPSTime 86354000 1906           , _observationHeader_n_obs = 33           }         assertMsgObs (msgs !! 2)         assertMsgObsLength (msgs !! 2) 1         -- Message 3         assertObsHeader (msgs !! 3) ObservationHeader {-            _observationHeader_t     = ObsGPSTime 86355000 1905+            _observationHeader_t     = ObsGPSTime 86355000 1906           , _observationHeader_n_obs = 32           }         assertMsgObsLength (msgs !! 3) 15         -- Message 4         assertObsHeader (msgs !! 4) ObservationHeader {-            _observationHeader_t     = ObsGPSTime 86355000 1905+            _observationHeader_t     = ObsGPSTime 86355000 1906           , _observationHeader_n_obs = 33           }         assertMsgObsLength (msgs !! 4) 1         -- Message 6         assertObsHeader (msgs !! 6) ObservationHeader {-            _observationHeader_t     = ObsGPSTime 86356000 1905+            _observationHeader_t     = ObsGPSTime 86356000 1906           , _observationHeader_n_obs = 32           }         assertMsgObsLength (msgs !! 6) 15         -- Message 7         assertObsHeader (msgs !! 7) ObservationHeader {-            _observationHeader_t     = ObsGPSTime 86356000 1905+            _observationHeader_t     = ObsGPSTime 86356000 1906           , _observationHeader_n_obs = 33           }         assertMsgObsLength (msgs !! 7) 1