labsat-0.0.0: src/Labsat/Parser.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Labsat.Parser where
import qualified Data.ByteString as BS
import Data.Attoparsec.ByteString
import Data.Attoparsec.ByteString.Char8 (char, decimal, double, isDigit_w8, isEndOfLine, scientific, signed)
import Labsat.Types
import Preamble hiding (takeWhile)
--------------------------------------------------------------------------------
-- Helper Parsers
--------------------------------------------------------------------------------
prompt :: Parser ByteString
prompt = string "LABSAT_V3 >"
newline :: Parser ByteString
newline = string "\r\r\n"
ok :: Parser ByteString
ok = string "OK"
err :: Parser ByteString
err = string "ERR"
colon :: Word8 -> Bool
colon = (== 58)
isETX :: Word8 -> Bool
isETX = (==03)
etx :: Parser Word8
etx = word8 03
isEscape :: Word8 -> Bool
isEscape = (== 27)
notNewline :: Word8 -> Bool
notNewline w = w /= 10 && w /= 13
parseNotNewline :: Parser Word8
parseNotNewline = satisfy notNewline
mediaDelim :: Parser ByteString
mediaDelim = string " \ESC[40G "
takeDigits :: Parser ByteString
takeDigits = takeWhile1 isDigit_w8
-- | Parse OK followed by prompt
--
okPrompt :: Parser ByteString
okPrompt = ok <* takeNewlines <* prompt
-- | Parse comma separated lists
--
commaSep :: Parser ByteString -> Parser [ByteString]
commaSep = flip sepBy' (char ',')
-- | take [\r\n]+
--
takeNewlines :: Parser ()
takeNewlines = satisfy isEndOfLine *> skipWhile isEndOfLine
-- | Parse everything up to [\r\n]+
--
parseLabsatLine :: Parser ByteString
parseLabsatLine = takeWhile1 (not . isEndOfLine) <* takeNewlines
-- | Parse lines until next Labsat prompt
--
parseLabsatLines :: Parser [ByteString]
parseLabsatLines = manyTill parseLabsatLine prompt
-- | Parse everything until next prompt (debug)
--
parseUntilPrompt :: Parser ByteString
parseUntilPrompt = BS.pack <$> manyTill anyWord8 prompt
-- | Parse everything up to and including next prompt (debug)
--
parseThroughPrompt :: Parser ByteString
parseThroughPrompt = do
x <- parseUntilPrompt
pure $ x <> "LABSAT_V3 >"
-- | Parse echoed command followed by newline(s)
--
parseCommandAck :: ByteString -> Parser ByteString
parseCommandAck cmd = string cmd <* takeNewlines
-- | Parse ANSI Color Escape Sequence
--
parseColorSeq :: Parser ByteString
parseColorSeq = do
esc <- string "\ESC["
n <- takeDigits
m <- string "m"
pure $ BS.concat [esc, n, m]
-- | Parse Labsat header
--
takeLabsatHeader :: Parser ByteString
takeLabsatHeader = takeWhile1 (not . isETX) <* etx
-- | Parse Y/N
--
parseYN :: Parser Bool
parseYN =
(string "Y" >> pure True) <|>
(string "N" >> pure False)
-- | Parse IP address
--
-- | TODO make sure its a valid IP address
parseIP :: Parser ByteString
parseIP = do
octet1 <- takeDigits
void $ char '.'
octet2 <- takeDigits
void $ char '.'
octet3 <- takeDigits
void $ char '.'
octet4 <- takeDigits
pure $ intercalate "." [octet1, octet2, octet3, octet4]
-- | Parse Duration HH:MM:SS
--
parseDuration :: Parser ByteString
parseDuration = do
hh <- takeWhile isDigit_w8
void $ char ':'
mm <- takeWhile isDigit_w8
void $ char ':'
ss <- takeWhile isDigit_w8
pure $ intercalate ":" [hh, mm, ss]
-- | Parse In Use Error
--
parseInUse :: Parser ByteString
parseInUse = takeLabsatHeader *> string "in use with " *> parseIP <* takeNewlines
-- | Parse First Labsat Message
--
parseFirstLabsatMsg :: Parser ByteString
parseFirstLabsatMsg = takeLabsatHeader <* takeNewlines *> prompt
--------------------------------------------------------------------------------
-- HELP Parsers
--------------------------------------------------------------------------------
parseHelp :: Parser HelpCommands
parseHelp =
HelpCommands <$> ("Current commands are : " <* takeNewlines *> parseLabsatLines)
--------------------------------------------------------------------------------
-- MEDIA Parsers
--------------------------------------------------------------------------------
parseMediaFile :: Parser Media
parseMediaFile = File <$> (BS.pack <$> manyTill parseNotNewline mediaDelim) <*> parseDuration <* newline
parseMediaDir :: Parser Media
parseMediaDir = Dir <$> (BS.pack <$> manyTill parseNotNewline newline)
parseMedia :: Parser Media
parseMedia = parseMediaFile <|> parseMediaDir
parseMediaList :: Parser MediaList
parseMediaList = MediaList <$> manyTill parseMedia (newline *> prompt)
parseMediaChdir :: Parser ByteString
parseMediaChdir = takeWhile notNewline <* takeNewlines <* ok <* takeNewlines <* prompt
--------------------------------------------------------------------------------
-- PLAY Parsers
--------------------------------------------------------------------------------
parsePlayFile :: ByteString -> Parser ByteString
parsePlayFile = string
parsePlayIdle :: Parser PlayStatus
parsePlayIdle = do
void "PLAY:IDLE"
pure PlayIdle
parsePlaying :: Parser PlayStatus
parsePlaying =
Playing <$> parseFile <*> parseDuration'
where
parseFile = "PLAY:/mnt/sata/" *> takeWhile1 (not . colon)
parseDuration' = ":DUR:" *> parseDuration
parsePlay :: ByteString -> Parser ByteString
parsePlay f = string f <* takeWhile notNewline <* takeNewlines <* prompt
parsePlayStatus :: Parser PlayStatus
parsePlayStatus = (parsePlayIdle <|> parsePlaying) <* takeNewlines <* prompt
--------------------------------------------------------------------------------
-- REC Parsers
--------------------------------------------------------------------------------
parseRec :: Parser ByteString
parseRec = takeWhile notNewline <* takeNewlines <* prompt
parseRecordIdle :: Parser RecordStatus
parseRecordIdle = do
void "REC:IDLE"
pure RecordIdle
parseRecording :: Parser RecordStatus
parseRecording =
Recording <$> parseFile <*> parseDuration'
where
parseFile = "REC:/mnt/sata/" *> takeWhile1 (not . colon)
parseDuration' = ":DUR:" *> parseDuration
parseRecordStatus :: Parser RecordStatus
parseRecordStatus = (parseRecordIdle <|> parseRecording) <* takeNewlines <* prompt
--------------------------------------------------------------------------------
-- TYPE Parser
--------------------------------------------------------------------------------
parseInfo :: Parser Info
parseInfo = Info <$> parseLabsatLines
--------------------------------------------------------------------------------
-- MON Parsers
--------------------------------------------------------------------------------
-- | Constellation parser.
--
parseConstellation :: Parser Constellation
parseConstellation =
(string "GPS" >> pure GPS) <|>
(string "GLO" >> pure GLO) <|>
(string "BDS" >> pure BDS) <|>
(string "GAL" >> pure GAL)
-- | Satellite CNO parser.
--
parseSatelliteCNO :: Parser [SatelliteCNO]
parseSatelliteCNO = do
res <- option [] (commaSep takeDigits <* takeNewlines)
pure $ uncurry SatelliteCNO <$> extractSatelliteCNOPairs res
where
extractSatelliteCNOPairs :: [ByteString] -> [(ByteString, ByteString)]
extractSatelliteCNOPairs [] = []
extractSatelliteCNOPairs [_] = error "Corrupt Satellite CNO information"
extractSatelliteCNOPairs (x:y:xs) = (x,y) : extractSatelliteCNOPairs xs
-- | Constellation CNO parser.
--
parseConstellationCNO :: Parser ConstellationCNO
parseConstellationCNO =
ConstellationCNO <$>
parseConstellation <* " " <*>
takeDigits <* takeNewlines <*>
parseSatelliteCNO
-- | 'MON:LOC' parser
--
-- | TODO handle case when record/replay is turned off and ',0.000000,,,,,' is returned
--
parseMonLoc :: Parser Location
parseMonLoc = Location <$> double <* "," <*> parseLocation <* "," <*> parseLocation <* "," <*> parseLocation
where
parseLocation = (,) <$> double <* "," <*> parseDirection
parseDirection = takeWhile1 (inClass "MNSEW")
-- | 'MON:SAT' parser
--
parseMonSat :: Parser [ConstellationCNO]
parseMonSat = manyTill parseConstellationCNO prompt
--------------------------------------------------------------------------------
-- MUTE Parsers
--------------------------------------------------------------------------------
-- | 'MUTE' parser
--
parseMute :: Parser MuteConf
parseMute =
MuteConf <$>
pure Nothing <*>
parseChannelMute "CH1" <*>
parseChannelMute "CH2" <*>
parseChannelMute "CH3" <* ok <* takeNewlines <* prompt
where
parseChannelId ch = string $ "OK:MUTE:"++ch++":"
parseChannelMute ch = option Nothing (parseChannelId ch *> (Just <$> parseYN) <* optWhitespace)
optWhitespace = option "" " \r\r\n"
--------------------------------------------------------------------------------
-- ATTN Parsers
--------------------------------------------------------------------------------
-- | 'ATTN' parser
--
parseAttn :: Parser AttnConf
parseAttn =
AttnConf <$>
pure Nothing <*>
parseChannelAttn "CH1" <*>
parseChannelAttn "CH2" <*>
parseChannelAttn "CH3" <* ok <* takeNewlines <* prompt
where
parseChannelId ch = string $ "OK:ATTN:"++ch++":"
parseChannelAttn ch = option Nothing (parseChannelId ch *> (Just <$> signed decimal) <* " " <* takeNewlines)
--------------------------------------------------------------------------------
-- CONF Parsers
--------------------------------------------------------------------------------
-- | 'CONF:SETUP:CAN:CHX:BAUD' parser
--
parseCANBaud :: Parser Double
parseCANBaud = "baud value is " *> double <* " " <* takeNewlines <* okPrompt
parseQuantization :: Parser Quantization
parseQuantization = (string "QUA-1" >> pure QUA1) <|>
(string "QUA-2" >> pure QUA2) <|>
(string "QUA-3" >> pure QUA3)
parseBandwidth :: Parser Bandwidth
parseBandwidth = (string "BW-10" >> pure BW_10) <|>
(string "BW-30" >> pure BW_30) <|>
(string "BW-56" >> pure BW_56)
-- | 'CONF:CONS' parsers
--
-- | WARNING: The output of CONF:CONS relies on a leading space in some cases
--
-- | TODO: cleanup
parseConsPreset :: Parser ConstellationPresetConf
parseConsPreset =
ConstellationPresetConf <$> parseQua <*> parseBW <*> parsePresets <* takeNewlines <* okPrompt
where parseQua = parseQuantization <* ", "
parseBW = parseBandwidth <* ", "
parsePresets = do
void $ string "Available ch(" <* takeDigits <* string ") "
presets <- takeDigits `sepBy'` string ", " <* string " "
pure $ freqPresetLookup <$> presets
parseConsFreq :: Parser ConstellationFreqConf
parseConsFreq =
ConstellationFreqConf <$> parseQua <*> parseBW <*> parseFreqs <* takeNewlines <* okPrompt
where optHeader = option "" ("TELNET_CONF " <* takeNewlines)
parseQua = optHeader *> " " *> parseQuantization <* ", "
parseBW = parseBandwidth <* ", "
parseFreqs = do
void $ string "Available ch(" <* takeDigits <* string ") "
scientific `sepBy'` string ", " <* string " "