use nom::{
bytes::complete::take_until,
character::complete::char,
combinator::{map_res, opt},
IResult,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{Error, NmeaSentence, SentenceType};
use super::utils::parse_float_num;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Clone, PartialEq, Debug)]
pub struct VhwData {
pub heading_true: Option<f64>,
pub heading_magnetic: Option<f64>,
pub relative_speed_knots: Option<f64>,
pub relative_speed_kmph: Option<f64>,
}
pub fn parse_vhw(sentence: NmeaSentence) -> Result<VhwData, Error> {
if sentence.message_id == SentenceType::VHW {
Ok(do_parse_vhw(sentence.data)?.1)
} else {
Err(Error::WrongSentenceHeader {
expected: SentenceType::VHW,
found: sentence.message_id,
})
}
}
fn do_parse_float_with_char(c: char, i: &str) -> IResult<&str, Option<f64>> {
let (i, value) = opt(map_res(take_until(","), parse_float_num::<f64>))(i)?;
let (i, _) = char(',')(i)?;
let (i, tag) = opt(char(c))(i)?;
Ok((i, tag.and(value)))
}
fn do_parse_vhw(i: &str) -> IResult<&str, VhwData> {
let comma = char(',');
let (i, heading_true) = do_parse_float_with_char('T', i)?;
let (i, _) = comma(i)?;
let (i, heading_magnetic) = do_parse_float_with_char('M', i)?;
let (i, _) = comma(i)?;
let (i, relative_speed_knots) = do_parse_float_with_char('N', i)?;
let (i, _) = comma(i)?;
let (i, relative_speed_kmph) = do_parse_float_with_char('K', i)?;
Ok((
i,
VhwData {
heading_true,
heading_magnetic,
relative_speed_knots,
relative_speed_kmph,
},
))
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
use crate::parse::parse_nmea_sentence;
#[test]
fn test_do_parse_float_with_char() {
assert_eq!(do_parse_float_with_char('T', "1.5,T"), Ok(("", Some(1.5))));
assert_eq!(do_parse_float_with_char('T', "1.5,"), Ok(("", None)));
assert_eq!(do_parse_float_with_char('T', ","), Ok(("", None)));
}
#[test]
fn test_wrong_sentence() {
let invalid_aam_sentence = NmeaSentence {
message_id: SentenceType::AAM,
data: "",
talker_id: "GP",
checksum: 0,
};
assert_eq!(
Err(Error::WrongSentenceHeader {
expected: SentenceType::VHW,
found: SentenceType::AAM
}),
parse_vhw(invalid_aam_sentence)
);
}
#[test]
fn test_parse_vhw() {
let s = NmeaSentence {
message_id: SentenceType::VHW,
talker_id: "GP",
data: "100.5,T,105.5,M,10.5,N,19.4,K",
checksum: 0x4f,
};
let vhw_data = parse_vhw(s).unwrap();
assert_relative_eq!(vhw_data.heading_true.unwrap(), 100.5);
assert_relative_eq!(vhw_data.heading_magnetic.unwrap(), 105.5);
assert_relative_eq!(vhw_data.relative_speed_knots.unwrap(), 10.5);
assert_relative_eq!(vhw_data.relative_speed_kmph.unwrap(), 19.4);
let s = parse_nmea_sentence("$GPVHW,100.5,T,105.5,M,10.5,N,19.4,K*4F").unwrap();
assert_eq!(s.checksum, s.calc_checksum());
assert_eq!(s.checksum, 0x4F);
let vhw_data = parse_vhw(s).unwrap();
assert_relative_eq!(vhw_data.heading_true.unwrap(), 100.5);
assert_relative_eq!(vhw_data.heading_magnetic.unwrap(), 105.5);
assert_relative_eq!(vhw_data.relative_speed_knots.unwrap(), 10.5);
assert_relative_eq!(vhw_data.relative_speed_kmph.unwrap(), 19.4);
}
#[test]
fn test_parse_incomplete_vhw() {
let s = NmeaSentence {
message_id: SentenceType::VHW,
talker_id: "GP",
data: ",T,,M,,N,,K",
checksum: 0,
};
assert_eq!(
parse_vhw(s),
Ok(VhwData {
heading_true: None,
heading_magnetic: None,
relative_speed_knots: None,
relative_speed_kmph: None,
})
);
let s = NmeaSentence {
message_id: SentenceType::VHW,
talker_id: "GP",
data: ",T,,M,10.5,N,20.0,K",
checksum: 0,
};
assert_eq!(
parse_vhw(s),
Ok(VhwData {
heading_true: None,
heading_magnetic: None,
relative_speed_knots: Some(10.5),
relative_speed_kmph: Some(20.0),
})
);
let s = NmeaSentence {
message_id: SentenceType::VHW,
talker_id: "GP",
data: ",,,,,,,",
checksum: 0,
};
assert_eq!(
parse_vhw(s),
Ok(VhwData {
heading_true: None,
heading_magnetic: None,
relative_speed_knots: None,
relative_speed_kmph: None
})
);
}
}