DEV Community

Cover image for Building a High-Precision RTK GPS Parser for Autonomous Robots in Rust
Armando Lopez de Elizalde
Armando Lopez de Elizalde

Posted on

Building a High-Precision RTK GPS Parser for Autonomous Robots in Rust


Building a High-Precision RTK GPS Parser for Autonomous Robots in Rust
In autonomous robotics, standard GPS is rarely accurate enough. Autonomous tractors, drones, and self-driving cars rely on Real-Time Kinematic (RTK) positioning to achieve centimeter-level accuracy.
To process this data at scale without sacrificing speed or safety, we need a high-performance parser. In this tutorial, we will build a production-grade NMEA ($GNGGA) sentence parser in Rust using the nom framework. We will design it to run efficiently on embedded robotic brains (like an NVIDIA Jetson or an ARM-based flight controller).
Why Rust for Robotics Navigation?
Zero-Cost Abstractions: Parse streaming byte data at native C/C++ speeds.
Memory Safety: Eliminate buffer overflows and segmentation faults common in legacy C robotics drivers.
Strict Typing: Prevent coordinate mixing bugs (e.g., mixing degrees and radians) at compile time.
Prerequisites
Ensure you have Rust installed. Create a new binary project:
bash
cargo new rtk_robot_parser
cd rtk_robot_parser

Use code with caution.
Add the following dependencies to your Cargo.toml:
toml
[dependencies]
nom = "7.1"
thiserror = "1.0"

Use code with caution.
Step 1: Define the Domain Types
We start by defining strict types for our GPS coordinates and fix quality. This ensures our robot cannot accidentally use an invalid or low-precision coordinate for navigation.
rust
// src/main.rs

[derive(Debug, PartialEq, Clone, Copy)]

pub enum GpsFixQuality {
Invalid = 0,
GpsSPS = 1,
DifferentialGPS = 2,
RTKFix = 4, // Centimeter-level accurate
RTKFloat = 5, // Decimeter-level accurate
}

impl From for GpsFixQuality {
fn from(val: u8) -> Self {
match val {
1 => GpsFixQuality::GpsSPS,
2 => GpsFixQuality::DifferentialGPS,
4 => GpsFixQuality::RTKFix,
5 => GpsFixQuality::RTKFloat,
_ => GpsFixQuality::Invalid,
}
}
}

[derive(Debug, PartialEq)]

pub struct RtkNavData {
pub utc_time: f64, // HHMMSS.SS
pub latitude: f64, // Decimal degrees
pub longitude: f64, // Decimal degrees
pub fix_quality: GpsFixQuality,
pub num_satellites: u8,
pub hdop: f32, // Horizontal Dilution of Precision
pub altitude_meters: f32,
}

Use code with caution.
Step 2: Implement the NMEA Coordinate Converter
NMEA outputs data in DDMM.MMMMM format. For robotics path planning, we need standard Decimal Degrees (DD.DDDDDD). Let's write a safe converter helper function.
rust
fn convert_to_decimal_degrees(raw_degree_minutes: f64, direction: &str) -> f64 {
let degrees = (raw_degree_minutes / 100.0).floor();
let minutes = raw_degree_minutes - (degrees * 100.0);
let decimal_degrees = degrees + (minutes / 60.0);

if direction == "S" || direction == "W" {
    -decimal_degrees
} else {
    decimal_degrees
}
Enter fullscreen mode Exit fullscreen mode

}

Use code with caution.
Step 3: Write the Streaming Parser Using Nom
Now, we use nom to parse a raw standard $GNGGA sentence byte-by-byte. This approach avoids string allocations, making it incredibly fast.
rust
use nom::{
bytes::complete::{tag, take_until, take_while_m_n},
character::complete::{char, digit1, multispace0},
combinator::{map, map_res},
sequence::tuple,
IResult,
};
use std::str::FromStr;

// Helper to parse float fields safely from byte slices
fn parse_f64(input: &[u8]) -> IResult<&[u8], f64> {
let (input, digested) = take_until(",")(input)?;
let parsed = f64::from_str(std::str::from_utf8(digested).unwrap_or("0.0")).unwrap_or(0.0);
Ok((input, parsed))
}

fn parse_f32(input: &[u8]) -> IResult<&[u8], f32> {
let (input, digested) = take_until(",")(input)?;
let parsed = f32::from_str(std::str::from_utf8(digested).unwrap_or("0.0")).unwrap_or(0.0);
Ok((input, parsed))
}

fn parse_u8(input: &[u8]) -> IResult<&[u8], u8> {
let (input, digested) = take_until(",")(input)?;
let parsed = u8::from_str(std::str::from_utf8(digested).unwrap_or("0")).unwrap_or(0);
Ok((input, parsed))
}

// Core parsing logic
pub fn parse_gngga(input: &[u8]) -> IResult<&[u8], RtkNavData> {
// 1. Match the NMEA header prefix
let (input, _) = tag("$GNGGA,")(input)?;

// 2. Extract raw CSV segments sequentially
let (input, utc_time) = parse_f64(input)?;
let (input, _) = tag(",")(input)?;

let (input, raw_lat) = parse_f64(input)?;
let (input, _) = tag(",")(input)?;
let (input, lat_dir) = take_until(",")(input)?;
let (input, _) = tag(",")(input)?;

let (input, raw_lon) = parse_f64(input)?;
let (input, _) = tag(",")(input)?;
let (input, lon_dir) = take_until(",")(input)?;
let (input, _) = tag(",")(input)?;

let (input, raw_fix) = parse_u8(input)?;
let (input, _) = tag(",")(input)?;

let (input, num_sats) = parse_u8(input)?;
let (input, _) = tag(",")(input)?;

let (input, hdop) = parse_f32(input)?;
let (input, _) = tag(",")(input)?;

let (input, altitude) = parse_f32(input)?;

// 3. Transform raw data into structured types
let latitude = convert_to_decimal_degrees(raw_lat, std::str::from_utf8(lat_dir).unwrap_or("N"));
let longitude = convert_to_decimal_degrees(raw_lon, std::str::from_utf8(lon_dir).unwrap_or("E"));
let fix_quality = GpsFixQuality::from(raw_fix);

Ok((
    input,
    RtkNavData {
        utc_time,
        latitude,
        longitude,
        fix_quality,
        num_satellites: num_sats,
        hdop,
        altitude_meters: altitude,
    },
))
Enter fullscreen mode Exit fullscreen mode

}

Use code with caution.
Step 4: Validate and Test the System
Let's add a main execution loop with a realistic test vector showcasing a live centimeter-level RTK fix (quality = 4).
rust
fn main() {
// Simulate raw streaming serial buffer incoming from an RTK Rover GPS module
let raw_rtk_stream = b"$GNGGA,123519.00,4807.03824,N,01131.00000,E,4,18,0.62,125.4,M,45.4,M,,*57";

println!("⚡ Initializing Robotic RTK Navigation Node...");

match parse_gngga(raw_rtk_stream) {
    Ok((_, nav_data)) => {
        println!("✅ Successfully Parsed High-Precision Telemetry Data!");
        println!("--------------------------------------------------");
        println!("🕒 UTC Time Stamp: {}", nav_data.utc_time);
        println!("📍 Coordinates   : {:.7}° N, {:.7}° E", nav_data.latitude, nav_data.longitude);
        println!("🛰️ Satellites    : {}", nav_data.num_satellites);
        println!("🎯 Precision HDOP: {}", nav_data.hdop);
        println!("🏔️ Altitude      : {} meters", nav_data.altitude_meters);

        // Critical Safety Guardrail for Autonomous Navigation
        if nav_data.fix_quality == GpsFixQuality::RTKFix {
            println!("🔒 Status        : RTK FIX ACTIVE [Centimeter Level Accuracy Verified]. Safe to execute path planning.");
        } else {
            println!("⚠️ Status        : POOR FIX QUALITY. Engaging emergency braking loop.");
        }
    }
    Err(e) => println!("🚨 Failed to parse NMEA stream payload: {:?}", e),
}
Enter fullscreen mode Exit fullscreen mode

}

Use code with caution.
Step 5: Verify the Execution Output
Run your project using Cargo:
bash
cargo run

Use code with caution.
You should see a clean, zero-allocation parse result layout outputted directly to your terminal:
text
⚡ Initializing Robotic RTK Navigation Node...

✅ Successfully Parsed High-Precision Telemetry Data!

🕒 UTC Time Stamp: 123519
📍 Coordinates : 48.1173040° N, 11.5166667° E
🛰️ Satellites : 18
🎯 Precision HDOP: 0.62
🏔️ Altitude : 125.4 meters
🔒 Status : RTK FIX ACTIVE [Centimeter Level Accuracy Verified]. Safe to execute path planning.

Use code with caution.
Key Takeaways for your GitHub / Dev.to readers:
Zero Allocations: Notice how we parsed the streaming raw bytes slice (&[u8]) directly without transforming the text blocks into intermediate String variables.
Compile-Time Domain Model Safety: By packaging strings directly into typed Enums (GpsFixQuality), downstream navigation controllers can confidently route paths without worrying about corrupt payloads causing unexpected crashes.

Advanced: Building an Asynchronous, Hardware-Abstracted RTK GPS Node in Rust
In a production robotic system, a navigation stack cannot afford to block the main control loop while waiting for slow serial hardware.
In this advanced extension, we will scale our RTK parser into a production-grade, multi-threaded navigation architecture. We will implement three advanced engineering patterns:
Hardware Abstraction: Utilizing embedded-hal traits so this code runs identically on bare-metal microcontrollers (STM32/ESP32) or Linux-based single-board computers (NVIDIA Jetson/Raspberry Pi).
Multi-Threaded Concurrency: Isolating the blocking serial hardware I/O driver on a background thread and passing safe, ownership-verified navigation payloads to the main flight controller using lock-free channels (std::sync::mpsc).
Rigorous Industrial Testing: Implementing a complete unit test suite to verify the parser parsing boundaries and safety guardrails.
mermaid
graph LR
Hardware[Serial UART Hardware] -->|embedded-hal Read| Driver[Thread 1: Driver Loop]
Driver -->|std::sync::mpsc Channel| Channel((Message Queue))
Channel -->|Thread 2: Main Navigation Stack| Planner[Robotic Path Planner]

Use code with caution.
Advanced Dependencies Configuration
Update your Cargo.toml file to include the required embedded traits:
toml
[dependencies]
nom = "7.1"
thiserror = "1.0"
embedded-hal = "1.0" # Standardized hardware abstraction layer

Use code with caution.
The Complete Production Implementation
Replace your entire src/main.rs file with this fully unified, self-contained implementation.
rust
// src/main.rs

use nom::{
bytes::complete::{tag, take_until},
IResult,
};
use std::str::FromStr;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use std::time::Duration;

// =========================================================================
// 1. DOMAIN DATA STRUCTURES & TYPE SAFEGUARDS
// =========================================================================

[derive(Debug, PartialEq, Clone, Copy)]

pub enum GpsFixQuality {
Invalid = 0,
GpsSPS = 1,
DifferentialGPS = 2,
RTKFix = 4, // Centimeter-level accuracy
RTKFloat = 5, // Decimeter-level accuracy
}

impl From for GpsFixQuality {
fn from(val: u8) -> Self {
match val {
1 => GpsFixQuality::GpsSPS,
2 => GpsFixQuality::DifferentialGPS,
4 => GpsFixQuality::RTKFix,
5 => GpsFixQuality::RTKFloat,
_ => GpsFixQuality::Invalid,
}
}
}

[derive(Debug, PartialEq, Clone)]

pub struct RtkNavData {
pub utc_time: f64,
pub latitude: f64,
pub longitude: f64,
pub fix_quality: GpsFixQuality,
pub num_satellites: u8,
pub hdop: f32,
pub altitude_meters: f32,
}

// =========================================================================
// 2. PARSING ENGINE (Zero-Allocation)
// =========================================================================

fn convert_to_decimal_degrees(raw_degree_minutes: f64, direction: &str) -> f64 {
let degrees = (raw_degree_minutes / 100.0).floor();
let minutes = raw_degree_minutes - (degrees * 100.0);
let decimal_degrees = degrees + (minutes / 60.0);

if direction == "S" || direction == "W" {
    -decimal_degrees
} else {
    decimal_degrees
}
Enter fullscreen mode Exit fullscreen mode

}

fn parse_f64(input: &[u8]) -> IResult<&[u8], f64> {
let (input, digested) = take_until(",")(input)?;
let parsed = f64::from_str(std::str::from_utf8(digested).unwrap_or("0.0")).unwrap_or(0.0);
Ok((input, parsed))
}

fn parse_f32(input: &[u8]) -> IResult<&[u8], f32> {
let (input, digested) = take_until(",")(input)?;
let parsed = f32::from_str(std::str::from_utf8(digested).unwrap_or("0.0")).unwrap_or(0.0);
Ok((input, parsed))
}

fn parse_u8(input: &[u8]) -> IResult<&[u8], u8> {
let (input, digested) = take_until(",")(input)?;
let parsed = u8::from_str(std::str::from_utf8(digested).unwrap_or("0")).unwrap_or(0);
Ok((input, parsed))
}

pub fn parse_gngga(input: &[u8]) -> IResult<&[u8], RtkNavData> {
let (input, _) = tag("$GNGGA,")(input)?;

let (input, utc_time) = parse_f64(input)?;
let (input, _) = tag(",")(input)?;

let (input, raw_lat) = parse_f64(input)?;
let (input, _) = tag(",")(input)?;
let (input, lat_dir) = take_until(",")(input)?;
let (input, _) = tag(",")(input)?;

let (input, raw_lon) = parse_f64(input)?;
let (input, _) = tag(",")(input)?;
let (input, lon_dir) = take_until(",")(input)?;
let (input, _) = tag(",")(input)?;

let (input, raw_fix) = parse_u8(input)?;
let (input, _) = tag(",")(input)?;

let (input, num_sats) = parse_u8(input)?;
let (input, _) = tag(",")(input)?;

let (input, hdop) = parse_f32(input)?;
let (input, _) = tag(",")(input)?;

let (input, altitude) = parse_f32(input)?;

let latitude = convert_to_decimal_degrees(raw_lat, std::str::from_utf8(lat_dir).unwrap_or("N"));
let longitude = convert_to_decimal_degrees(raw_lon, std::str::from_utf8(lon_dir).unwrap_or("E"));
let fix_quality = GpsFixQuality::from(raw_fix);

Ok((
    input,
    RtkNavData {
        utc_time,
        latitude,
        longitude,
        fix_quality,
        num_satellites: num_sats,
        hdop,
        altitude_meters: altitude,
    },
))
Enter fullscreen mode Exit fullscreen mode

}

// =========================================================================
// 3. HARDWARE ABSTRACTION LAYER (HAL) IMPLEMENTATION
// =========================================================================

/// Mock UART hardware driver implementing the explicit embedded_hal::serial::nb::Read trait.
/// This simulates real streaming registers of raw bytes coming into an embedded serial port.
pub struct MockUartHardware {
buffer: Vec,
position: usize,
}

impl MockUartHardware {
pub fn new(mock_data: &[u8]) -> Self {
Self {
buffer: mock_data.to_vec(),
position: 0,
}
}
}

// Implement standard embedded-hal protocol
impl embedded_hal::nb::serial::ErrorType for MockUartHardware {
type Error = std::convert::Infallible;
}

impl embedded_hal::nb::serial::Read for MockUartHardware {
fn read(&mut self) -> Result> {
if self.position >= self.buffer.len() {
// Signal to the robotic thread that the hardware buffer is empty, avoiding blocking
return Err(embedded_hal::nb::Error::WouldBlock);
}
let byte = self.buffer[self.position];
self.position += 1;
Ok(byte)
}
}

// =========================================================================
// 4. MULTI-THREADED ROBOTIC TELEMETRY ENGINE
// =========================================================================

/// Spawns the dedicated hardware I/O driver thread to ingest data concurrently.
pub fn spawn_hardware_driver_thread(
mut hardware: MockUartHardware,
tx_channel: Sender
) {
thread::spawn(move || {
let mut byte_accumulator = Vec::new();

    println!("[Driver Thread] Monitoring hardware UART interface...");

    loop {
        // Utilize non-blocking read calls specified by embedded-hal
        match hardware.read() {
            Ok(byte) => {
                // Check for standard carriage-return/newline frame boundaries
                if byte == b'\n' || byte == b'\r' {
                    if !byte_accumulator.is_empty() {
                        if let Ok((_, nav_payload)) = parse_gngga(&byte_accumulator) {
                            // Thread-safely pass ownership of parsed data across to the navigation thread
                            if tx_channel.send(nav_payload).is_err() {
                                println!("[Driver Thread] Channel disconnected. Shutting down.");
                                break;
                            }
                        }
                        byte_accumulator.clear();
                    }
                } else {
                    byte_accumulator.push(byte);
                }
            }
            Err(embedded_hal::nb::Error::WouldBlock) => {
                // Hardware is sleeping or spinning. Throttle thread to preserve processor utilization.
                thread::sleep(Duration::from_millis(10));
            }
            Err(_) => {
                println!("[Driver Thread] Critical hardware register fault detected.");
                break;
            }
        }
    }
});
Enter fullscreen mode Exit fullscreen mode

}

// =========================================================================
// 5. MAIN SYSTEM APPLICATION ENTRYPOINT
// =========================================================================

fn main() {
println!("⚡ Initializing Asynchronous Robotic Navigation Core Node...");

// Construct a realistic, dynamic streaming data buffer mimicking hardware outputs
let simulated_hardware_feed = b"$GNGGA,123519.00,4807.03824,N,01131.00000,E,4,18,0.62,125.4,M,45.4,M,,*57\r\n";

let hardware_driver = MockUartHardware::new(simulated_hardware_feed);
let (tx, rx): (Sender<RtkNavData>, Receiver<RtkNavData>) = channel();

// Spawn our background parser concurrency worker
spawn_hardware_driver_thread(hardware_driver, tx);

println!("[Main Thread] Path Planning Engine Engaged. Awaiting precision positioning synchronization...");

// Main Control/Flight Path Loop
let mut messages_processed = 0;
while messages_processed < 1 {
    if let Ok(telemetry) = rx.recv_timeout(Duration::from_secs(2)) {
        println!("\n📥 [Main Thread] Telemetry Intercepted via IPC Channels:");
        println!("   Coordinates : {:.7}° N, {:.7}° E", telemetry.latitude, telemetry.longitude);
        println!("   Satellites  : {} connected nodes", telemetry.num_satellites);
        println!("   Altitude    : {} meters above ellipsoid", telemetry.altitude_meters);

        if telemetry.fix_quality == GpsFixQuality::RTKFix {
            println!("   🔒 NAV STATUS: [CENTIMETER RTK ACCURACY VALIDATED] Path planning safe to execute.");
        } else {
            println!("   ⚠️ NAV STATUS: [DEGRADED PRECISION] Disengaging autonomies.");
        }
        messages_processed += 1;
    } else {
        println!("[Main Thread] Watchdog timeout: GPS hardware stopped responding.");
        break;
    }
}
println!("\n⚡ Navigation Core shut down successfully.");
Enter fullscreen mode Exit fullscreen mode

}

// =========================================================================
// 6. INDUSTRIAL UNIT TESTING SUITE
// =========================================================================

[cfg(test)]

mod tests {
use super::*;

#[test]
fn test_valid_rtk_fix_parsing() {
    let stream = b"$GNGGA,123519.00,4807.03824,N,01131.00000,E,4,18,0.62,125.4,M,45.4,M,,*57";
    let result = parse_gngga(stream);

    assert!(result.is_ok());
    let (_, data) = result.unwrap();
    assert_eq!(data.fix_quality, GpsFixQuality::RTKFix);
    assert_eq!(data.num_satellites, 18);
    assert!((data.latitude - 48.117304).abs() < 1e-5);
}

#[test]
fn test_invalid_fix_quality_fallback() {
    // Fix quality set to '0' (Invalid)
    let stream = b"$GNGGA,123519.00,4807.03824,N,01131.00000,E,0,00,9.99,0.0,M,0.0,M,,*57";
Enter fullscreen mode Exit fullscreen mode

Use code with caution.
let result = parse_gngga(stream);
assert!(result.is_ok());
let (_, data) = result.unwrap();
assert_eq!(data.fix_quality, GpsFixQuality::Invalid);
assert_eq!(data.num_satellites, 0);
}

[test]

fn test_coordinate_conversion_cardinality() {
// Verify Western/Southern Hemispheres yield exact negative values
let south_lat = convert_to_decimal_degrees(3723.456, "S");
let west_lon = convert_to_decimal_degrees(12205.123, "W");
assert!(south_lat < 0.0);
assert!(west_lon < 0.0);
}
}

Running the Advanced Workspace

To verify everything compiles, passes unit safety tests, and runs flawlessly, execute these standard commands:

  1. Verify Unit Tests pass cleanly:
   cargo test

Execute the concurrent hardware architecture simulation:
bash
cargo run
Use code with caution.


Building a Production-Grade, Multi-Threaded RTK GPS Robotic Navigation Node in Rust
In autonomous robotics—such as self-driving vehicles, agricultural drones, and industrial rovers—standard GPS accuracy is insufficient. These systems rely on Real-Time Kinematic (RTK) positioning to achieve centimeter-level precision.
To process high-frequency streaming sensor telemetry without injecting latency or risking safety critical memory faults, we need a zero-overhead, highly concurrent hardware abstraction driver.
In this comprehensive, production-grade guide, we will implement an RTK GPS NMEA-0183 ($GNGGA) sentence parser in Rust from scratch. This architecture is designed for deployment on embedded robotic brains—whether running bare-metal microcontrollers or high-compute Linux environments like an NVIDIA Jetson.
mermaid
graph LR
    Hardware[Serial UART Hardware] -->|embedded-hal Read| Driver[Thread 1: Driver Loop]
    Driver -->|std::sync::mpsc Channel| Channel((Message Queue))
    Channel -->|Thread 2: Main Navigation Stack| Planner[Robotic Path Planner]

Use code with caution.
Architectural Blueprint & Technical Strategy
To meet rigorous industrial robotics standards, our implementation covers five pillars:
Zero-Copy Byte Parsing: Utilizing the nom parser combinator library to ingest raw serial stream bytes (&[u8]) directly, avoiding heap allocations or invalid UTF-8 string conversions.
Compile-Time Domain Safety: Packaging loose numerical coordinates and strings into strictly typed structures and safe Enums (GpsFixQuality).
Cross-Platform Hardware Abstraction (HAL): Implementing core I/O abstractions via the standardized embedded-hal ecosystem. This code runs identically on embedded bare-metal targets or POSIX Linux operating systems.
Lock-Free Concurrency: Spawning a dedicated high-priority background hardware reader thread that dispatches thread-safe coordinates over a bounded Multi-Producer Single-Consumer (std::sync::mpsc) channel to the main trajectory planner.
Industrial Test Coverage: Implementing a comprehensive unit testing suite to evaluate system behavior against valid fixes, degraded positions, and negative hemisphere coordinates.
Setting Up Your Project Workspace
Create a brand new Rust binary workspace via your terminal:
bash
cargo new rtk_robot_parser --bin
cd rtk_robot_parser

Use code with caution.
Open your Cargo.toml file and replace its contents with the production dependencies below:
toml
[package]
name = "rtk_robot_parser"
version = "0.1.0"
edition = "2021"

[dependencies]
nom = "7.1"
thiserror = "1.0"
embedded-hal = "1.0" # Standardized industrial hardware traits

Use code with caution.
The Complete Unified Architecture Code

rust
// src/main.rs

use nom::{
    bytes::complete::{tag, take_until},
    IResult,
};
use std::str::FromStr;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread;
use std::time::Duration;

// =========================================================================
// 1. DOMAIN DATA STRUCTURES & TYPE SAFEGUARDS
// =========================================================================

/// Verifiable precision tiers of an RTK Receiver GNSS module.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum GpsFixQuality {
    Invalid =
0,
    GpsSPS = 1,
    DifferentialGPS = 2,
    RTKFix = 4,   // Centimeter-level accuracy (Carrier-phase fixed)
    RTKFloat = 5, // Decimeter-level accuracy (Carrier-phase floating)
}

impl From<u8> for GpsFixQuality {
    fn from(val: u8) -> Self {
        match val {
            1 => GpsFixQuality::GpsSPS,
            2 => GpsFixQuality::DifferentialGPS,
            4 => GpsFixQuality::RTKFix,
            5 => GpsFixQuality::RTKFloat,
            _ => GpsFixQuality::Invalid,
        }
    }
}

/// Structured, fully evaluated precision telemetry from a verified NMEA sentence.
#[derive(Debug, PartialEq, Clone)]
pub struct RtkNavData {
    pub utc_time: f64,        // Format: HHMMSS.SS
    pub latitude: f64,        // Converted directly to Decimal Degrees
    pub longitude: f64,       // Converted directly to Decimal Degrees
    pub fix_quality: GpsFixQuality,
    pub num_satellites: u8,
    pub hdop: f32,            // Horizontal Dilution of Precision
    pub altitude_meters: f32, // Height above local mean sea level
}

// =========================================================================
// 2. PARSING ENGINE (Zero-Allocation, No-String Hex Tokens)
// =========================================================================

/// Converts legacy NMEA `DDMM.MMMMM` format string arrays into decimal degrees (`DD.DDDDDD`).
fn convert_to_decimal_degrees(raw_degree_minutes: f64, direction: &str) -> f64 {
    let degrees = (raw_degree_minutes / 100.0).floor();
    let minutes = raw_degree_minutes - (degrees * 100.0);
    let decimal_degrees = degrees + (minutes / 60.0);

    if direction == "S" || direction == "W" {
        -decimal_degrees
    } else {
        decimal_degrees
    }
}

fn parse_f64(input: &[u8]) -> IResult<&[u8], f64> {
    let (input, digested) = take_until(",")(input)?;
    let parsed = f64::from_str(std::str::from_utf8(digested).unwrap_or("0.0")).unwrap_or(0.0);
    Ok((input, parsed))
}

fn parse_f32(input: &[u8]) -> IResult<&[u8], f32> {
    let (input, digested) = take_until(",")(input)?;
    let parsed = f32::from_str(std::str::from_utf8(digested).unwrap_or("0.0")).unwrap_or(0.0);
    Ok((input, parsed))
}

fn parse_u8(input: &[u8]) -> IResult<&[u8], u8> {
    let (input, digested) = take_until(",")(input)?;
    let parsed = u8::from_str(std::str::from_utf8(digested).unwrap_or("0")).unwrap_or(0);
    Ok((input, parsed))
}

/// Zero-copy parsing engine extracting values directly out of streaming binary bytes.
pub fn parse_gngga(input: &[u8]) -> IResult<&[u8], RtkNavData> {
    // Confirm the sentence matches standard global navigation configurations
    let (input, _) = tag("$GNGGA,")(input)?;

    let (input, utc_time) = parse_f64(input)?;
    let (input, _) = tag(",")(input)?;

    let (input, raw_lat) = parse_f64(input)?;
    let (input, _) = tag(",")(input)?;
    let (input, lat_dir) = take_until(",")(input)?;
    let (input, _) = tag(",")(input)?;

    let (input, raw_lon) = parse_f64(input)?;
    let (input, _) = tag(",")(input)?;
    let (input, lon_dir) = take_until(",")(input)?;
    let (input, _) = tag(",")(input)?;

    let (input, raw_fix) = parse_u8(input)?;
    let (input, _) = tag(",")(input)?;

    let (input, num_sats) = parse_u8(input)?;
    let (input, _) = tag(",")(input)?;

    let (input, hdop) = parse_f32(input)?;
    let (input, _) = tag(",")(input)?;

    let (input, altitude) = parse_f32(input)?;

    let latitude = convert_to_decimal_degrees(raw_lat, std::str::from_utf8(lat_dir).unwrap_or("N"));
    let longitude = convert_to_decimal_degrees(raw_lon, std::str::from_utf8(lon_dir).unwrap_or("E"));
    let fix_quality = GpsFixQuality::from(raw_fix);

    Ok((
        input,
        RtkNavData {
            utc_time,
            latitude,
            longitude,
            fix_quality,
            num_satellites: num_sats,
            hdop,
            altitude_meters: altitude,
        },
    ))
}

// =========================================================================
// 3. HARDWARE ABSTRACTION LAYER (HAL) DRIVER
// =========================================================================

/// Mock UART hardware interface matching real bare-metal embedded registers.
pub struct MockUartHardware {
    buffer: Vec<u8>,
    position: usize,
}

impl MockUartHardware {
    pub fn new(mock_data: &[u8]) -> Self {
        Self {
            buffer: mock_data.to_vec(),
            position: 0,
        }
    }
}

// Associate error types natively required by embedded-hal interfaces
impl embedded_hal::nb::serial::ErrorType for MockUartHardware {
    type Error = std::convert::Infallible;
}

// Fully execute standard, non-blocking serial read traits
impl embedded_hal::nb::serial::Read<u8> for MockUartHardware {
    fn read(&mut self) -> Result<u8, embedded_hal::nb::Error<Self::Error>> {
        if self.position >= self.buffer.len() {
            // Non-blocking catch alerting thread to safely backoff without freezing
            return Err(embedded_hal::nb::Error::WouldBlock);
        }
        let byte = self.buffer[self.position];
        self.position += 1;
        Ok(byte)
    }
}

// =========================================================================
// 4. LOCK-FREE CONCURRENT ENGINE
// =========================================================================

/// Spawns a dedicated low-latency hardware listener running outside the path planner loop.
pub fn spawn_hardware_driver_thread(
    mut hardware: MockUartHardware, 
    tx_channel: Sender<RtkNavData>
) {
    thread::spawn(move || {
        let mut byte_accumulator = Vec::new();

        println!("[Driver Thread] Ingesting hardware serial registers...");

        loop {
            match hardware.read() {
                Ok(byte) => {
                    // Look for carriage return / line feed demarcating an NMEA sequence boundary
                    if byte == b'\n' || byte == b'\r' {
                        if !byte_accumulator.is_empty() {
                            if let Ok((_, nav_payload)) = parse_gngga(&byte_accumulator) {
                                // Thread-safely push ownership of coordinates across the memory channel
                                if tx_channel.send(nav_payload).is_err() {
                                    println!("[Driver Thread] Control thread closed channels. Exiting.");
                                    break;
                                }
                            }
                            byte_accumulator.clear();
                        }
                    } else {
                        byte_accumulator.push(byte);
                    }
                }
                Err(embedded_hal::nb::Error::WouldBlock) => {
                    // Serial bus buffer is empty. Sleep driver briefly to avoid CPU thrashing.
                    thread::sleep(Duration::from_millis(10));
                }
                Err(_) => {
                    println!("[Driver Thread] Unrecoverable hardware register fault encountered.");
                    break;
                }
            }
        }
    });
}

// =========================================================================
// 5. APPLICATION RUNTIME ENGINE
// =========================================================================

fn main() {
    println!("⚡ Starting Real-Time Robotic Navigation Core...");

    // Simulating an incoming telemetry frame directly from an operational GPS receiver
    let simulated_hardware_feed = b"$GNGGA,123519.00,4807.03824,N,01131.00000,E,4,18,0.62,125.4,M,45.4,M,,*57\r\n";

    let hardware_driver = MockUartHardware::new(simulated_hardware_feed);
    let (tx, rx): (Sender<RtkNavData>, Receiver<RtkNavData>) = channel();

    // Fire up background thread worker
    spawn_hardware_driver_thread(hardware_driver, tx);

    println!("[Main Thread] Path Planning Engine online. Synching localization arrays...");

    let mut messages_processed = 0;
    while messages_processed < 1 {
        // High safety timeout guard prevents vehicle drift if hardware disconnects
        if let Ok(telemetry) = rx.recv_timeout(Duration::from_secs(2)) {
            println!("\n📥 [Main Thread] Telemetry Intercepted via Internal Bus:");
            println!("   Coordinates : {:.7}° N, {:.7}° E", telemetry.latitude, telemetry.longitude);
            println!("   Satellites  : {} constellations tracked", telemetry.num_satellites);
            println!("   Altitude    : {} meters above WGS84", telemetry.altitude_meters);

            if telemetry.fix_quality == GpsFixQuality::RTKFix {
                println!("   🔒 NAV STATUS: [CENTIMETER RTK FIX ACQUIRED] Trajectory modifications authorized.");
            } else {
                println!("   ⚠️ NAV STATUS: [INSUFFICIENT POSITION PRECISION] Stopping thrusters.");
            }
            messages_processed += 1;
        } else {
            println!("[Main Thread] Watchdog Exception: No telemetry frames detected inside safety window.");
            break;
        }
    }
    println!("\n⚡ Navigation Core shut down successfully.");
}

// =========================================================================
// 6. CONTINUOUS INTEGRATION & SYSTEM TEST COVERAGE
// =========================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_valid_rtk_fix_parsing() {

Use code with caution.
let stream = b"$GNGGA,123519.00,4807.03824,N,01131.00000,E,4,18,0.62,125.4,M,45.4,M,,*57";
let result = parse_gngga(stream);
assert!(result.is_ok());
let (_, data) = result.unwrap();
assert_eq!(data.fix_quality, GpsFixQuality::RTKFix);
assert_eq!(data.num_satellites, 18);
assert!((data.latitude - 48.117304).abs() < 1e-5);
}
#[test]
fn test_invalid_fix_quality_fallback() {
let stream = b"$GNGGA,123519.00,4807.03824,N,01131.00000,E,0,00,9.99,0.0,M,0.0,M,,*57";
let result = parse_gngga(stream);
assert!(result.is_ok());
let (_, data) = result.unwrap();
assert_eq!(data.fix_quality, GpsFixQuality::Invalid);
assert_eq!(data.num_satellites, 0);
}
#[test]
fn test_coordinate_conversion_cardinality() {
let south_lat = convert_to_decimal_degrees(3723.456, "S");
let west_lon = convert_to_decimal_degrees(12205.123, "W");
assert!(south_lat < 0.0);
assert!(west_lon < 0.0);
}
}

## Compilation and Validation Pipeline
To ensure the code is error-free, run the test and simulation verification suite locally:

1. **Execute the Unit Testing Engine**:
Enter fullscreen mode Exit fullscreen mode


bash
cargo test

Execute the Concurrent Multi-Threaded Simulator:
bash
cargo run

Top comments (0)