use core::str;
use nom::{
bytes::complete::{take, take_until},
character::complete::char,
combinator::map_res,
sequence::preceded,
IResult,
};
use cfg_if::cfg_if;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{sentences::*, Error, SentenceType};
pub const SENTENCE_MAX_LEN: usize = 102;
pub const TEXT_PARAMETER_MAX_LEN: usize = 64;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NmeaSentence<'a> {
pub talker_id: &'a str,
pub message_id: SentenceType,
pub data: &'a str,
pub checksum: u8,
}
impl<'a> NmeaSentence<'a> {
pub fn calc_checksum(&self) -> u8 {
checksum(
self.talker_id
.as_bytes()
.iter()
.chain(self.message_id.as_str().as_bytes())
.chain(&[b','])
.chain(self.data.as_bytes()),
)
}
}
pub(crate) fn checksum<'a, I: Iterator<Item = &'a u8>>(bytes: I) -> u8 {
bytes.fold(0, |c, x| c ^ *x)
}
fn parse_hex(data: &str) -> Result<u8, &'static str> {
u8::from_str_radix(data, 16).map_err(|_| "Failed to parse checksum as hex number")
}
fn parse_checksum(i: &str) -> IResult<&str, u8> {
map_res(preceded(char('*'), take(2usize)), parse_hex)(i)
}
fn parse_sentence_type(i: &str) -> IResult<&str, SentenceType> {
map_res(take(3usize), |sentence_type: &str| {
SentenceType::try_from(sentence_type).map_err(|_| "Unknown sentence type")
})(i)
}
fn do_parse_nmea_sentence(i: &str) -> IResult<&str, NmeaSentence> {
let (i, talker_id) = preceded(char('$'), take(2usize))(i)?;
let (i, message_id) = parse_sentence_type(i)?;
let (i, _) = char(',')(i)?;
let (i, data) = take_until("*")(i)?;
let (i, checksum) = parse_checksum(i)?;
Ok((
i,
NmeaSentence {
talker_id,
message_id,
data,
checksum,
},
))
}
pub fn parse_nmea_sentence(sentence: &str) -> core::result::Result<NmeaSentence, Error<'_>> {
if sentence.len() > SENTENCE_MAX_LEN {
Err(Error::SentenceLength(sentence.len()))
} else {
Ok(do_parse_nmea_sentence(sentence)?.1)
}
}
#[derive(Debug, PartialEq)]
pub enum ParseResult {
AAM(AamData),
ALM(AlmData),
BOD(BodData),
BWC(BwcData),
BWW(BwwData),
DBK(DbkData),
GBS(GbsData),
GGA(GgaData),
GLL(GllData),
GNS(GnsData),
GSA(GsaData),
GSV(GsvData),
HDT(HdtData),
MDA(MdaData),
MTW(MtwData),
MWV(MwvData),
RMC(RmcData),
TXT(TxtData),
VHW(VhwData),
VTG(VtgData),
ZDA(ZdaData),
ZFO(ZfoData),
ZTG(ZtgData),
PGRMZ(PgrmzData),
Unsupported(SentenceType),
}
impl From<&ParseResult> for SentenceType {
fn from(parse_result: &ParseResult) -> Self {
match parse_result {
ParseResult::AAM(_) => SentenceType::AAM,
ParseResult::ALM(_) => SentenceType::ALM,
ParseResult::BOD(_) => SentenceType::BOD,
ParseResult::BWC(_) => SentenceType::BWC,
ParseResult::BWW(_) => SentenceType::BWW,
ParseResult::DBK(_) => SentenceType::DBK,
ParseResult::GBS(_) => SentenceType::GBS,
ParseResult::GGA(_) => SentenceType::GGA,
ParseResult::GLL(_) => SentenceType::GLL,
ParseResult::GNS(_) => SentenceType::GNS,
ParseResult::GSA(_) => SentenceType::GSA,
ParseResult::GSV(_) => SentenceType::GSV,
ParseResult::HDT(_) => SentenceType::HDT,
ParseResult::MDA(_) => SentenceType::MDA,
ParseResult::MTW(_) => SentenceType::MTW,
ParseResult::MWV(_) => SentenceType::MWV,
ParseResult::RMC(_) => SentenceType::RMC,
ParseResult::TXT(_) => SentenceType::TXT,
ParseResult::VHW(_) => SentenceType::VHW,
ParseResult::VTG(_) => SentenceType::VTG,
ParseResult::ZFO(_) => SentenceType::ZFO,
ParseResult::ZTG(_) => SentenceType::ZTG,
ParseResult::PGRMZ(_) => SentenceType::RMZ,
ParseResult::ZDA(_) => SentenceType::ZDA,
ParseResult::Unsupported(sentence_type) => *sentence_type,
}
}
}
pub fn parse_bytes(sentence_input: &[u8]) -> Result<ParseResult, Error> {
let string = core::str::from_utf8(sentence_input).map_err(|_err| Error::Utf8Decoding)?;
parse_str(string)
}
pub fn parse_str(sentence_input: &str) -> Result<ParseResult, Error> {
if !sentence_input.is_ascii() {
return Err(Error::ASCII);
}
let nmea_sentence = parse_nmea_sentence(sentence_input)?;
let calculated_checksum = nmea_sentence.calc_checksum();
if nmea_sentence.checksum == calculated_checksum {
match nmea_sentence.message_id {
SentenceType::AAM => {
cfg_if! {
if #[cfg(feature = "AAM")] {
parse_aam(nmea_sentence).map(ParseResult::AAM)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::ALM => {
cfg_if! {
if #[cfg(feature = "ALM")] {
parse_alm(nmea_sentence).map(ParseResult::ALM)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::BOD => {
cfg_if! {
if #[cfg(feature = "BOD")] {
parse_bod(nmea_sentence).map(ParseResult::BOD)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::BWC => {
cfg_if! {
if #[cfg(feature = "BWC")] {
parse_bwc(nmea_sentence).map(ParseResult::BWC)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::BWW => {
cfg_if! {
if #[cfg(feature = "BWW")] {
parse_bww(nmea_sentence).map(ParseResult::BWW)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::DBK => {
cfg_if! {
if #[cfg(feature = "DBK")] {
parse_dbk(nmea_sentence).map(Into::into)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::GBS => {
cfg_if! {
if #[cfg(feature = "GBS")] {
parse_gbs(nmea_sentence).map(ParseResult::GBS)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::GGA => {
cfg_if! {
if #[cfg(feature = "GGA")] {
parse_gga(nmea_sentence).map(ParseResult::GGA)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::GLL => {
cfg_if! {
if #[cfg(feature = "GLL")] {
parse_gll(nmea_sentence).map(ParseResult::GLL)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::GNS => {
cfg_if! {
if #[cfg(feature = "GNS")] {
parse_gns(nmea_sentence).map(ParseResult::GNS)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::GSA => {
cfg_if! {
if #[cfg(feature = "GSA")] {
parse_gsa(nmea_sentence).map(ParseResult::GSA)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::GSV => {
cfg_if! {
if #[cfg(feature = "GSV")] {
parse_gsv(nmea_sentence).map(ParseResult::GSV)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::HDT => {
cfg_if! {
if #[cfg(feature = "HDT")] {
parse_hdt(nmea_sentence).map(ParseResult::HDT)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::MDA => {
cfg_if! {
if #[cfg(feature = "MDA")] {
parse_mda(nmea_sentence).map(ParseResult::MDA)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::MTW => {
cfg_if! {
if #[cfg(feature = "MTW")] {
parse_mtw(nmea_sentence).map(ParseResult::MTW)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::MWV => {
cfg_if! {
if #[cfg(feature = "MWV")] {
parse_mwv(nmea_sentence).map(ParseResult::MWV)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::RMC => {
cfg_if! {
if #[cfg(feature = "RMC")] {
parse_rmc(nmea_sentence).map(ParseResult::RMC)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::RMZ => {
cfg_if! {
if #[cfg(feature = "RMZ")] {
parse_pgrmz(nmea_sentence).map(ParseResult::PGRMZ)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::TXT => {
cfg_if! {
if #[cfg(feature = "TXT")] {
parse_txt(nmea_sentence).map(ParseResult::TXT)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::VHW => {
cfg_if! {
if #[cfg(feature = "VHW")] {
parse_vhw(nmea_sentence).map(ParseResult::VHW)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::VTG => {
cfg_if! {
if #[cfg(feature = "VTG")] {
parse_vtg(nmea_sentence).map(ParseResult::VTG)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::ZDA => {
cfg_if! {
if #[cfg(feature = "ZDA")] {
parse_zda(nmea_sentence).map(ParseResult::ZDA)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::ZFO => {
cfg_if! {
if #[cfg(feature = "ZFO")] {
parse_zfo(nmea_sentence).map(ParseResult::ZFO)
} else {
return Err(Error::DisabledSentence);
}
}
}
SentenceType::ZTG => {
cfg_if! {
if #[cfg(feature = "ZTG")] {
parse_ztg(nmea_sentence).map(ParseResult::ZTG)
} else {
return Err(Error::DisabledSentence);
}
}
}
sentence_type => Ok(ParseResult::Unsupported(sentence_type)),
}
} else {
Err(Error::ChecksumMismatch {
calculated: calculated_checksum,
found: nmea_sentence.checksum,
})
}
}