Handle malformed NMEA sentences

This commit is contained in:
Richard Patching 2025-06-18 08:35:48 +01:00
parent a22efdfca3
commit f6fc0a71c6

View File

@ -11,6 +11,91 @@ const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
const GPS_BAUD: u32 = 9600; const GPS_BAUD: u32 = 9600;
const INIT_DELAY_MS: u32 = 5000; // 5 second initialization delay const INIT_DELAY_MS: u32 = 5000; // 5 second initialization delay
const POWER_CHECK_INTERVAL: u32 = 1000; // Check power every second const POWER_CHECK_INTERVAL: u32 = 1000; // Check power every second
const UART_BUFFER_SIZE: usize = 256; // Increased buffer size
const READ_DELAY_MS: u32 = 10; // Small delay between reads
fn decimal_to_dms(decimal: f32) -> (i32, i32, f32) {
let degrees = decimal.abs() as i32;
let minutes_decimal = (decimal.abs() - degrees as f32) * 60.0;
let minutes = minutes_decimal as i32;
let seconds = (minutes_decimal - minutes as f32) * 60.0;
(degrees, minutes, seconds)
}
fn format_coordinates(lat: f32, long: f32) -> String {
// Decimal degrees (DD)
let dd = format!("Decimal degrees (DD): {:.5}, {:.5}", lat, long);
// Degrees, minutes and seconds (DMS)
let (lat_deg, lat_min, lat_sec) = decimal_to_dms(lat);
let (long_deg, long_min, long_sec) = decimal_to_dms(long);
let dms = format!(
"Degrees, minutes and seconds (DMS): {}°{}'{:.1}\"{}, {}°{}'{:.1}\"{}",
lat_deg,
lat_min,
lat_sec,
if lat >= 0.0 { "N" } else { "S" },
long_deg,
long_min,
long_sec,
if long >= 0.0 { "E" } else { "W" }
);
// Degrees and decimal minutes (DMM)
let dmm = format!(
"Degrees and decimal minutes (DMM): {} {:.4}, {} {:.4}",
lat_deg,
(lat.abs() - lat_deg as f32) * 60.0,
long_deg,
(long.abs() - long_deg as f32) * 60.0
);
format!("{}\n{}\n{}", dd, dms, dmm)
}
fn parse_gll(sentence: &str) -> Option<(f32, f32)> {
let parts: Vec<&str> = sentence.split(',').collect();
if parts.len() < 7 {
return None;
}
// Parse latitude (DDMM.MMMM format)
if let (Ok(lat_deg), Ok(lat_min)) = (parts[1][..2].parse::<f32>(), parts[1][2..].parse::<f32>())
{
let lat = lat_deg + lat_min / 60.0;
let lat = if parts[2] == "S" { -lat } else { lat };
// Parse longitude (DDDMM.MMMM format)
if let (Ok(long_deg), Ok(long_min)) =
(parts[3][..3].parse::<f32>(), parts[3][3..].parse::<f32>())
{
let long = long_deg + long_min / 60.0;
let long = if parts[4] == "W" { -long } else { long };
return Some((lat, long));
}
}
None
}
fn is_valid_nmea_sentence(sentence: &str) -> bool {
// Check if sentence starts with $ and ends with * followed by checksum
if !sentence.starts_with('$') || !sentence.contains('*') {
return false;
}
// Check if sentence has at least 6 characters (minimum valid NMEA length)
if sentence.len() < 6 {
return false;
}
// Check if sentence has at least one comma (all NMEA sentences have fields)
if !sentence.contains(',') {
return false;
}
true
}
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize ESP-IDF // Initialize ESP-IDF
@ -25,20 +110,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("3. GPS VCC → 3.3V"); info!("3. GPS VCC → 3.3V");
info!("4. GPS GND → GND"); info!("4. GPS GND → GND");
info!("----------------------------------------"); info!("----------------------------------------");
info!("Please check if the red LED on the GPS module is lit"); info!("Please check if the blue LED on the GPS module is blinking");
info!("This indicates the module is receiving power"); info!("This indicates the module is receiving power");
info!("----------------------------------------"); info!("----------------------------------------");
// Initialize peripherals // Initialize peripherals
let peripherals = Peripherals::take().unwrap(); let peripherals = Peripherals::take().unwrap();
// Configure UART // Configure UART with specific settings
let config = UartConfig::default().baudrate(Hertz(GPS_BAUD)); let config = UartConfig::default().baudrate(Hertz(GPS_BAUD));
info!("Initializing UART with config: baudrate={}", GPS_BAUD); info!("Initializing UART with config: baudrate={}", GPS_BAUD);
// Create UART driver // Create UART driver with explicit pin configuration
let mut uart_driver = UartDriver::new( let uart_driver = UartDriver::new(
peripherals.uart2, peripherals.uart2,
peripherals.pins.gpio17, // TX peripherals.pins.gpio17, // TX
peripherals.pins.gpio16, // RX peripherals.pins.gpio16, // RX
@ -55,10 +140,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
FreeRtos::delay_ms(INIT_DELAY_MS); FreeRtos::delay_ms(INIT_DELAY_MS);
// Buffer for GPS data // Buffer for GPS data
let mut buffer = [0u8; 128]; let mut buffer = [0u8; UART_BUFFER_SIZE];
let mut timeout_count = 0; let mut timeout_count = 0;
let mut last_data_time = 0; let mut last_data_time = 0;
let mut start_time = 0; let start_time = 0;
let mut partial_sentence = String::new();
let mut last_position: Option<(f32, f32)> = None;
loop { loop {
let current_time = last_data_time + 1; let current_time = last_data_time + 1;
@ -68,44 +155,81 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(bytes_read) if bytes_read > 0 => { Ok(bytes_read) if bytes_read > 0 => {
timeout_count = 0; // Reset timeout counter on successful read timeout_count = 0; // Reset timeout counter on successful read
last_data_time = current_time; last_data_time = current_time;
let gps_data = String::from_utf8_lossy(&buffer[..bytes_read]);
info!("Raw GPS Data: {}", gps_data);
// Try to parse each line of GPS data // Convert received bytes to string, handling invalid UTF-8
for line in gps_data.lines() { let new_data = String::from_utf8_lossy(&buffer[..bytes_read]);
if line.starts_with("$GPGGA") { info!("Raw data received ({} bytes): {}", bytes_read, new_data);
let mut sentence = line.to_string();
match parse_gga(&mut sentence) { // Add new data to partial sentence
Ok(position) => { partial_sentence.push_str(&new_data);
info!("Successfully parsed GPS data:");
info!( // Process complete sentences
" Latitude: {} {}", while let Some(end_idx) = partial_sentence.find('\n') {
position.lat.abs(), let sentence = partial_sentence[..end_idx].trim().to_string();
if position.lat >= 0.0 { "N" } else { "S" } partial_sentence = partial_sentence[end_idx + 1..].to_string();
);
info!( // Only process valid NMEA sentences
" Longitude: {} {}", if is_valid_nmea_sentence(&sentence) {
position.long.abs(), info!("Complete sentence: {}", sentence);
if position.long >= 0.0 { "E" } else { "W" }
); if sentence.starts_with("$GPGGA") {
info!(" Altitude: {:.1} meters", position.alt); info!("Attempting to parse GGA sentence");
match parse_gga(&mut sentence.clone()) {
Ok(position) => {
info!("Successfully parsed GPS data from GGA:");
info!("{}", format_coordinates(position.lat, position.long));
info!(" Altitude: {:.1} meters", position.alt);
last_position = Some((position.lat, position.long));
}
Err(e) => {
info!("Error parsing GGA sentence:");
match e {
GpsError::InvalidMessageType => {
info!(" Invalid message type, expected GGA")
}
GpsError::InvalidLength => {
info!(" Invalid GGA sentence length")
}
GpsError::ParseError(msg) => {
info!(" Parse error: {}", msg)
}
GpsError::Other(msg) => info!(" Other error: {}", msg),
}
}
} }
Err(e) => { } else if sentence.starts_with("$GPGLL") {
info!("Error parsing GPS data:"); if let Some((lat, long)) = parse_gll(&sentence) {
match e { info!("Successfully parsed GPS data from GLL:");
GpsError::InvalidMessageType => { info!("{}", format_coordinates(lat, long));
info!(" Invalid message type, expected GGA") last_position = Some((lat, long));
} }
GpsError::InvalidLength => { } else if sentence.starts_with("$GPRMC") {
info!(" Invalid GGA sentence length") // Parse RMC for speed and course
} let parts: Vec<&str> = sentence.split(',').collect();
GpsError::ParseError(msg) => info!(" Parse error: {}", msg), if parts.len() >= 13 && parts[2] == "A" {
GpsError::Other(msg) => info!(" Other error: {}", msg), if let (Ok(speed), Ok(course)) =
(parts[7].parse::<f32>(), parts[8].parse::<f32>())
{
info!(" Speed: {:.1} knots", speed);
info!(" Course: {:.1}°", course);
} }
} }
} }
} }
} }
// If partial sentence is getting too long, clear it
if partial_sentence.len() > 1024 {
info!("Partial sentence too long, clearing buffer");
partial_sentence.clear();
}
if last_position != None {
info!("Last position: {:?}", last_position);
}
// Add a small delay between reads to prevent buffer overflow
FreeRtos::delay_ms(READ_DELAY_MS);
} }
Ok(_) => { Ok(_) => {
timeout_count += 1; timeout_count += 1;
@ -113,7 +237,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Only log every 10 timeouts to avoid spam // Only log every 10 timeouts to avoid spam
info!("No GPS data received (timeout #{})", timeout_count); info!("No GPS data received (timeout #{})", timeout_count);
info!("Please check:"); info!("Please check:");
info!("1. GPS module is powered (3.3V) - red LED should be lit"); info!("1. GPS module is powered (3.3V) - blue LED should be blinking");
info!("2. GPS TX is connected to ESP32 GPIO16 D16"); info!("2. GPS TX is connected to ESP32 GPIO16 D16");
info!("3. GPS RX is connected to ESP32 GPIO17 D17"); info!("3. GPS RX is connected to ESP32 GPIO17 D17");
info!("4. GPS GND is connected to ESP32 GND"); info!("4. GPS GND is connected to ESP32 GND");
@ -124,21 +248,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
// Check if we've received any data in the last second
if current_time - last_data_time > POWER_CHECK_INTERVAL {
info!("GPS Module Status:");
info!("1. Power LED should be lit (red)");
info!(
"2. No data received for {} seconds",
(current_time - last_data_time) / 1000
);
info!(
"3. Total runtime: {} seconds",
(current_time - start_time) / 1000
);
}
info!("----------------------------------------"); info!("----------------------------------------");
FreeRtos::delay_ms(3000); // Wait 3 seconds before next reading FreeRtos::delay_ms(1000); // Wait 1 second before next reading
} }
} }