Python library for parsing and generating NMEA 0183 protocol messages used in GPS and marine navigation systems
77
The core parsing system in pynmea2 provides functions and classes for converting between NMEA sentence strings and structured Python objects. This includes parsing incoming NMEA data, generating NMEA strings from data, and validating checksums.
def parse(line: str, check: bool = False) -> NMEASentence:
"""
Parse a string representing a NMEA 0183 sentence.
Args:
line: NMEA sentence string (leading '$' optional, trailing whitespace ignored)
check: If True, raise ChecksumError if checksum is missing
Returns:
NMEASentence object of appropriate subclass
Raises:
ParseError: If string cannot be parsed
ChecksumError: If checksum validation fails or missing when check=True
SentenceTypeError: If sentence type is not recognized
"""import pynmea2
# Parse with checksum validation (default)
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D")
print(type(msg)) # <class 'pynmea2.types.talker.GGA'>
# Parse without requiring checksum
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000", check=False)
# Parse with strict checksum requirement
try:
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000", check=True)
except pynmea2.ChecksumError:
print("Checksum missing - required when check=True")class NMEASentence:
"""Base class for all NMEA sentence types."""
data: List[str]
sentence_types: Dict[str, Type['NMEASentence']]
@staticmethod
def parse(line: str, check: bool = False) -> 'NMEASentence':
"""
Parse NMEA sentence string into appropriate sentence object.
Args:
line: NMEA sentence string
check: Require checksum validation
Returns:
Appropriate NMEASentence subclass instance
"""
@staticmethod
def checksum(nmea_str: str) -> int:
"""
Calculate XOR checksum for NMEA string.
Args:
nmea_str: NMEA string without '$' or checksum
Returns:
Calculated checksum as integer
"""
def render(self, checksum: bool = True, dollar: bool = True, newline: Union[bool, str] = False) -> str:
"""
Render sentence as NMEA string.
Args:
checksum: Include checksum (*HH format)
dollar: Include leading '$'
newline: Include trailing newline (True = \\r\\n, str = custom)
Returns:
Formatted NMEA sentence string
"""
def identifier(self) -> str:
"""Return string identifier for sentence type (abstract method)."""
def __str__(self) -> str:
"""Return rendered NMEA sentence with checksum and dollar sign."""
def __repr__(self) -> str:
"""Return detailed representation showing field names and values."""import pynmea2
# Parse sentence
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D")
# Generate NMEA string with different formatting options
print(msg.render()) # $GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D
print(msg.render(checksum=False)) # $GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000
print(msg.render(dollar=False)) # GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D
print(msg.render(newline=True)) # $GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D\r\n
# Calculate checksum manually
nmea_str = "GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000"
checksum = pynmea2.NMEASentence.checksum(nmea_str)
print(f"Checksum: {checksum:02X}") # Checksum: 6D
# String representations
print(str(msg)) # $GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D
print(repr(msg)) # <GGA(timestamp=datetime.time(18, 43, 53), lat='1929.045', ...)>class TalkerSentence(NMEASentence):
"""Base class for standard NMEA talker sentences."""
talker: str
sentence_type: str
data: List[str]
def __init__(self, talker: str, sentence_type: str, data: List[str]):
"""
Initialize talker sentence.
Args:
talker: Two-character talker ID (e.g., 'GP', 'GL', 'GA')
sentence_type: Three-character sentence type (e.g., 'GGA', 'RMC')
data: List of sentence field data as strings
"""
def identifier(self) -> str:
"""Return sentence identifier (e.g., 'GPGGA,')."""import pynmea2
# Create GGA sentence manually
gga_data = ['184353.07', '1929.045', 'S', '02410.506', 'E', '1', '04', '2.6', '100.00', 'M', '-33.9', 'M', '', '0000']
msg = pynmea2.GGA('GP', 'GGA', gga_data)
print(msg.talker) # 'GP'
print(msg.sentence_type) # 'GGA'
print(msg.identifier()) # 'GPGGA,'
print(str(msg)) # $GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6Dclass ProprietarySentence(NMEASentence):
"""Base class for proprietary manufacturer sentences."""
manufacturer: str
data: List[str]
def __init__(self, manufacturer: str, data: List[str]):
"""
Initialize proprietary sentence.
Args:
manufacturer: Three-character manufacturer code (e.g., 'ASH', 'GRM')
data: List of sentence field data as strings
"""
def identifier(self) -> str:
"""Return sentence identifier (e.g., 'PASH')."""import pynmea2
# Parse proprietary sentence
msg = pynmea2.parse("$PGRME,15.0,M,45.0,M,25.0,M*22")
print(type(msg)) # <class 'pynmea2.types.proprietary.grm.GRME'>
print(msg.manufacturer) # 'GRM'
print(msg.identifier()) # 'PGRM'
# Create proprietary sentence manually
data = ['15.0', 'M', '45.0', 'M', '25.0', 'M']
msg = pynmea2.GRME('GRM', data)
print(str(msg)) # $PGRME,15.0,M,45.0,M,25.0,M*22class QuerySentence(NMEASentence):
"""Class for NMEA query sentences."""
talker: str
listener: str
sentence_type: str
data: List[str]
def __init__(self, talker: str, listener: str, sentence_type: str):
"""
Initialize query sentence.
Args:
talker: Two-character querying device ID
listener: Two-character target device ID
sentence_type: Three-character requested sentence type
"""
def identifier(self) -> str:
"""Return query identifier (e.g., 'CCGPQ,GGA')."""import pynmea2
# Parse query sentence
msg = pynmea2.parse("$CCGPQ,GGA")
print(type(msg)) # <class 'pynmea2.nmea.QuerySentence'>
print(msg.talker) # 'CC'
print(msg.listener) # 'GP'
print(msg.sentence_type) # 'GGA'
print(msg.identifier()) # 'CCGPQ,GGA'
# Create query sentence manually
query = pynmea2.QuerySentence('CC', 'GP', 'GGA')
print(str(query)) # $CCGPQ,GGA*27All sentence objects provide dynamic field access based on the sentence type's field definitions:
import pynmea2
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D")
# Access fields by name (defined in sentence class)
print(msg.timestamp) # datetime.time(18, 43, 53, 70000)
print(msg.lat) # '1929.045'
print(msg.lat_dir) # 'S'
print(msg.gps_qual) # '1'
print(msg.altitude) # 100.0
# Access raw field data by index
print(msg.data[0]) # '184353.07'
print(msg.data[1]) # '1929.045'
print(msg.data[2]) # 'S'
# Set field values (modifies data list)
msg.gps_qual = '2'
print(msg.gps_qual) # '2'
print(msg.data[5]) # '2'class ParseError(ValueError):
"""Base exception for NMEA parsing errors."""
def __init__(self, message: str, data: str):
"""Initialize with error message and problematic data."""
class ChecksumError(ParseError):
"""Raised when NMEA sentence checksum validation fails."""
class SentenceTypeError(ParseError):
"""Raised when sentence type is not recognized."""import pynmea2
# Handle parse errors
try:
msg = pynmea2.parse("invalid nmea data")
except pynmea2.ParseError as e:
print(f"Parse error: {e.args[0]}") # Error message
print(f"Bad data: {e.args[1]}") # Raw data that failed
# Handle checksum errors
try:
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*FF")
except pynmea2.ChecksumError as e:
print(f"Checksum error: {e}")
# Handle unknown sentence types
try:
msg = pynmea2.parse("$GPXYZ,some,unknown,sentence,type*12")
except pynmea2.SentenceTypeError as e:
print(f"Unknown sentence type: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-pynmea2docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10