Python library for parsing and generating NMEA 0183 protocol messages used in GPS and marine navigation systems
npx @tessl/cli install tessl/pypi-pynmea2@1.19.0A comprehensive Python library for parsing and generating NMEA 0183 protocol messages commonly used in GPS and marine navigation systems. The library provides a clean API for parsing individual NMEA sentences into structured Python objects with easy access to GPS coordinates, timestamps, and other navigation data.
pip install pynmea2import pynmea2Import specific classes and functions:
from pynmea2 import (
parse, NMEASentence, NMEAStreamReader, NMEAFile,
ParseError, ChecksumError, SentenceTypeError
)Import specific sentence types:
from pynmea2 import GGA, RMC, GSA, GSV # GPS sentence typesAccess version information:
import pynmea2
print(pynmea2.__version__) # '1.19.0'
print(pynmea2.version) # '1.19.0' (alias)import pynmea2
# Parse a single NMEA sentence
nmea_string = "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D"
msg = pynmea2.parse(nmea_string)
# Access parsed data
print(f"Timestamp: {msg.timestamp}") # datetime.time(18, 43, 53)
print(f"Latitude: {msg.latitude}") # -19.4840833333 (decimal degrees)
print(f"Longitude: {msg.longitude}") # 24.1751 (decimal degrees)
print(f"GPS Quality: {msg.gps_qual}") # '1'
print(f"Satellites: {msg.num_sats}") # '04'
print(f"Altitude: {msg.altitude}") # 100.0
# Generate NMEA sentence from data
new_msg = pynmea2.GGA('GP', 'GGA', ('184353.07', '1929.045', 'S', '02410.506', 'E', '1', '04', '2.6', '100.00', 'M', '-33.9', 'M', '', '0000'))
print(str(new_msg)) # $GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6Dpynmea2 uses a class hierarchy with automatic sentence type detection and parsing:
Parse individual NMEA sentences into structured objects and generate NMEA strings from data.
def parse(line: str, check: bool = False) -> NMEASentence:
"""Parse NMEA sentence string into appropriate sentence object."""class NMEASentence:
def __init__(self):
"""Base class for all NMEA sentences."""
@staticmethod
def parse(line: str, check: bool = False) -> 'NMEASentence':
"""Parse NMEA sentence string."""
@staticmethod
def checksum(nmea_str: str) -> int:
"""Calculate XOR checksum for NMEA string."""
def render(self, checksum: bool = True, dollar: bool = True, newline: bool = False) -> str:
"""Render sentence as NMEA string."""Access GPS positioning data including latitude, longitude, altitude, and quality indicators.
class GGA(TalkerSentence):
"""Global Positioning System Fix Data."""
timestamp: datetime.time
lat: str
lat_dir: str
lon: str
lon_dir: str
gps_qual: str
num_sats: str
horizontal_dil: str
altitude: float
altitude_units: str
@property
def latitude(self) -> float:
"""Latitude in decimal degrees."""
@property
def longitude(self) -> float:
"""Longitude in decimal degrees."""
@property
def is_valid(self) -> bool:
"""True if GPS quality indicates valid fix."""class RMC(TalkerSentence):
"""Recommended Minimum Specific GPS/TRANSIT Data."""
timestamp: datetime.time
status: str
lat: str
lat_dir: str
lon: str
lon_dir: str
spd_over_grnd: str
true_course: str
datestamp: datetime.date
@property
def latitude(self) -> float:
"""Latitude in decimal degrees."""
@property
def longitude(self) -> float:
"""Longitude in decimal degrees."""
@property
def datetime(self) -> datetime.datetime:
"""Combined date and time."""
@property
def is_valid(self) -> bool:
"""True if status and mode indicators are valid."""Access navigation data including headings, bearings, waypoints, and course information.
class BWC(TalkerSentence):
"""Bearing & Distance to Waypoint - Great Circle."""
timestamp: datetime.time
lat_next: str
lat_next_direction: str
lon_next: str
lon_next_direction: str
true_track: str
mag_track: str
range_next: str
waypoint_name: strclass HDG(TalkerSentence):
"""Heading, Deviation and Variation."""
heading: str
deviation: str
dev_dir: str
variation: str
var_dir: strNavigation and Course Sentences
Access meteorological data including wind speed, direction, temperature, and atmospheric conditions.
class MWV(TalkerSentence):
"""Wind Speed and Angle."""
wind_angle: str
reference: str
wind_speed: str
wind_speed_units: str
status: str
@property
def is_valid(self) -> bool:
"""True if status indicates valid data."""class MDA(TalkerSentence):
"""Meteorological Composite."""
b_pressure_inch: str
b_pressure_bar: str
air_temp: str
water_temp: str
rel_humidity: str
wind_speed_knots: str
wind_speed_meters: strAccess depth measurement data from various sonar and depth finding equipment.
class DBT(TalkerSentence):
"""Depth Below Transducer."""
depth_feet: str
unit_feet: str
depth_meters: str
unit_meters: str
depth_fathoms: str
unit_fathoms: strclass DPT(TalkerSentence):
"""Depth of Water."""
depth: str
offset: str
range: strProcess continuous streams of NMEA data from files or serial connections.
class NMEAStreamReader:
"""Reads NMEA sentences from a stream."""
def __init__(self, stream=None, errors: str = 'raise'):
"""
Create NMEAStreamReader object.
Args:
stream: File-like object with readline() method
errors: Error handling - 'raise', 'yield', or 'ignore'
"""
def next(self, data: str = None) -> Iterator[NMEASentence]:
"""Parse data and yield NMEA sentence objects."""
def __iter__(self) -> Iterator[List[NMEASentence]]:
"""Iterator protocol support."""class NMEAFile:
"""File reader for NMEA sentences."""
def __init__(self, f, *args, **kwargs):
"""Open NMEA file for reading."""
def __iter__(self) -> Iterator[NMEASentence]:
"""Iterate through file yielding NMEA sentences."""
def read(self) -> List[NMEASentence]:
"""Read all sentences as list."""
def __enter__(self):
"""Context manager entry."""
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit."""Support for manufacturer-specific NMEA sentence extensions from GPS and marine equipment manufacturers.
class ProprietarySentence(NMEASentence):
"""Base class for proprietary manufacturer sentences."""
manufacturer: str
data: List[str]Supported manufacturers include:
Helper functions for coordinate conversion, timestamp parsing, and validation.
def timestamp(s: str) -> datetime.time:
"""Convert HHMMSS[.ss] string to datetime.time object."""
def datestamp(s: str) -> datetime.date:
"""Convert DDMMYY string to datetime.date object."""
def dm_to_sd(dm: str) -> float:
"""Convert degrees/minutes format to signed decimal degrees."""
def valid(s: str) -> bool:
"""Check if status flag equals 'A' (active/valid)."""class LatLonFix:
"""Mixin adding latitude/longitude properties as signed decimals."""
@property
def latitude(self) -> float:
"""Latitude in signed decimal degrees."""
@property
def longitude(self) -> float:
"""Longitude in signed decimal degrees."""
@property
def latitude_minutes(self) -> float:
"""Latitude minutes component."""
@property
def longitude_minutes(self) -> float:
"""Longitude minutes component."""class ParseError(ValueError):
"""
Base exception for NMEA parsing errors.
Args:
message: Error description
data: Raw data that caused the error
"""
def __init__(self, message: str, data: str):
"""Initialize with error message and raw data."""
class ChecksumError(ParseError):
"""
Raised when NMEA sentence checksum validation fails.
Inherits message and data parameters from ParseError.
"""
class SentenceTypeError(ParseError):
"""
Raised when sentence type is not recognized.
Inherits message and data parameters from ParseError.
"""All parsing exceptions include the error message and the raw data that caused the error for debugging purposes.
from typing import List, Optional, Union, Iterator
from datetime import time, date, datetime
# Sentence data is stored as list of strings
SentenceData = List[str]
# Parse function can accept optional checksum validation
ChecksumValidation = bool