use chrono::{NaiveDate, NaiveTime};
use nom::{
character::complete::{anychar, char, one_of},
combinator::{cond, map_res, opt},
number::complete::float,
IResult,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
parse::NmeaSentence,
sentences::utils::{parse_date, parse_hms, parse_lat_lon},
Error, SentenceType,
};
use super::{faa_mode::parse_faa_mode, utils::parse_magnetic_variation, FaaMode};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RmcStatusOfFix {
Autonomous,
Differential,
Invalid,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RmcNavigationStatus {
Autonomous,
Differential,
Estimated,
Manual,
NotValid,
Simulator,
Valid,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RmcData {
#[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
pub fix_time: Option<NaiveTime>,
#[cfg_attr(feature = "defmt-03", defmt(Debug2Format))]
pub fix_date: Option<NaiveDate>,
pub status_of_fix: RmcStatusOfFix,
pub lat: Option<f64>,
pub lon: Option<f64>,
pub speed_over_ground: Option<f32>,
pub true_course: Option<f32>,
pub magnetic_variation: Option<f32>,
pub faa_mode: Option<FaaMode>,
pub nav_status: Option<RmcNavigationStatus>,
}
fn do_parse_rmc(i: &str) -> IResult<&str, RmcData> {
let (i, fix_time) = opt(parse_hms)(i)?;
let (i, _) = char(',')(i)?;
let (i, status_of_fix) = one_of("ADV")(i)?;
let status_of_fix = match status_of_fix {
'A' => RmcStatusOfFix::Autonomous,
'D' => RmcStatusOfFix::Differential,
'V' => RmcStatusOfFix::Invalid,
_ => unreachable!(),
};
let (i, _) = char(',')(i)?;
let (i, lat_lon) = parse_lat_lon(i)?;
let (i, _) = char(',')(i)?;
let (i, speed_over_ground) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, true_course) = opt(float)(i)?;
let (i, _) = char(',')(i)?;
let (i, fix_date) = opt(parse_date)(i)?;
let (i, _) = char(',')(i)?;
let (i, magnetic_variation) = parse_magnetic_variation(i)?;
let (i, next) = opt(char(','))(i)?;
let (i, faa_mode) = cond(
next.is_some(),
opt(map_res(anychar, |c| parse_faa_mode(c).ok_or("argh"))),
)(i)?;
let (i, next) = opt(char(','))(i)?;
let (i, nav_status) = cond(next.is_some(), opt(parse_navigation_status))(i)?;
Ok((
i,
RmcData {
fix_time,
fix_date,
status_of_fix,
lat: lat_lon.map(|v| v.0),
lon: lat_lon.map(|v| v.1),
speed_over_ground,
true_course,
magnetic_variation,
faa_mode: faa_mode.flatten(),
nav_status: nav_status.flatten(),
},
))
}
fn parse_navigation_status(i: &str) -> IResult<&str, RmcNavigationStatus> {
let (i, c) = one_of("ADEMNSV")(i)?;
let status = match c {
'A' => RmcNavigationStatus::Autonomous,
'D' => RmcNavigationStatus::Differential,
'E' => RmcNavigationStatus::Estimated,
'M' => RmcNavigationStatus::Manual,
'N' => RmcNavigationStatus::NotValid,
'S' => RmcNavigationStatus::Simulator,
'V' => RmcNavigationStatus::Valid,
_ => unreachable!(),
};
Ok((i, status))
}
pub fn parse_rmc(sentence: NmeaSentence) -> Result<RmcData, Error> {
if sentence.message_id != SentenceType::RMC {
Err(Error::WrongSentenceHeader {
expected: SentenceType::RMC,
found: sentence.message_id,
})
} else {
Ok(do_parse_rmc(sentence.data)?.1)
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
use crate::parse::parse_nmea_sentence;
#[test]
fn parse_rmc_v23_all_fields_have_value() {
let s = parse_nmea_sentence(
"$GPRMC,225446.33,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E,A*2B",
)
.unwrap();
assert_eq!(s.checksum, s.calc_checksum());
assert_eq!(s.checksum, 0x2b);
let rmc_data = parse_rmc(s).unwrap();
assert_eq!(
rmc_data.fix_time,
Some(NaiveTime::from_hms_milli_opt(22, 54, 46, 330).expect("invalid time"))
);
assert_eq!(
rmc_data.fix_date,
Some(NaiveDate::from_ymd_opt(1994, 11, 19).expect("invalid time"))
);
println!("lat: {}", rmc_data.lat.unwrap());
assert_relative_eq!(rmc_data.lat.unwrap(), 49.0 + 16.45 / 60.);
println!(
"lon: {}, diff {}",
rmc_data.lon.unwrap(),
(rmc_data.lon.unwrap() + (123.0 + 11.12 / 60.)).abs()
);
assert_relative_eq!(rmc_data.lon.unwrap(), -(123.0 + 11.12 / 60.));
assert_relative_eq!(rmc_data.speed_over_ground.unwrap(), 0.5);
assert_relative_eq!(rmc_data.true_course.unwrap(), 54.7);
assert_relative_eq!(rmc_data.magnetic_variation.unwrap(), 20.3);
assert_eq!(rmc_data.faa_mode, Some(FaaMode::Autonomous));
assert_eq!(rmc_data.nav_status, None);
}
#[test]
fn parse_rmc_pre_v23_all_fields_have_value() {
let s = parse_nmea_sentence(
"$GPRMC,225446.33,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*46",
)
.unwrap();
assert_eq!(s.checksum, s.calc_checksum());
assert_eq!(s.checksum, 0x46);
let RmcData {
fix_time,
status_of_fix,
lat,
lon,
speed_over_ground,
true_course,
fix_date,
magnetic_variation,
faa_mode,
nav_status,
} = parse_rmc(s).unwrap();
assert_eq!(
fix_time,
Some(NaiveTime::from_hms_milli_opt(22, 54, 46, 330).expect("invalid time"))
);
assert_eq!(status_of_fix, RmcStatusOfFix::Autonomous);
assert_eq!(
fix_date,
Some(NaiveDate::from_ymd_opt(1994, 11, 19).expect("invalid time"))
);
println!("lat: {:?}", lat);
assert_relative_eq!(lat.unwrap(), 49.0 + 16.45 / 60.);
println!(
"lon: {}, diff {}",
lon.unwrap(),
(lon.unwrap() + (123.0 + 11.12 / 60.)).abs()
);
assert_relative_eq!(lon.unwrap(), -(123.0 + 11.12 / 60.));
assert_relative_eq!(speed_over_ground.unwrap(), 0.5);
assert_relative_eq!(true_course.unwrap(), 54.7);
assert_relative_eq!(magnetic_variation.unwrap(), 20.3);
assert_eq!(faa_mode, None);
assert_eq!(nav_status, None);
}
#[test]
fn parse_rmc_v23_warning_status_most_fields_empty() {
let s = parse_nmea_sentence("$GPRMC,,V,,,,,,,,,,N*53").unwrap();
let rmc = parse_rmc(s).unwrap();
assert_eq!(
RmcData {
fix_time: None,
fix_date: None,
status_of_fix: RmcStatusOfFix::Invalid,
lat: None,
lon: None,
speed_over_ground: None,
true_course: None,
magnetic_variation: None,
faa_mode: Some(FaaMode::DataNotValid),
nav_status: None
},
rmc
);
}
#[test]
fn parse_rmc_v23_missing_true_course_and_magnetic_variation() {
let gpsd_example = "$GNRMC,001031.00,A,4404.13993,N,12118.86023,W,0.146,,100117,,,A*7B";
let RmcData {
fix_time,
fix_date,
status_of_fix,
lat,
lon,
speed_over_ground,
true_course,
magnetic_variation,
faa_mode,
nav_status,
} = parse_nmea_sentence(gpsd_example)
.map(parse_rmc)
.unwrap()
.unwrap();
assert_eq!(
fix_time,
Some(NaiveTime::from_hms_milli_opt(0, 10, 31, 0).expect("invalid time"))
);
assert_eq!(
fix_date,
Some(NaiveDate::from_ymd_opt(2017, 1, 10).expect("invalid time"))
);
assert_eq!(status_of_fix, RmcStatusOfFix::Autonomous);
assert_relative_eq!(lat.unwrap(), (44. + 4.13993 / 60.));
assert_relative_eq!(lon.unwrap(), -(121. + 18.86023 / 60.));
assert_relative_eq!(speed_over_ground.unwrap(), 0.146);
assert_eq!(true_course, None);
assert_eq!(magnetic_variation, None);
assert_eq!(faa_mode, Some(FaaMode::Autonomous));
assert_eq!(nav_status, None);
}
#[test]
fn parse_rmc_v41_full() {
let rmc_v41 =
"$GPRMC,225207.376,A,5232.067,N,01325.658,E,038.9,324.5,011122,000.0,W,M,E*7A";
let RmcData {
fix_time,
fix_date,
status_of_fix,
lat,
lon,
speed_over_ground,
true_course,
magnetic_variation,
faa_mode,
nav_status,
} = parse_nmea_sentence(rmc_v41)
.map(parse_rmc)
.unwrap()
.unwrap();
assert_eq!(
fix_time,
Some(NaiveTime::from_hms_milli_opt(22, 52, 7, 376).expect("invalid time"))
);
assert_eq!(
fix_date,
Some(NaiveDate::from_ymd_opt(2022, 11, 1).expect("invalid time"))
);
assert_eq!(status_of_fix, RmcStatusOfFix::Autonomous);
assert_relative_eq!(lat.unwrap(), (52. + 32.067 / 60.));
assert_relative_eq!(lon.unwrap(), (13. + 25.658 / 60.));
assert_relative_eq!(speed_over_ground.unwrap(), 38.9);
assert_relative_eq!(true_course.unwrap(), 324.5);
assert_relative_eq!(magnetic_variation.unwrap(), 0.0);
assert_eq!(faa_mode, Some(FaaMode::Manual));
assert_eq!(nav_status, Some(RmcNavigationStatus::Estimated));
}
}