{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} -- | -- Module: SBP2JNMEA -- Copyright: Copyright (C) 2020-2021 Swift Navigation, Inc. -- License: MIT -- Contact: https://support.swiftnav.com -- Stability: experimental -- Portability: portable -- -- SBP to NMEA tool - reads SBP binary from stdin and sends NMEA -- to stdout. import BasicPrelude hiding (map) import Control.Lens import Control.Monad.Trans.Resource import Data.Bits import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as BS8 import Data.Conduit import Data.Conduit.Binary import Data.Conduit.List import Data.Conduit.Serialization.Binary import Data.Time import Data.Time.Clock.POSIX import Data.Word import SwiftNav.SBP import System.IO import Text.Printf -- | NMEA time. -- timestamp :: MsgPosLlh -> ByteString timestamp posLlh = BS8.pack $ printf "%s.%03u" times mods where epochs = 315964800 offsets = 18 (tows, mods) = divMod (posLlh ^. msgPosLlh_tow) 1000 seconds = epochs - offsets + tows time = posixSecondsToUTCTime $ fromIntegral seconds times = formatTime defaultTimeLocale "%H%M%S" time -- | NMEA latitude. -- lat :: MsgPosLlh -> ByteString lat posLlh = BS8.pack $ printf "%02u%010.7f" i (60 * f) where (i, f) = properFraction (abs $ posLlh ^. msgPosLlh_lat) :: (Word16, Double) -- | NMEA latitude direction. -- latDir :: MsgPosLlh -> ByteString latDir posLlh = bool "S" "N" $ posLlh ^. msgPosLlh_lat > 0 -- | NMEA longitude. -- lon :: MsgPosLlh -> ByteString lon posLlh = BS8.pack $ printf "%03u%010.7f" i (60 * f) where (i, f) = properFraction (abs $ posLlh ^. msgPosLlh_lon) :: (Word16, Double) -- | NMEA longitude direction. -- lonDir :: MsgPosLlh -> ByteString lonDir posLlh = bool "W" "E" $ posLlh ^. msgPosLlh_lon > 0 -- | NMEA quality. -- quality :: MsgPosLlh -> ByteString quality posLlh = case posLlh ^. msgPosLlh_flags .&. 0x7 of 0 -> "0" 1 -> "1" 2 -> "2" 3 -> "5" 4 -> "4" _ -> "0" -- | NMEA number of satellites. -- satellites :: MsgPosLlh -> ByteString satellites posLlh = BS8.pack $ printf "%02u" $ posLlh ^. msgPosLlh_n_sats -- | NMEA altitude. -- height :: MsgPosLlh -> ByteString height posLlh = BS8.pack $ printf "%.2f" $ posLlh ^. msgPosLlh_height -- | NMEA sentence for GGA for GPS. -- sentence :: MsgPosLlh -> ByteString sentence posLlh = BS8.intercalate "," [ "GPGGA" , timestamp posLlh , lat posLlh , latDir posLlh , lon posLlh , lonDir posLlh , quality posLlh , satellites posLlh , "0.0" , height posLlh , "M" , "0.0" , "M" , mempty , mempty ] -- | NMEA checksum. -- checksum :: ByteString -> ByteString checksum s = BS8.pack $ printf "%02x" $ BS.foldl' xor 0 s -- | NMEA GGA for GPS. -- gpgga :: MsgPosLlh -> ByteString gpgga posLlh = "$" <> s <> "*" <> c <> "\r\n" where s = sentence posLlh c = checksum s -- | Encode a SBPMsg to a line of JSON. encodeLine :: SBPMsg -> ByteString encodeLine (SBPMsgPosLlh posLlh _msg) = gpgga posLlh encodeLine _ = mempty main :: IO () main = do hSetBuffering stdin NoBuffering hSetBuffering stdout NoBuffering runResourceT $ sourceHandle stdin =$= conduitDecode =$= map encodeLine $$ sinkHandle stdout