use crate::{parse::*, sentences::utils::array_string, Error, SentenceType};
use arrayvec::ArrayString;
use nom::{
bytes::complete::{is_not, take_until},
character::complete::char,
combinator::{map_parser, opt},
number::complete::float,
sequence::preceded,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BodData {
pub bearing_true: Option<f32>,
pub bearing_magnetic: Option<f32>,
#[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
pub to_waypoint: Option<ArrayString<TEXT_PARAMETER_MAX_LEN>>,
#[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
pub from_waypoint: Option<ArrayString<TEXT_PARAMETER_MAX_LEN>>,
}
fn do_parse_bod(i: &str) -> Result<BodData, Error> {
let (i, bearing_true) = opt(map_parser(take_until(","), float))(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = char('T')(i)?;
let (i, _) = char(',')(i)?;
let (i, bearing_magnetic) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = char('M')(i)?;
let (i, _) = char(',')(i)?;
let (i, to_waypoint) = opt(is_not(",*"))(i)?;
let from_waypoint = opt(preceded(char(','), is_not("*")))(i)?.1;
Ok(BodData {
bearing_true,
bearing_magnetic,
to_waypoint: to_waypoint
.map(array_string::<TEXT_PARAMETER_MAX_LEN>)
.transpose()?,
from_waypoint: from_waypoint
.map(array_string::<TEXT_PARAMETER_MAX_LEN>)
.transpose()?,
})
}
pub fn parse_bod(sentence: NmeaSentence) -> Result<BodData, Error> {
if sentence.message_id != SentenceType::BOD {
Err(Error::WrongSentenceHeader {
expected: SentenceType::BOD,
found: sentence.message_id,
})
} else {
do_parse_bod(sentence.data)
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn parse_bod_with_route_active_example_full() {
let sentence = parse_nmea_sentence("$GPBOD,097.0,T,103.2,M,POINTB,POINTA*4A").unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x4A);
let data = parse_bod(sentence).unwrap();
assert_relative_eq!(data.bearing_true.unwrap(), 97.0);
assert_relative_eq!(data.bearing_magnetic.unwrap(), 103.2);
assert_eq!(data.to_waypoint.as_deref(), Some("POINTB"));
assert_eq!(data.from_waypoint.as_deref(), Some("POINTA"));
}
#[test]
fn parse_bod_with_route_active_missing_destination_waypoint_example_full() {
let sentence = parse_nmea_sentence("$GPBOD,097.0,T,103.2,M,,POINTA*44").unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x44);
let data = parse_bod(sentence).unwrap();
assert_relative_eq!(data.bearing_true.unwrap(), 97.0);
assert_relative_eq!(data.bearing_magnetic.unwrap(), 103.2);
assert!(data.to_waypoint.is_none());
assert_eq!(data.from_waypoint.as_deref(), Some("POINTA"));
}
#[test]
fn parse_bod_with_route_active_missing_source_waypoint_example_full() {
let sentence = parse_nmea_sentence("$GPBOD,097.0,T,103.2,M,POINTB,*47").unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x47);
let data = parse_bod(sentence).unwrap();
assert_relative_eq!(data.bearing_true.unwrap(), 97.0);
assert_relative_eq!(data.bearing_magnetic.unwrap(), 103.2);
assert_eq!(data.to_waypoint.as_deref(), Some("POINTB"));
assert!(data.from_waypoint.is_none());
}
#[test]
fn parse_bod_no_route_active_example_full() {
let sentence = parse_nmea_sentence("$GPBOD,099.3,T,105.6,M,POINTB*64").unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x64);
let data = parse_bod(sentence).unwrap();
assert_relative_eq!(data.bearing_true.unwrap(), 99.3);
assert_relative_eq!(data.bearing_magnetic.unwrap(), 105.6);
assert_eq!(data.to_waypoint.as_deref(), Some("POINTB"));
assert!(data.from_waypoint.is_none());
}
#[test]
fn parse_bod_no_route_active_no_bearing_example_full() {
let sentence = parse_nmea_sentence("$GPBOD,,T,105.6,M,POINTB*49").unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x49);
let data = parse_bod(sentence).unwrap();
assert!(data.bearing_true.is_none());
assert_relative_eq!(data.bearing_magnetic.unwrap(), 105.6);
assert_eq!(data.to_waypoint.as_deref(), Some("POINTB"));
assert!(data.from_waypoint.is_none());
}
}