#![no_std]
mod message;
mod parser;
mod types;
pub use message::{Field, GgaData, GllData, GsaData, GsvData, RmcData, SatelliteInfo, VtgData};
pub use parser::NmeaParser;
pub use types::*;
pub type ParseResult = Result<(Option<NmeaMessage>, usize), (ParseError, usize)>;
#[cfg(test)]
mod tests {
use super::*;
fn test_message_parsing(sentence: &[u8], expected_type: MessageType) {
let parser = NmeaParser::new();
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, consumed) = result.unwrap();
assert_eq!(consumed, sentence.len());
let msg = msg.unwrap();
assert_eq!(msg.message_type(), expected_type);
}
#[test]
fn test_parser_initialization() {
let _parser = NmeaParser::new();
}
#[test]
fn test_parse_all_message_types() {
let test_cases = [
(
b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n".as_slice(),
MessageType::GGA,
),
(
b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n"
.as_slice(),
MessageType::RMC,
),
(
b"$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n".as_slice(),
MessageType::GSA,
),
(
b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n"
.as_slice(),
MessageType::GSV,
),
(
b"$GPGLL,4916.45,N,12311.12,W,225444,A,*1D\r\n".as_slice(),
MessageType::GLL,
),
(
b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n".as_slice(),
MessageType::VTG,
),
];
for (sentence, expected_type) in test_cases {
test_message_parsing(sentence, expected_type);
}
}
#[test]
fn test_multiple_messages_stream() {
let parser = NmeaParser::new();
let stream = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n\
$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n";
let mut message_count = 0;
let mut offset = 0;
while offset < stream.len() {
let result = parser.parse_bytes(&stream[offset..]);
if result.is_err() {
break;
}
let (msg, consumed) = result.unwrap();
if consumed == 0 {
break;
}
offset += consumed;
if msg.is_some() {
message_count += 1;
}
}
assert_eq!(message_count, 3);
}
#[test]
fn test_field_extraction() {
let parser = NmeaParser::new();
let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, consumed) = result.unwrap();
assert_eq!(consumed, sentence.len());
let msg = msg.unwrap();
assert_eq!(msg.message_type(), MessageType::GGA);
let gga = msg.as_gga().expect("Expected GGA message");
assert_eq!(gga.time(), "123519");
assert_eq!(gga.latitude, 4807.038);
}
#[test]
fn test_invalid_and_partial_sentences() {
let parser = NmeaParser::new();
let invalid = b"INVALID DATA\r\n";
let result = parser.parse_bytes(invalid);
assert!(result.is_ok());
let (msg, consumed) = result.unwrap();
assert!(msg.is_none());
assert_eq!(consumed, invalid.len());
let partial = b"$GPGGA,123519,4807.038,N";
let result = parser.parse_bytes(partial);
assert!(result.is_ok());
let (msg, consumed) = result.unwrap();
assert!(msg.is_none());
assert_eq!(consumed, 0);
}
#[test]
fn test_gga_parameters() {
let parser = NmeaParser::new();
let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let gga = msg.as_gga();
assert!(gga.is_some());
let gga_data = gga.unwrap();
assert_eq!(gga_data.time(), "123519");
assert_eq!(gga_data.latitude, 4807.038);
assert_eq!(gga_data.lat_direction, 'N');
assert_eq!(gga_data.longitude, 1131.000);
assert_eq!(gga_data.lon_direction, 'E');
assert_eq!(gga_data.fix_quality, 1);
assert_eq!(gga_data.num_satellites, Some(8));
assert_eq!(gga_data.hdop, Some(0.9));
assert_eq!(gga_data.altitude, Some(545.4));
assert_eq!(gga_data.altitude_units, Some('M'));
}
#[test]
fn test_rmc_parameters() {
let parser = NmeaParser::new();
let sentence = b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let rmc = msg.as_rmc();
assert!(rmc.is_some());
let rmc_data = rmc.unwrap();
assert_eq!(rmc_data.time(), "123519");
assert_eq!(rmc_data.status, 'A');
assert_eq!(rmc_data.latitude, 4807.038);
assert_eq!(rmc_data.lat_direction, 'N');
assert_eq!(rmc_data.longitude, 1131.000);
assert_eq!(rmc_data.lon_direction, 'E');
assert_eq!(rmc_data.speed_knots, 22.4);
assert_eq!(rmc_data.track_angle, 84.4);
assert_eq!(rmc_data.date(), "230394");
}
#[test]
fn test_gsa_parameters() {
let parser = NmeaParser::new();
let sentence = b"$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let gsa = msg.as_gsa();
assert!(gsa.is_some());
let gsa_data = gsa.unwrap();
assert_eq!(gsa_data.mode, 'A');
assert_eq!(gsa_data.fix_type, 3);
assert_eq!(gsa_data.satellite_ids[0], Some(4));
assert_eq!(gsa_data.satellite_ids[1], Some(5));
assert_eq!(gsa_data.satellite_ids[3], Some(9));
assert_eq!(gsa_data.pdop, Some(2.5));
assert_eq!(gsa_data.hdop, Some(1.3));
assert_eq!(gsa_data.vdop, Some(2.1));
}
#[test]
fn test_gsv_parameters() {
let parser = NmeaParser::new();
let sentence = b"$GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let gsv = msg.as_gsv();
assert!(gsv.is_some());
let gsv_data = gsv.unwrap();
assert_eq!(gsv_data.num_messages, 2);
assert_eq!(gsv_data.message_num, 1);
assert_eq!(gsv_data.satellites_in_view, 8);
assert!(gsv_data.satellite_info[0].is_some());
let sat1 = gsv_data.satellite_info[0].as_ref().unwrap();
assert_eq!(sat1.prn, Some(1));
assert_eq!(sat1.elevation, Some(40));
assert_eq!(sat1.azimuth, Some(83));
assert_eq!(sat1.snr, Some(46));
}
#[test]
fn test_gll_parameters() {
let parser = NmeaParser::new();
let sentence = b"$GPGLL,4916.45,N,12311.12,W,225444,A,*1D\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let gll = msg.as_gll();
assert!(gll.is_some());
let gll_data = gll.unwrap();
assert_eq!(gll_data.latitude, 4916.45);
assert_eq!(gll_data.lat_direction, 'N');
assert_eq!(gll_data.longitude, 12311.12);
assert_eq!(gll_data.lon_direction, 'W');
assert_eq!(gll_data.time(), "225444");
assert_eq!(gll_data.status, 'A');
}
#[test]
fn test_vtg_parameters() {
let parser = NmeaParser::new();
let sentence = b"$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let vtg = msg.as_vtg();
assert!(vtg.is_some());
let vtg_data = vtg.unwrap();
assert_eq!(vtg_data.track_true, Some(54.7));
assert_eq!(vtg_data.track_true_indicator, Some('T'));
assert_eq!(vtg_data.track_magnetic, Some(34.4));
assert_eq!(vtg_data.track_magnetic_indicator, Some('M'));
assert_eq!(vtg_data.speed_knots, Some(5.5));
assert_eq!(vtg_data.speed_knots_indicator, Some('N'));
assert_eq!(vtg_data.speed_kph, Some(10.2));
assert_eq!(vtg_data.speed_kph_indicator, Some('K'));
}
#[test]
fn test_wrong_message_type_extraction() {
let parser = NmeaParser::new();
let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let rmc = msg.as_rmc();
assert!(rmc.is_none());
let gga = msg.as_gga();
assert!(gga.is_some());
}
#[test]
fn test_gga_with_empty_fields() {
let parser = NmeaParser::new();
let sentence = b"$GPGGA,123519,,N,,E,1,,,,,M,,M,,*47\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_err());
let (err, _consumed) = result.unwrap_err();
assert_eq!(err, ParseError::InvalidMessage);
}
#[test]
fn test_rmc_with_empty_status() {
let parser = NmeaParser::new();
let sentence = b"$GPRMC,123519,V,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let rmc = msg.as_rmc();
assert!(rmc.is_some());
let rmc_data = rmc.unwrap();
assert_eq!(rmc_data.status, 'V');
}
#[test]
fn test_gsa_with_partial_satellites() {
let parser = NmeaParser::new();
let sentence = b"$GPGSA,A,3,01,,,,,,,,,,,,2.5,1.3,2.1*39\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let gsa = msg.as_gsa();
assert!(gsa.is_some());
let gsa_data = gsa.unwrap();
assert_eq!(gsa_data.mode, 'A');
assert_eq!(gsa_data.fix_type, 3);
assert_eq!(gsa_data.satellite_ids[0], Some(1));
assert_eq!(gsa_data.satellite_ids[1], None);
assert_eq!(gsa_data.pdop, Some(2.5));
assert_eq!(gsa_data.hdop, Some(1.3));
assert_eq!(gsa_data.vdop, Some(2.1));
}
#[test]
fn test_gsv_with_partial_satellite_data() {
let parser = NmeaParser::new();
let sentence = b"$GPGSV,1,1,02,01,40,083,46,02,17,308,*75\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let gsv = msg.as_gsv();
assert!(gsv.is_some());
let gsv_data = gsv.unwrap();
assert_eq!(gsv_data.num_messages, 1);
assert_eq!(gsv_data.satellites_in_view, 2);
assert!(gsv_data.satellite_info[0].is_some());
let sat1 = gsv_data.satellite_info[0].as_ref().unwrap();
assert_eq!(sat1.prn, Some(1));
assert_eq!(sat1.elevation, Some(40));
assert_eq!(sat1.azimuth, Some(83));
assert_eq!(sat1.snr, Some(46));
assert!(gsv_data.satellite_info[1].is_some());
let sat2 = gsv_data.satellite_info[1].as_ref().unwrap();
assert_eq!(sat2.prn, Some(2));
assert_eq!(sat2.elevation, Some(17));
assert_eq!(sat2.azimuth, Some(308));
assert_eq!(sat2.snr, None);
assert!(gsv_data.satellite_info[2].is_none());
assert!(gsv_data.satellite_info[3].is_none());
}
#[test]
fn test_numeric_type_parsing() {
let parser = NmeaParser::new();
let sentence = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
let result = parser.parse_bytes(sentence);
assert!(result.is_ok());
let (msg, _) = result.unwrap();
let msg = msg.unwrap();
let gga = msg.as_gga();
assert!(gga.is_some());
let gga_data = gga.unwrap();
assert!((gga_data.latitude - 4807.038).abs() < 0.001);
assert!((gga_data.longitude - 1131.000).abs() < 0.001);
if let Some(hdop) = gga_data.hdop {
assert!((hdop - 0.9).abs() < 0.01);
}
if let Some(alt) = gga_data.altitude {
assert!((alt - 545.4).abs() < 0.1);
}
}
#[test]
fn test_messages_with_missing_mandatory_fields() {
let parser = NmeaParser::new();
let test_cases = [
b"$GPGGA,,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n".as_slice(), b"$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,,003.1,W*6A\r\n".as_slice(), b"$GPGSA,,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n".as_slice(), b"$GPGSV,,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45*75\r\n".as_slice(), b"$GPGLL,4916.45,N,12311.12,W,225444,,*1D\r\n".as_slice(), ];
for sentence in test_cases {
let result = parser.parse_bytes(sentence);
assert!(result.is_err());
let (err, _consumed) = result.unwrap_err();
assert_eq!(err, ParseError::InvalidMessage);
}
}
#[test]
fn test_parse_with_spurious_characters() {
let parser = NmeaParser::new();
let data = b"GARBAGE$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
let result = parser.parse_bytes(data);
assert!(result.is_ok());
let (msg, consumed) = result.unwrap();
assert!(msg.is_some());
assert_eq!(consumed, data.len());
}
#[test]
fn test_parse_multiple_messages_in_buffer() {
let parser = NmeaParser::new();
let data = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
let result1 = parser.parse_bytes(data);
assert!(result1.is_ok());
let (msg1, consumed1) = result1.unwrap();
assert!(msg1.is_some());
assert_eq!(msg1.unwrap().message_type(), MessageType::GGA);
let result2 = parser.parse_bytes(&data[consumed1..]);
assert!(result2.is_ok());
let (msg2, consumed2) = result2.unwrap();
assert!(msg2.is_some());
assert_eq!(msg2.unwrap().message_type(), MessageType::RMC);
assert_eq!(consumed1 + consumed2, data.len());
}
#[test]
fn test_partial_message_returns_none() {
let parser = NmeaParser::new();
let partial = b"$GPGGA,123519,4807.038,N,01131.000,E,1";
let result = parser.parse_bytes(partial);
assert!(result.is_ok());
let (msg, consumed) = result.unwrap();
assert!(msg.is_none());
assert_eq!(consumed, 0); }
#[test]
fn test_spurious_only_data() {
let parser = NmeaParser::new();
let spurious = b"GARBAGE DATA WITHOUT DOLLAR SIGN\r\n";
let result = parser.parse_bytes(spurious);
assert!(result.is_ok());
let (msg, consumed) = result.unwrap();
assert!(msg.is_none());
assert_eq!(consumed, spurious.len()); }
#[test]
fn test_bytes_consumed_tracking() {
let parser = NmeaParser::new();
let data = b"JUNK$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\nMORE";
let result = parser.parse_bytes(data);
assert!(result.is_ok());
let (msg, consumed) = result.unwrap();
assert!(msg.is_some());
assert!(consumed < data.len());
assert!(consumed > 0);
assert_eq!(&data[consumed..], b"MORE");
}
#[test]
fn test_invalid_message_returns_error() {
let parser = NmeaParser::new();
let invalid = b"$GPGGA,,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
let result = parser.parse_bytes(invalid);
assert!(result.is_err());
let (err, _consumed) = result.unwrap_err();
assert_eq!(err, ParseError::InvalidMessage);
}
#[test]
fn test_multiple_messages_with_different_timestamps() {
let parser = NmeaParser::new();
let data = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n\
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n";
let result1 = parser.parse_bytes(data);
assert!(result1.is_ok());
let (msg1, consumed1) = result1.unwrap();
assert!(msg1.is_some());
let msg1 = msg1.unwrap();
assert_eq!(msg1.message_type(), MessageType::GGA);
let result2 = parser.parse_bytes(&data[consumed1..]);
assert!(result2.is_ok());
let (msg2, _consumed2) = result2.unwrap();
assert!(msg2.is_some());
let msg2 = msg2.unwrap();
assert_eq!(msg2.message_type(), MessageType::RMC);
}
}