CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pynmea2

Python library for parsing and generating NMEA 0183 protocol messages used in GPS and marine navigation systems

77

1.11x
Overview
Eval results
Files

utilities.mddocs/

Utility Functions and Mixins

Utility functions and mixin classes provide coordinate conversion, timestamp parsing, validation helpers, and extended functionality for NMEA sentence objects.

Utility Functions

Timestamp and Date Parsing

def timestamp(s: str) -> datetime.time:
    """
    Convert HHMMSS[.ss] ASCII string to datetime.time object.
    
    Args:
        s: Time string in format HHMMSS or HHMMSS.ss
        
    Returns:
        datetime.time object with UTC timezone
        
    Example:
        timestamp('184353.07') -> datetime.time(18, 43, 53, 70000, tzinfo=datetime.timezone.utc)
    """

def datestamp(s: str) -> datetime.date:
    """
    Convert DDMMYY ASCII string to datetime.date object.
    
    Args:
        s: Date string in format DDMMYY
        
    Returns:
        datetime.date object
        
    Example:
        datestamp('120598') -> datetime.date(1998, 5, 12)
    """

Coordinate Conversion

def dm_to_sd(dm: str) -> float:
    """
    Convert degrees/minutes format to signed decimal degrees.
    
    Args:
        dm: Coordinate in DDDMM.MMMM format (e.g., '12319.943281')
        
    Returns:
        Decimal degrees as float
        
    Raises:
        ValueError: If format is invalid
        
    Example:
        dm_to_sd('12319.943281') -> 123.33238801666667
    """

Status Validation

def valid(s: str) -> bool:
    """
    Check if status flag equals 'A' (active/valid).
    
    Args:
        s: Status character
        
    Returns:
        True if s == 'A', False otherwise
    """

Usage Examples

import pynmea2
from pynmea2 import timestamp, datestamp, dm_to_sd, valid

# Parse timestamps
time_obj = timestamp('184353.07')
print(f"Time: {time_obj}")                    # 18:43:53.070000+00:00
print(f"Hour: {time_obj.hour}")               # 18
print(f"Microseconds: {time_obj.microsecond}")# 70000

# Parse dates
date_obj = datestamp('120598')
print(f"Date: {date_obj}")                    # 1998-05-12

# Convert coordinates
decimal_deg = dm_to_sd('12319.943281')
print(f"Decimal degrees: {decimal_deg}")      # 123.33238801666667

# Validate status
print(f"Valid status A: {valid('A')}")        # True
print(f"Valid status V: {valid('V')}")        # False

Mixin Classes

LatLonFix

class LatLonFix:
    """
    Mixin adding latitude/longitude properties as signed decimal degrees.
    
    Requires sentence to have lat, lat_dir, lon, lon_dir fields.
    """
    
    @property
    def latitude(self) -> float:
        """
        Latitude in signed decimal degrees.
        
        Returns:
            Positive for North, negative for South
        """
    
    @property
    def longitude(self) -> float:
        """
        Longitude in signed decimal degrees.
        
        Returns:
            Positive for East, negative for West
        """
    
    @property
    def latitude_minutes(self) -> float:
        """Minutes component of latitude."""
    
    @property
    def longitude_minutes(self) -> float:
        """Minutes component of longitude."""
    
    @property
    def latitude_seconds(self) -> float:
        """Seconds component of latitude."""
    
    @property
    def longitude_seconds(self) -> float:
        """Seconds component of longitude."""

DatetimeFix

class DatetimeFix:
    """
    Mixin adding datetime property combining date and time fields.
    
    Requires sentence to have datestamp and timestamp fields.
    """
    
    @property
    def datetime(self) -> datetime.datetime:
        """Combined date and time as datetime object."""

Validation Mixins

class ValidStatusFix:
    """
    Mixin adding is_valid property checking status == 'A'.
    
    Requires sentence to have status field.
    """
    
    @property
    def is_valid(self) -> bool:
        """True if status field equals 'A'."""

class ValidGGAFix:
    """
    Mixin for GGA sentence validity checking.
    
    Requires sentence to have gps_qual field.
    """
    
    @property
    def is_valid(self) -> bool:
        """True if GPS quality indicates valid fix (1-5)."""

class ValidGSAFix:
    """
    Mixin for GSA sentence validity checking.
    
    Requires sentence to have mode_fix_type field.
    """
    
    @property
    def is_valid(self) -> bool:
        """True if fix type is 2D or 3D (2 or 3)."""

class ValidRMCStatusFix(ValidStatusFix):
    """
    Extended validity checking for RMC sentences.
    
    Checks status, mode_indicator, and nav_status fields.
    """
    
    @property
    def is_valid(self) -> bool:
        """True if all status indicators are valid."""

Usage Examples

import pynmea2

# LatLonFix example
msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D")

# Decimal degrees (most useful)
print(f"Position: {msg.latitude:.6f}, {msg.longitude:.6f}")  # -19.484083, 24.175100

# Component access
print(f"Lat minutes: {msg.latitude_minutes:.3f}")           # 29.045
print(f"Lon minutes: {msg.longitude_minutes:.3f}")          # 10.506
print(f"Lat seconds: {msg.latitude_seconds:.1f}")           # 2.7
print(f"Lon seconds: {msg.longitude_seconds:.1f}")          # 30.4

# Format coordinates in different ways
abs_lat, abs_lon = abs(msg.latitude), abs(msg.longitude)
print(f"DMS: {abs_lat:.0f}°{msg.latitude_minutes:.3f}'{msg.lat_dir} {abs_lon:.0f}°{msg.longitude_minutes:.3f}'{msg.lon_dir}")

# DatetimeFix example  
msg = pynmea2.parse("$GPRMC,184353.07,A,1929.045,S,02410.506,E,0.13,309.62,120598,,A*70")
print(f"Date: {msg.datestamp}")                             # 1998-05-12
print(f"Time: {msg.timestamp}")                             # 18:43:53.070000+00:00
print(f"Combined: {msg.datetime}")                          # 1998-05-12 18:43:53.070000+00:00

# Validation examples
gga_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(f"GGA valid: {gga_msg.is_valid}")                     # True (GPS quality = 1)

rmc_msg = pynmea2.parse("$GPRMC,184353.07,A,1929.045,S,02410.506,E,0.13,309.62,120598,,A*70")
print(f"RMC valid: {rmc_msg.is_valid}")                     # True (status = A)

gsa_msg = pynmea2.parse("$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39")
print(f"GSA valid: {gsa_msg.is_valid}")                     # True (3D fix)

Timezone Support

class TZInfo(datetime.tzinfo):
    """Custom timezone info class for local time zones."""
    
    def __init__(self, hh: int, mm: int):
        """
        Initialize with hour and minute offset from UTC.
        
        Args:
            hh: Hour offset from UTC
            mm: Minute offset from UTC
        """
    
    def utcoffset(self, dt: datetime.datetime) -> datetime.timedelta:
        """Return offset from UTC."""
    
    def tzname(self, dt: datetime.datetime) -> str:
        """Return timezone name."""
    
    def dst(self, dt: datetime.datetime) -> datetime.timedelta:
        """Return DST offset (always 0)."""

Usage Example

import pynmea2
from pynmea2 import TZInfo

# Create timezone info for UTC+5:30 (India Standard Time)
ist = TZInfo(5, 30)

msg = pynmea2.parse("$GPZDA,184353.07,12,05,1998,05,30*4F")
utc_time = msg.datetime

# Convert to local time
local_time = utc_time.replace(tzinfo=pynmea2.datetime.timezone.utc).astimezone(ist)
print(f"UTC: {utc_time}")                                   # 1998-05-12 18:43:53.070000
print(f"Local: {local_time}")                               # 1998-05-13 00:13:53.070000+05:30

SeaTalk Support

class SeaTalk:
    """
    Mixin adding SeaTalk protocol functionality.
    
    Based on Thomas Knauf's SeaTalk documentation.
    Requires sentence to have cmd field.
    """
    
    byte_to_command: Dict[str, str]  # Mapping of command bytes to descriptions
    
    @property
    def command_name(self) -> str:
        """Get human-readable command name from cmd field."""

Usage Example

import pynmea2

# Parse SeaTalk sentence (ALK sentence type)
msg = pynmea2.parse("$STALK,84,96,82,00,00,00,08,02,00*77")
print(f"Command: {msg.cmd}")                                # 84
print(f"Command name: {msg.command_name}")                  # Compass heading Autopilot course and Rudder position

Install with Tessl CLI

npx tessl i tessl/pypi-pynmea2

docs

core-parsing.md

depth-sonar.md

gps-positioning.md

index.md

navigation-course.md

proprietary-sentences.md

stream-processing.md

utilities.md

wind-weather.md

tile.json