use super::nmea_parser::{NmeaFields, Parse};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NmeaRmc {
pub time_of_day_ms: u32,
pub latitude_degrees_x1e7: i32,
pub longitude_degrees_x1e7: i32,
pub is_healthy: u8,
pub ground_speed_cmps: i16,
pub heading_deci_degrees: i16,
}
impl Default for NmeaRmc {
fn default() -> Self {
Self::new()
}
}
#[allow(unused)]
impl NmeaRmc {
pub const fn new() -> Self {
Self {
time_of_day_ms: 0,
latitude_degrees_x1e7: 0,
longitude_degrees_x1e7: 0,
is_healthy: 0,
ground_speed_cmps: 0,
heading_deci_degrees: 0,
}
}
}
impl NmeaRmc {
pub fn parse(record: &[u8]) -> Option<Self> {
let mut fields = NmeaFields::new(record);
let talker_id = fields.next()?;
if talker_id.len() != 5 || &talker_id[2..] != b"RMC" {
return None;
}
let mut ret = Self::default();
let time = fields.next()?;
ret.time_of_day_ms = Parse::nmea_time(time)?;
let status = fields.next()?;
if status.len() != 1 {
return None;
}
match status[0] {
b'A' => ret.is_healthy = 1,
b'V' => ret.is_healthy = 0,
_ => return None,
}
let latitude = fields.next()?;
let latitude_direction = fields.next()?;
let longitude = fields.next()?;
let longitude_direction = fields.next()?;
if latitude_direction.len() != 1 || longitude_direction.len() != 1 {
return None;
}
ret.latitude_degrees_x1e7 = Parse::nmea_coordinate(latitude, latitude_direction[0])?;
ret.longitude_degrees_x1e7 = Parse::nmea_coordinate(longitude, longitude_direction[0])?;
let speed = fields.next()?;
let speed_knots_x10 = Parse::fixed_point(speed, 10)?;
let speed_cmps = speed_knots_x10.checked_mul(1852)?.checked_div(360)?;
ret.ground_speed_cmps = i16::try_from(speed_cmps).ok()?;
let course = fields.next()?;
let course = Parse::fixed_point(course, 10)?;
ret.heading_deci_degrees = i16::try_from(course).ok()?;
let _date = fields.next()?;
Some(ret)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
use super::*;
#[test]
fn parse_rmc_record_extracts_navigation_data() {
let record = b"GPRMC,123519.00,A,4916.45,N,12311.12,W,022.4,084.4,230394,,,";
let result = NmeaRmc::parse(record).expect("RMC record should parse");
assert_eq!(result.time_of_day_ms, 45_319_000);
assert_eq!(result.latitude_degrees_x1e7, 492_741_667);
assert_eq!(result.longitude_degrees_x1e7, -1_231_853_333);
assert_eq!(result.ground_speed_cmps, 1_152);
assert_eq!(result.heading_deci_degrees, 844);
assert_eq!(result.is_healthy, 1);
}
#[test]
fn parse_rmc_record_marks_invalid_fix_unhealthy() {
let record = b"GPRMC,123519.00,V,4916.45,N,12311.12,W,022.4,084.4,230394,,,";
let result = NmeaRmc::parse(record).expect("RMC record should parse");
assert_eq!(result.is_healthy, 0);
}
#[test]
fn parse_rmc_record_accepts_different_talker_ids() {
let record = b"GNRMC,123519.00,A,4916.45,N,12311.12,W,022.4,084.4,230394,,,";
assert!(NmeaRmc::parse(record).is_some());
}
}