use arrayvec::ArrayString;
use nom::{
bytes::complete::is_not, character::complete::char, combinator::opt, number::complete::float,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
parse::{NmeaSentence, TEXT_PARAMETER_MAX_LEN},
Error, SentenceType,
};
use super::utils::array_string;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Debug, PartialEq)]
pub struct BwwData {
pub true_bearing: Option<f32>,
pub magnetic_bearing: Option<f32>,
#[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
pub to_waypoint_id: Option<ArrayString<TEXT_PARAMETER_MAX_LEN>>,
#[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
pub from_waypoint_id: Option<ArrayString<TEXT_PARAMETER_MAX_LEN>>,
}
fn do_parse_bww(i: &str) -> Result<BwwData, Error> {
let (i, true_bearing) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('T'))(i)?;
let (i, _) = char(',')(i)?;
let (i, magnetic_bearing) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('M'))(i)?;
let (i, _) = char(',')(i)?;
let (i, to_waypoint_id) = opt(is_not(","))(i)?;
let to_waypoint_id = to_waypoint_id
.map(array_string::<TEXT_PARAMETER_MAX_LEN>)
.transpose()?;
let (i, _) = char(',')(i)?;
let (_i, from_waypoint_id) = opt(is_not(",*"))(i)?;
let from_waypoint_id = from_waypoint_id
.map(array_string::<TEXT_PARAMETER_MAX_LEN>)
.transpose()?;
Ok(BwwData {
true_bearing,
magnetic_bearing,
to_waypoint_id,
from_waypoint_id,
})
}
pub fn parse_bww(sentence: NmeaSentence) -> Result<BwwData, Error> {
if sentence.message_id != SentenceType::BWW {
Err(Error::WrongSentenceHeader {
expected: SentenceType::BWW,
found: sentence.message_id,
})
} else {
Ok(do_parse_bww(sentence.data)?)
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
use crate::parse::parse_nmea_sentence;
#[test]
fn test_parse_bww_full() {
let sentence = parse_nmea_sentence("$GPBWW,213.8,T,218.0,M,TOWPT,FROMWPT*42").unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x42);
let data = parse_bww(sentence).unwrap();
assert_relative_eq!(data.true_bearing.unwrap(), 213.8);
assert_relative_eq!(data.magnetic_bearing.unwrap(), 218.0);
assert_eq!(&data.to_waypoint_id.unwrap(), "TOWPT");
assert_eq!(&data.from_waypoint_id.unwrap(), "FROMWPT");
}
#[test]
fn test_parse_bww_with_optional_fields() {
let sentence = parse_nmea_sentence("$GPBWW,,T,,M,,*4C").unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x4C);
let data = parse_bww(sentence).unwrap();
assert_eq!(
BwwData {
true_bearing: None,
magnetic_bearing: None,
to_waypoint_id: None,
from_waypoint_id: None,
},
data
);
}
#[test]
fn test_parse_bww_with_wrong_sentence() {
let sentence = parse_nmea_sentence("$GPAAM,,T,,M,,*4C").unwrap();
assert_eq!(
parse_bww(sentence).unwrap_err(),
Error::WrongSentenceHeader {
expected: SentenceType::BWW,
found: SentenceType::AAM,
}
);
}
#[test]
fn test_parse_bww_with_too_long_to_waypoint_parameter() {
let sentence = parse_nmea_sentence("$GPBWW,,T,,M,ABCDEFGHIJKLMNOPQRSTUWXYZABCDEFGHIJKLMNOPQRSTUWXYZABCDEFGHIJKLMNOPQRSTUWXYZ,*4C").unwrap();
assert_eq!(
parse_bww(sentence).unwrap_err(),
Error::ParameterLength {
max_length: 64,
parameter_length: 75
}
);
}
#[test]
fn test_parse_bww_with_too_long_from_waypoint_parameter() {
let sentence = parse_nmea_sentence("$GPBWW,,T,,M,,ABCDEFGHIJKLMNOPQRSTUWXYZABCDEFGHIJKLMNOPQRSTUWXYZABCDEFGHIJKLMNOPQRSTUWXYZ*4C").unwrap();
assert_eq!(
parse_bww(sentence).unwrap_err(),
Error::ParameterLength {
max_length: 64,
parameter_length: 75
}
);
}
}