# rustedbytes-nmea
[](https://crates.io/crates/rustedbytes-nmea)
[](https://docs.rs/rustedbytes-nmea)
[](https://opensource.org/licenses/MIT)
[](https://www.rust-lang.org)
[](https://github.com/mad4j/rustedbytes-nmea/actions/workflows/test.yml)
[]()
Rust `no_std` library for parsing NMEA messages from a GNSS receiver.
## Features
- `no_std` compatible - can be used in embedded systems
- **Stateless parser** - no internal buffers or state retention
- **Multi-byte parsing** - parse multiple bytes at once with bytes consumed tracking
- **Local time registration** - optionally record local reception time for each message
- **Multiconstellation support** - tracks which GNSS constellation provided each message
- GPS (GP), GLONASS (GL), Galileo (GA), BeiDou (GB/BD), Multi-GNSS (GN), QZSS (QZ)
- Supports common NMEA message types:
- GGA (Global Positioning System Fix Data)
- RMC (Recommended Minimum Navigation Information)
- GSA (GPS DOP and active satellites)
- GSV (GPS Satellites in view)
- GLL (Geographic Position - Latitude/Longitude)
- VTG (Track Made Good and Ground Speed)
- GNS (GNSS Fix Data)
- Handles spurious characters between messages
- Structured parameter extraction for each message type
## Usage
Add this to your `Cargo.toml`:
```toml
[dependencies]
rustedbytes-nmea = "0.1.0"
```
### Basic Example
```rust
use rustedbytes_nmea::{NmeaParser, MessageType, NmeaMessage, ParseError};
fn main() {
let parser = NmeaParser::new();
// NMEA sentence as bytes (can contain multiple messages or partial data)
let data = b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n";
// Parse the data
let result = parser.parse_bytes(data);
match result {
Ok((Some(message), bytes_consumed)) => {
// Successfully parsed a complete message
match message {
NmeaMessage::GGA(gga_data) => {
println!("GGA message from {:?}", gga_data.talker_id);
println!("Time: {}", gga_data.time());
println!("Latitude: {} {}", gga_data.latitude, gga_data.lat_direction);
println!("Longitude: {} {}", gga_data.longitude, gga_data.lon_direction);
println!("Altitude: {:?} {:?}", gga_data.altitude, gga_data.altitude_units);
println!("Satellites: {:?}", gga_data.num_satellites);
}
NmeaMessage::RMC(rmc_data) => {
println!("RMC message from {:?}", rmc_data.talker_id);
println!("Time: {}", rmc_data.time());
println!("Status: {}", rmc_data.status);
println!("Speed: {} knots", rmc_data.speed_knots);
}
_ => {} // Handle other message types
}
println!("Consumed {} bytes", bytes_consumed);
}
Ok((None, bytes_consumed)) => {
// Partial message or spurious data - need more bytes
println!("Partial message, consumed {} bytes", bytes_consumed);
}
Err((ParseError::InvalidMessage, bytes_consumed)) => {
// Complete but invalid message (e.g., missing mandatory fields)
println!("Invalid message found, consumed {} bytes", bytes_consumed);
}
Err((ParseError::InvalidChecksum, bytes_consumed)) => {
// Checksum verification failed
println!("Invalid checksum, consumed {} bytes", bytes_consumed);
}
}
}
```
### Streaming Example
```rust
use rustedbytes_nmea::{NmeaParser, MessageType};
fn main() {
let parser = NmeaParser::new();
// Simulate a stream of multiple NMEA sentences
let mut 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\
$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39\r\n".as_slice();
// Parse all messages in the stream
while !data.is_empty() {
match parser.parse_bytes(data) {
Ok((msg, consumed)) => {
if consumed == 0 {
// Partial message - would need more data in a real stream
break;
}
match msg {
Some(message) => {
println!("Parsed {:?} message", message.message_type());
}
None => {
// Spurious data consumed
println!("Consumed {} bytes of spurious data", consumed);
}
}
// Move to next message
data = &data[consumed..];
}
Err((error, consumed)) => {
println!("Parse error: {:?}, consumed {} bytes", error, consumed);
// Move past the invalid message
data = &data[consumed..];
}
}
}
}
```
### Multiconstellation Support
The library automatically tracks which GNSS constellation provided each message through the `talker_id` field:
```rust
use rustedbytes_nmea::{NmeaParser, TalkerId};
fn main() {
let parser = NmeaParser::new();
// Parse messages from different constellations
let sentences = [
b"$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n", // GPS
b"$GLGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n", // GLONASS
b"$GAGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n", // Galileo
b"$GNGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47\r\n", // Multi-GNSS
];
for sentence in &sentences {
if let Ok((Some(message), _)) = parser.parse_bytes(sentence) {
if let Some(gga_data) = message.as_gga() {
match gga_data.talker_id {
TalkerId::GP => println!("GPS fix: {}", gga_data.time()),
TalkerId::GL => println!("GLONASS fix: {}", gga_data.time()),
TalkerId::GA => println!("Galileo fix: {}", gga_data.time()),
TalkerId::GN => println!("Multi-GNSS fix: {}", gga_data.time()),
_ => println!("Other constellation fix"),
}
}
}
}
}
```
### Supported Constellations
| `GP` | GPS | Global Positioning System (USA) |
| `GL` | GLONASS | Russian satellite navigation |
| `GA` | Galileo | European satellite navigation |
| `GB` | BeiDou | Chinese satellite navigation (GBxxxx format) |
| `BD` | BeiDou | Chinese satellite navigation (BDxxxx format) |
| `GN` | Multi-GNSS | Combined data from multiple systems |
| `QZ` | QZSS | Japanese Quasi-Zenith Satellite System |
## API
### `NmeaParser`
The main parser structure. **The parser is now stateless** - it maintains no internal buffers or message storage.
#### Methods
- `new()` - Create a new parser instance
- `parse_bytes(data: &[u8]) -> Result<(Option<NmeaMessage>, usize), (ParseError, usize)>` - Parse bytes and return:
- `Ok((Some(message), bytes_consumed))` - Successfully parsed a complete, valid message
- `Ok((None, bytes_consumed))` - Partial message (need more data) or consumed spurious characters
- `Err((ParseError::InvalidMessage, bytes_consumed))` - Complete message but missing mandatory fields
- `Err((ParseError::InvalidChecksum, bytes_consumed))` - Checksum verification failed
### `ParseError`
Error types returned when parsing fails:
- `InvalidMessage` - Message is syntactically complete but missing mandatory fields or invalid
- `InvalidChecksum` - Checksum verification failed (not yet fully implemented)
### `NmeaMessage`
Enum representing a parsed NMEA message with associated data.
#### Variants
- `GGA(GgaData)` - Global Positioning System Fix Data
- `RMC(RmcData)` - Recommended Minimum Navigation Information
- `GSA(GsaData)` - GPS DOP and active satellites
- `GSV(GsvData)` - GPS Satellites in view
- `GLL(GllData)` - Geographic Position - Latitude/Longitude
- `VTG(VtgData)` - Track Made Good and Ground Speed
- `GNS(GnsData)` - GNSS Fix Data
#### Methods
- `message_type() -> MessageType` - Get the message type identifier
- `talker_id() -> TalkerId` - Get the talker ID (constellation identifier)
- `as_gga() -> Option<&GgaData>` - Extract GGA message parameters
- `as_rmc() -> Option<&RmcData>` - Extract RMC message parameters
- `as_gsa() -> Option<&GsaData>` - Extract GSA message parameters
- `as_gsv() -> Option<&GsvData>` - Extract GSV message parameters
- `as_gll() -> Option<&GllData>` - Extract GLL message parameters
- `as_vtg() -> Option<&VtgData>` - Extract VTG message parameters
- `as_gns() -> Option<&GnsData>` - Extract GNS message parameters
### `MessageType`
Enumeration of NMEA message type identifiers:
- `GGA` - Global Positioning System Fix Data
- `RMC` - Recommended Minimum Navigation Information
- `GSA` - GPS DOP and active satellites
- `GSV` - GPS Satellites in view
- `GLL` - Geographic Position - Latitude/Longitude
- `VTG` - Track Made Good and Ground Speed
- `GNS` - GNSS Fix Data
- `Unknown` - Unrecognized message type
### Parameter Structures
The library provides typed parameter structures for each NMEA message type, allowing structured access to message-specific fields.
#### `GgaData`
Global Positioning System Fix Data parameters:
- `time()` - **Mandatory** - UTC time (hhmmss format) - accessed via method
- `latitude` - **Mandatory** - Latitude value
- `lat_direction` - **Mandatory** - N or S
- `longitude` - **Mandatory** - Longitude value
- `lon_direction` - **Mandatory** - E or W
- `fix_quality` - **Mandatory** - Fix quality (0=invalid, 1=GPS fix, 2=DGPS fix, etc.)
- `num_satellites` - *Optional* - Number of satellites in use
- `hdop` - *Optional* - Horizontal Dilution of Precision
- `altitude` - *Optional* - Altitude above mean sea level
- `altitude_units` - *Optional* - Units of altitude (M for meters)
- `geoid_separation` - *Optional* - Height of geoid above WGS84 ellipsoid
- `geoid_units` - *Optional* - Units of geoid separation
- `age_of_diff` - *Optional* - Age of differential GPS data
- `diff_station_id()` - *Optional* - Differential reference station ID - accessed via method
**Note:** If any mandatory field is missing or cannot be parsed, the parser returns `None`.
#### `RmcData`
Recommended Minimum Navigation Information parameters:
- `time()` - **Mandatory** - UTC time (hhmmss format) - accessed via method
- `status` - **Mandatory** - Status (A=active/valid, V=void/invalid)
- `latitude` - **Mandatory** - Latitude value
- `lat_direction` - **Mandatory** - N or S
- `longitude` - **Mandatory** - Longitude value
- `lon_direction` - **Mandatory** - E or W
- `speed_knots` - **Mandatory** - Speed over ground in knots
- `track_angle` - **Mandatory** - Track angle in degrees
- `date()` - **Mandatory** - Date (ddmmyy format) - accessed via method
- `magnetic_variation` - *Optional* - Magnetic variation
- `mag_var_direction` - *Optional* - E or W
**Note:** If any mandatory field is missing or cannot be parsed, the parser returns `None`.
#### `GsaData`
GPS DOP and active satellites parameters:
- `mode` - **Mandatory** - Mode (M=manual, A=automatic)
- `fix_type` - **Mandatory** - Fix type (1=no fix, 2=2D, 3=3D)
- `satellite_ids` - *Optional* - Array of up to 12 satellite PRN numbers
- `pdop` - *Optional* - Position Dilution of Precision
- `hdop` - *Optional* - Horizontal Dilution of Precision
- `vdop` - *Optional* - Vertical Dilution of Precision
**Note:** If any mandatory field is missing or cannot be parsed, `as_gsa()` returns `None`.
#### `GsvData`
GPS Satellites in view parameters:
- `num_messages` - **Mandatory** - Total number of GSV messages
- `message_num` - **Mandatory** - Current message number
- `satellites_in_view` - **Mandatory** - Total number of satellites in view
- `satellite_info` - *Optional* - Array of up to 4 satellite information structures
Each `SatelliteInfo` contains:
- `prn` - *Optional* - Satellite PRN number
- `elevation` - *Optional* - Elevation in degrees (0-90)
- `azimuth` - *Optional* - Azimuth in degrees (0-359)
- `snr` - *Optional* - Signal-to-Noise Ratio in dB
**Note:** If any mandatory field is missing or cannot be parsed, `as_gsv()` returns `None`.
#### `GllData`
Geographic Position parameters:
- `latitude` - **Mandatory** - Latitude value
- `lat_direction` - **Mandatory** - N or S
- `longitude` - **Mandatory** - Longitude value
- `lon_direction` - **Mandatory** - E or W
- `time()` - **Mandatory** - UTC time (hhmmss format) - accessed via method
- `status` - **Mandatory** - Status (A=active/valid, V=void/invalid)
**Note:** If any mandatory field is missing or cannot be parsed, the parser returns `None`.
#### `VtgData`
Track Made Good and Ground Speed parameters (all fields are optional):
- `track_true` - *Optional* - True track angle
- `track_true_indicator` - *Optional* - T (true)
- `track_magnetic` - *Optional* - Magnetic track angle
- `track_magnetic_indicator` - *Optional* - M (magnetic)
- `speed_knots` - *Optional* - Speed in knots
- `speed_knots_indicator` - *Optional* - N (knots)
- `speed_kph` - *Optional* - Speed in kilometers per hour
- `speed_kph_indicator` - *Optional* - K (km/h)
**Note:** VTG messages can be parsed even with all fields empty, as all fields are optional.
#### `GnsData`
GNSS Fix Data parameters:
- `time()` - **Mandatory** - UTC time (hhmmss format) - accessed via method
- `latitude` - **Mandatory** - Latitude value
- `lat_direction` - **Mandatory** - N or S
- `longitude` - **Mandatory** - Longitude value
- `lon_direction` - **Mandatory** - E or W
- `mode_indicator()` - **Mandatory** - Position fix mode for each GNSS system - accessed via method
- `num_satellites` - **Mandatory** - Number of satellites in use
- `hdop` - *Optional* - Horizontal Dilution of Precision
- `altitude` - *Optional* - Altitude above mean sea level
- `geoid_separation` - *Optional* - Height of geoid above WGS84 ellipsoid
- `age_of_diff` - *Optional* - Age of differential GPS data
- `diff_station_id()` - *Optional* - Differential reference station ID - accessed via method
- `nav_status` - *Optional* - Navigation status indicator
**Note:** If any mandatory field is missing or cannot be parsed, the parser returns `None`.
## NMEA 0183 Compliance
For detailed information about the library's compliance with the NMEA 0183 standard, including supported and unsupported message types and fields, see the [NMEA 0183 Compliance Matrix](NMEA-183-COMPLIANCE.md).
## Testing
Run the test suite:
```bash
cargo test
```
## Contributing
Contributions are welcome! Please ensure:
- All tests pass (`cargo test`)
- Code is properly formatted (`cargo fmt`)
- No clippy warnings (`cargo clippy`)
For maintainers: See [RELEASE.md](RELEASE.md) for instructions on creating a new release.
## License
MIT License - see LICENSE file for details.