use arrayvec::ArrayString;
use chrono::NaiveTime;
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},
sentences::utils::{parse_hms, parse_lat_lon},
Error, SentenceType,
};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Debug, PartialEq)]
pub struct BwcData {
#[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
pub fix_time: Option<NaiveTime>,
pub latitude: Option<f64>,
pub longitude: Option<f64>,
pub true_bearing: Option<f32>,
pub magnetic_bearing: Option<f32>,
pub distance: Option<f32>,
#[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
pub waypoint_id: Option<ArrayString<TEXT_PARAMETER_MAX_LEN>>,
}
fn do_parse_bwc(i: &str) -> Result<BwcData, Error> {
let (i, fix_time) = opt(parse_hms)(i)?;
let (i, _) = char(',')(i)?;
let (i, lat_lon) = parse_lat_lon(i)?;
let (i, _) = char(',')(i)?;
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, distance) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, _) = opt(char('N'))(i)?;
let (i, _) = char(',')(i)?;
let (_i, waypoint_id) = opt(is_not(",*"))(i)?;
let waypoint_id = if let Some(waypoint_id) = waypoint_id {
Some(
ArrayString::from(waypoint_id)
.map_err(|_e| Error::SentenceLength(waypoint_id.len()))?,
)
} else {
None
};
Ok(BwcData {
fix_time,
latitude: lat_lon.map(|v| v.0),
longitude: lat_lon.map(|v| v.1),
true_bearing,
magnetic_bearing,
distance,
waypoint_id,
})
}
pub fn parse_bwc(sentence: NmeaSentence) -> Result<BwcData, Error> {
if sentence.message_id != SentenceType::BWC {
Err(Error::WrongSentenceHeader {
expected: SentenceType::BWC,
found: sentence.message_id,
})
} else {
Ok(do_parse_bwc(sentence.data)?)
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
use crate::parse::parse_nmea_sentence;
#[test]
fn test_parse_bwc_full() {
let sentence = parse_nmea_sentence(
"$GPBWC,220516,5130.02,N,00046.34,W,213.8,T,218.0,M,0004.6,N,EGLM*21",
)
.unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x21);
let data = parse_bwc(sentence).unwrap();
assert_eq!(
data.fix_time,
Some(NaiveTime::from_hms_opt(22, 5, 16).expect("invalid time"))
);
assert_relative_eq!(data.latitude.unwrap(), 51. + 30.02 / 60.);
assert_relative_eq!(data.longitude.unwrap(), -46.34 / 60.0);
assert_relative_eq!(data.true_bearing.unwrap(), 213.8);
assert_relative_eq!(data.magnetic_bearing.unwrap(), 218.0);
assert_relative_eq!(data.distance.unwrap(), 4.6);
assert_eq!(&data.waypoint_id.unwrap(), "EGLM");
}
#[test]
fn test_parse_bwc_with_optional_fields() {
let sentence = parse_nmea_sentence("$GPBWC,081837,,,,,,T,,M,,N,*13").unwrap();
assert_eq!(sentence.checksum, sentence.calc_checksum());
assert_eq!(sentence.checksum, 0x13);
let data = parse_bwc(sentence).unwrap();
assert_eq!(
BwcData {
fix_time: Some(NaiveTime::from_hms_opt(8, 18, 37).expect("invalid time")),
latitude: None,
longitude: None,
true_bearing: None,
magnetic_bearing: None,
distance: None,
waypoint_id: None,
},
data
);
}
}