### Install pynmeagps with pip Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Install or upgrade the pynmeagps library using pip. This is the recommended method for most users. ```shell python3 -m pip install --upgrade pynmeagps ``` -------------------------------- ### Install pygnssutils Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Install the pygnssutils package, which provides the gnssstreamer command-line utility. ```bash python3 -m pip install --upgrade pygnssutils ``` -------------------------------- ### Initialize NMEAReader with Serial Stream Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Create an NMEAReader instance with a serial port stream. This example ignores non-NMEA data. ```python from serial import Serial from pynmeagps import NMEAReader with Serial('/dev/tty.usbmodem14101', 9600, timeout=3) as stream: nmr = NMEAReader(stream) raw_data, parsed_data = nmr.read() if parsed_data is not None: print(parsed_data) ``` -------------------------------- ### Install pynmeagps in a virtual environment Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Install pynmeagps within a Python virtual environment. This helps manage dependencies for different projects. ```shell python3 -m venv env source env/bin/activate # (or env\Scripts\activate on Windows) python3 -m pip install --upgrade pynmeagps ``` -------------------------------- ### Get gnssstreamer help Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Display help information for the gnssstreamer command-line utility. ```bash gnssstreamer -h ``` -------------------------------- ### Install pynmeagps with Conda Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Install pynmeagps using Conda from the conda-forge channel. This is an alternative for users who prefer the Conda package manager. ```shell conda install -c conda-forge pynmeagps ``` -------------------------------- ### NMEAMessage Creation and Serialization Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Demonstrates how to create NMEA messages using keyword arguments or raw payloads for GET, POLL, and SET operations, and how to serialize them into byte format. ```APIDOC ## NMEAMessage Creation and Serialization This section illustrates the creation and serialization of NMEA messages using the `NMEAMessage` class. ### GET message from keyword arguments (lat/lon with auto NS/EW) ```python from pynmeagps import NMEAMessage, GET msg = NMEAMessage("GN", "GLL", GET, lat=43.5, lon=-2.75, status="A", posMode="A") print(msg) # print(msg.serialize()) # b'$GNGLL,4330.00000,N,00245.00000,W,143022.45,A,A*58\r\n' ``` ### GET message from raw payload list ```python from pynmeagps import NMEAMessage, GET pyld = ["4330.00000", "N", "00245.000000", "W", "120425.234", "A", "A"] msg = NMEAMessage("GN", "GLL", GET, payload=pyld) print(msg) # ``` ### POLL message (query receiver for a specific response) ```python from pynmeagps import NMEAMessage, POLL msg = NMEAMessage("EI", "GNQ", POLL, msgId="RMC") print(msg) # print(msg.serialize()) # b'$EIGNQ,RMC*24\r\n' ``` ### SET message with date/time as strings ```python from pynmeagps import NMEAMessage, SET msg = NMEAMessage( "P", "GRMI", SET, lat=37.23345, lon=-115.81513, date="2025-09-12", time="12:15:34", rcvr_cmd="D", ) print(msg.serialize()) # b'$PGRMI,3714.00700,N,11548.90780,W,120925,121534,D*04\r\n' ``` ### SET message with datetime objects ```python from datetime import datetime from pynmeagps import NMEAMessage, SET msg = NMEAMessage( "P", "GRMI", SET, lat=37.23345, lon=-115.81513, date=datetime(2025, 9, 12).date(), time=datetime(2025, 9, 12, 12, 15, 34).time(), rcvr_cmd="D", ) print(msg.serialize()) # b'$PGRMI,3714.00700,N,11548.90780,W,120925,121534.00,D*2A\r\n' ``` ### High-precision mode (7dp decimal minutes instead of 5dp) ```python from pynmeagps import NMEAMessage, GET msghp = NMEAMessage("GN", "GLL", GET, lat=-43.123456789, lon=2.987654321, status="A", posMode="A", hpnmeamode=True) print(repr(msghp)) # NMEAMessage('GN','GLL', 0, payload=['4307.4074073', 'S', '00259.2592593', 'E', ...]) ``` ``` -------------------------------- ### Create NMEA Message with Payload List Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Construct an NMEAMessage object by providing the full payload as a list of strings. Any other keyword parameters will be ignored. The 'msgmode' parameter signifies message type: GET, SET, or POLL. ```python from pynmeagps import NMEAMessage, GET pyld=['4330.00000','N','00245.000000','W','120425.234','A','A'] msg = NMEAMessage('GN', 'GLL', GET, payload=pyld) print(msg) ``` ```text ``` -------------------------------- ### Define and Use User-Defined NMEA Messages Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Configure NMEAReader to parse custom NMEA messages by providing a user-defined payload definition dictionary. The example shows parsing a custom 'XX1' message. ```python from pynmeagps import NMEAReader, DE, CH NMEA_PROD_DEVEL = { "XX1": { "roll": DE, "pitch": DE, "yaw": DE, "status": CH, } } with open('testfile.log', 'rb') as stream: nmr = NMEAReader(stream, userdefined=NMEA_PROD_DEVEL) for raw_data, parsed_data in nmr: print(parsed_data) ``` ```text ``` -------------------------------- ### Create and Serialize NMEA Message Source: https://github.com/semuconsulting/pynmeagps/blob/master/RELEASE_NOTES.md Demonstrates creating an NMEA message with specific parameters and then serializing it back into its NMEA string format. Use this to construct custom NMEA sentences. ```python print(msg2) print(msg2.serialize()) ``` ```python msg3 = NMEAMessage( "P", "GRMI", SET, lat=-115.81513, lon=37.23345, rcvr_cmd="D", ) # # b'$PGRMI,11548.90780,S,03714.00700,E,180925,143720.37,D*18\r\n' print(msg3) print(msg3.serialize()) ``` -------------------------------- ### Populate NMEA Date/Time Attributes with Strings or Datetime Objects Source: https://github.com/semuconsulting/pynmeagps/blob/master/RELEASE_NOTES.md Demonstrates how to populate NMEA Date (DM, DT, DTL) and Time (TM) attributes using either formatted string types or datetime.date/time objects. Delimiters in string formats are optional. ```python from datetime import datetime from pynmeagps import SET, NMEAMessage # NOTE THAT LAD/NS ("N"/"S") and LND/EW ("E"/"W") attributes do not need to be explicitly # provided - these values will be derived from the sign of the decimal lat/lon values. # NMEA Date (DM, DT, DTL) and Time (TM) attributes can be populated in any of the following ways: # A) use formatted string types for TM and DT attributes msg1 = NMEAMessage( "P", "GRMI", SET, lat=-115.81513, lon=37.23345, date="2025-09-12", # "-" delimiters are optional time="12:15:34", # ":" delimiters are optional rcvr_cmd="D", ) # # b'$PGRMI,11548.90780,S,03714.00700,E,120925,121534,D*3B\r\n' print(msg1) print(msg1.serialize()) # B) use datetime.date() and datetime.time() types for DT and TM attributes msg2 = NMEAMessage( "P", "GRMI", SET, lat=-115.81513, lon=37.23345, date=datetime(2025, 9, 12).date(), time=datetime(2025, 9, 12, 12, 15, 34).time(), rcvr_cmd="D", ) # ``` -------------------------------- ### Create NMEA Message with Keyword Arguments Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Instantiate an NMEAMessage object by passing individual typed values as keyword arguments. Omitted keywords will default to nominal values. For position messages, NS/EW are derived from lat/lon signs. ```python from pynmeagps import NMEAMessage, GET msg = NMEAMessage('GN', 'GLL', GET, lat=43.5, lon=-2.75, status='A', posMode='A') print(msg) ``` ```text ``` ```python from pynmeagps import NMEAMessage, POLL msg = NMEAMessage('EI', 'GNQ', POLL, msgId='RMC') print(msg) ``` ```text ``` -------------------------------- ### Define and use custom NMEA sentences Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Extend NMEAMessage and NMEAReader to parse and generate proprietary NMEA sentences by defining custom message structures. ```python from pynmeagps import NMEAReader, NMEAMessage, GET, DE, CH, IN # Define a custom sentence: $PXX1,,,,* NMEA_CUSTOM = { "XX1": { "roll": DE, # decimal float "pitch": DE, "yaw": DE, "status": CH, # character/string }, "XX2": { "svCount": IN, # integer "mode": CH, }, } # Parse a file containing custom sentences with open("custom_device.log", "rb") as stream: nmr = NMEAReader(stream, userdefined=NMEA_CUSTOM) for raw, parsed in nmr: if parsed and parsed.msgID == "XX1": print(f"roll={parsed.roll}, pitch={parsed.pitch}, yaw={parsed.yaw}") # roll=0.3455, pitch=1.5456, yaw=18.1844 # Generate a custom message msg = NMEAMessage("P", "XX1", GET, userdefined=NMEA_CUSTOM, roll=0.3455, pitch=1.5456, yaw=18.1844, status="SYNC") print(msg) print(msg.serialize()) ``` -------------------------------- ### Real-time GNSS with Threaded Read/Write Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Utilize Python threads and Queue objects with NMEAReader for concurrent reading of GNSS messages and sending of commands without blocking. This pattern is suitable for real-time applications. ```python from queue import Queue from threading import Event, Thread from time import sleep from serial import Serial from pynmeagps import NMEAReader, NMEAMessage, POLL, NMEA_MSGIDS def io_thread(nmr, read_q, send_q, stop): while not stop.is_set(): raw, parsed = nmr.read() if parsed: read_q.put((raw, parsed)) while not send_q.empty(): msg = send_q.get(False) nmr.datastream.write(msg.serialize()) send_q.task_done() def process_thread(read_q, stop): while not stop.is_set(): if not read_q.empty(): _, parsed = read_q.get() if parsed.msgID == "GGA": print(f"Position: {parsed.lat}, {parsed.lon}, alt={parsed.alt}m") read_q.task_done() with Serial("/dev/ttyACM0", 38400, timeout=3) as serial_stream: nmr = NMEAReader(serial_stream) read_queue, send_queue, stop_event = Queue(), Queue(), Event() t1 = Thread(target=io_thread, args=(nmr, read_queue, send_queue, stop_event)) t2 = Thread(target=process_thread, args=(read_queue, stop_event)) t1.start(); t2.start() # Poll for each supported NMEA sentence type for msgid in list(NMEA_MSGIDS.keys())[:5]: # first 5 for brevity send_queue.put(NMEAMessage("EI", "GNQ", POLL, msgId=msgid)) sleep(1) stop_event.set() t1.join(); t2.join() ``` -------------------------------- ### GNSS Time System Utilities (leapsecond, utc2wnotow, wnotow2utc) Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Convert between UTC and GNSS-specific time representations, including GPS Week Number and Time of Week. Supports multiple GNSS systems like GPS, Galileo, BeiDou, QZSS, and IRNSS/NavIC. ```APIDOC ## leapsecond / utc2wnotow / wnotow2utc — GNSS Time System Utilities Convert between UTC and GNSS-specific time representations (GPS Week Number / Time of Week). Supports GPS, Galileo, BeiDou, QZSS, and IRNSS/NavIC time systems. ```python from datetime import datetime, timezone from pynmeagps import leapsecond, utc2wnotow, wnotow2utc # Get current GPS leapsecond offset now = datetime.now(tz=timezone.utc) ls = leapsecond(now, gnss="G") # GPS print(f"Current GPS leapsecond offset: {ls}s") # 18 # UTC datetime → GPS week number + time of week (ms) wno, tow, ls = utc2wnotow(datetime(2024, 6, 15, 12, 0, 0, tzinfo=timezone.utc), gnss="G") print(f"WNO={wno}, TOW={tow}ms, leapseconds={ls}") # WNO=358, TOW=302418000ms, leapseconds=18 # GPS week number + TOW → UTC datetime utc = wnotow2utc(wno=358, tow=302418000, gnss="G") print(utc) # 2024-06-15 12:00:00+00:00 # BeiDou time system wno_b, tow_b, ls_b = utc2wnotow(now, gnss="C") # BeiDou epoch: 2006-01-01 # Auto-rollover mode: disambiguate modular week numbers utc_rolled = wnotow2utc(wno=366, tow=381600000, gnss="G", autoroll=True) # Resolves to latest valid date before 'now', not the 1987 date ``` ``` -------------------------------- ### Coordinate Conversion Helpers Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Utility functions to convert decimal degree coordinates to Degrees, Minutes, Seconds (DMS) or Degrees, Decimal Minutes (DMM) formats. ```APIDOC ## Coordinate Conversion Functions ### Description Provides helper functions to convert decimal degree latitude and longitude values into human-readable formats. ### Functions - **`latlon2dms(latlon)`**: Converts decimal degrees to Degrees, Minutes, Seconds (DMS) format. - **`latlon2dmm(latlon)`**: Converts decimal degrees to Degrees, Decimal Minutes (DMM) format. ### Parameters - **latlon** (tuple) - A tuple containing latitude and longitude as signed decimal values, e.g., `(latitude, longitude)`. ### Request Example ```python from pynmeagps import latlon2dms, latlon2dmm # Assuming 'msg' is a parsed NMEA message object with lat and lon attributes # Example coordinates: lat=52.62063, lon=-2.16012 lat, lon = (52.62063, -2.16012) print(latlon2dms((lat, lon))) print(latlon2dmm((lat, lon))) ``` ### Response Example ``` ('52°37′14.268″N', '2°9′36.432″W') ('52°37.2378′N', '2°9.6072′W') ``` ``` -------------------------------- ### Set NMEA message with date/time as strings Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Construct a SET message with date and time specified as strings. This is useful for direct input or when parsing from text sources. ```python msg = NMEAMessage( "P", "GRMI", SET, lat=37.23345, lon=-115.81513, date="2025-09-12", time="12:15:34", rcvr_cmd="D", ) print(msg.serialize()) ``` -------------------------------- ### Iterate NMEA Messages from File Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Use the NMEAReader as an iterator to process NMEA messages from a log file. This configuration raises an error if non-NMEA data is encountered. ```python from pynmeagps import NMEAReader with open('nmeadata.log', 'rb') as stream: nmr = NMEAReader(stream, nmeaonly=True) for raw_data, parsed_data in nmr: print(parsed_data) ``` -------------------------------- ### Create NMEA message from raw payload list Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Build an NMEA message by providing a list of raw payload values. The library parses these values into the appropriate fields. ```python pyld = ["4330.00000", "N", "00245.000000", "W", "120425.234", "A", "A"] msg = NMEAMessage("GN", "GLL", GET, payload=pyld) print(msg) ``` -------------------------------- ### Set NMEA message with datetime objects Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Create a SET message using Python datetime objects for date and time. This ensures proper type handling and formatting. ```python from datetime import datetime msg = NMEAMessage( "P", "GRMI", SET, lat=37.23345, lon=-115.81513, date=datetime(2025, 9, 12).date(), time=datetime(2025, 9, 12, 12, 15, 34).time(), rcvr_cmd="D", ) print(msg.serialize()) ``` -------------------------------- ### Read NMEA Messages from Serial Port Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Instantiate NMEAReader with a serial port stream and validation options. The read() method returns raw bytes and a parsed NMEAMessage object. ```python from serial import Serial from pynmeagps import NMEAReader, NMEAStreamError, ERR_RAISE, VALCKSUM, VALMSGID # --- Serial port stream --- with Serial("/dev/ttyACM0", 9600, timeout=3) as stream: nmr = NMEAReader( stream, validate=VALCKSUM | VALMSGID, # validate checksum AND message ID quitonerror=ERR_RAISE, # raise on any parse error ) raw_data, parsed_data = nmr.read() if parsed_data: print(parsed_data) # ``` -------------------------------- ### Create NMEA Message with High Precision Mode Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Use the `hpnmeamode` keyword argument to control NMEA position message payload precision. `hpnmeamode=0` uses standard 5dp precision, while `hpnmeamode=1` increases it to 7dp. ```python from pynmeagps import NMEAMessage, GET msgsp = NMEAMessage('GN', 'GLL', GET, lat=43.123456789, lon=-2.987654321, status='A', posMode='A', hpnmeamode=0) # standard precision print(msgsp) msghp = NMEAMessage('GN', 'GLL', GET, lat=-43.123456789, lon=2.987654321, status='A', posMode='A', hpnmeamode=1) # high precision print(msghp) ``` ```text NMEAMessage('GN','GLL', 0, payload=['4307.40741', 'N', '00259.25926', 'W', '095045.78', 'A', 'A']) NMEAMessage('GN','GLL', 0, payload=['4307.4074073', 'S', '00259.2592593', 'E', '094824.88', 'A', 'A']) ``` -------------------------------- ### Generate GPX Track Files from NMEA Logs Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Process NMEA log files using NMEAReader's iterator to extract GGA fix data and generate standard GPX track files. These files can be used with GIS tools and online viewers. ```python from datetime import datetime from pynmeagps import NMEAReader XML_HDR = '' GPX_OPEN = '' GPX_CLOSE = "" def nmea_to_gpx(infile: str, outfile: str): points = [] with open(infile, "rb") as stream: for _, msg in NMEAReader(stream): if msg and msg.msgID == "GGA" and msg.quality > 0: tim = msg.time ts = datetime.now().replace( hour=tim.hour, minute=tim.minute, second=tim.second ).isoformat() + "Z" points.append( f' f"{msg.alt}" ) with open(outfile, "w", encoding="utf-8") as f: f.write(XML_HDR + GPX_OPEN + "".join(points) + GPX_CLOSE) print(f"Written {len(points)} trackpoints to {outfile}") nmea_to_gpx("track.log", "output.gpx") # Written 1247 trackpoints to output.gpx ``` -------------------------------- ### Convert decimal degrees to DMS format Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Convert signed decimal degree latitude and longitude values into human-readable Degrees-Minutes-Seconds (DMS) format strings. ```python from pynmeagps import latlon2dms, latlon2dmm, dms2deg lat, lon = 52.620630, -2.160120 # Decimal degrees → DMS lat_dms, lon_dms = latlon2dms(lat, lon) print(lat_dms) # 52°37′14.268″N print(lon_dms) # 2°9′36.432″W ``` -------------------------------- ### Parse Unrecognized NMEA Sentences (Default Behavior) Source: https://github.com/semuconsulting/pynmeagps/blob/master/RELEASE_NOTES.md Parses an NMEA sentence with an unrecognized message ID using the default behavior where `VALMSGID` is not set. Unrecognized messages are parsed into a nominal structure. ```python from pynmeagps import NMEAReader msg = NMEAReader.parse("$GNACN,103607.00,ECN,E,A,W,A,test,C*67\r\n") print(msg) ``` ```text ``` -------------------------------- ### Convert decimal degrees to DMM format Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Convert signed decimal degree latitude and longitude values into human-readable Degrees-Decimal-Minutes (DMM) format strings. ```python from pynmeagps import latlon2dms, latlon2dmm, dms2deg lat, lon = 52.620630, -2.160120 # Decimal degrees → DMM lat_dmm, lon_dmm = latlon2dmm(lat, lon) print(lat_dmm) # 52°37.2378′N print(lon_dmm) # 2°9.6072′W ``` -------------------------------- ### NMEAReader.parse() - Static Single-Message Parser Source: https://context7.com/semuconsulting/pynmeagps/llms.txt A static method to parse a single NMEA sentence (string or bytes) into an NMEAMessage object without requiring a stream. It's the simplest way to parse individual messages. ```APIDOC ## NMEAReader.parse() ### Description Parses a single NMEA sentence (string or bytes) into an `NMEAMessage` object without needing a stream. It is the simplest entry point for one-off message parsing. ### Usage ```python from pynmeagps import NMEAReader, VALCKSUM, VALMSGID, latlon2dms, latlon2dmm # Parse standard GLL sentence msg = NMEAReader.parse("$GNGLL,5327.04319,S,00214.41396,E,223232.00,A,A*68\r\n") print(msg) # print(msg.lat, msg.lon) # -53.45072 2.240233 print(msg.msgID) # GLL print(msg.talker) # GN # Parse RMC sentence and use helper formatters msg = NMEAReader.parse("$GNRMC,221838.00,A,5237.23780,N,00209.60671,W,37.84,,050321,,,A*70\r\n") print(latlon2dms(msg.lat, msg.lon)) # ('52°37′14.268″N', '2°9′36.432″W') print(latlon2dmm(msg.lat, msg.lon)) # ('52°37.2378′N', '2°9.6072′W') print(msg.spd) # 37.84 (knots) print(msg.date) # 2021-03-05 # Parse unknown/unrecognised sentence type without raising (NOMINAL mode) msg = NMEAReader.parse("$GNACN,103607.00,ECN,E,A,W,A,test,C*67\r\n", validate=VALCKSUM) print(msg) # # Parse bytes with strict validation (raises NMEAParseError on unknown msgID) try: msg = NMEAReader.parse(b"$GNXXX,foo,bar*XX\r\n", validate=VALCKSUM | VALMSGID) except Exception as e: print(e) # Message GNXXX invalid checksum ... ``` ``` -------------------------------- ### GNSS Time System Utilities Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Convert between UTC and GNSS-specific time representations (GPS Week Number / Time of Week). Supports GPS, Galileo, BeiDou, QZSS, and IRNSS/NavIC time systems. Includes leap second offset calculation and auto-rollover mode for disambiguating week numbers. ```python from datetime import datetime, timezone from pynmeagps import leapsecond, utc2wnotow, wnotow2utc # Get current GPS leapsecond offset now = datetime.now(tz=timezone.utc) ls = leapsecond(now, gnss="G") # GPS print(f"Current GPS leapsecond offset: {ls}s") # 18 # UTC datetime → GPS week number + time of week (ms) wno, tow, ls = utc2wnotow(datetime(2024, 6, 15, 12, 0, 0, tzinfo=timezone.utc), gnss="G") print(f"WNO={wno}, TOW={tow}ms, leapseconds={ls}") # WNO=358, TOW=302418000ms, leapseconds=18 # GPS week number + TOW → UTC datetime utc = wnotow2utc(wno=358, tow=302418000, gnss="G") print(utc) # 2024-06-15 12:00:00+00:00 # BeiDou time system wno_b, tow_b, ls_b = utc2wnotow(now, gnss="C") # BeiDou epoch: 2006-01-01 # Auto-rollover mode: disambiguate modular week numbers utc_rolled = wnotow2utc(wno=366, tow=381600000, gnss="G", autoroll=True) # Resolves to latest valid date before 'now', not the 1987 date ``` -------------------------------- ### User-Defined Message Definitions Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Enables parsing and generation of proprietary or unreleased NMEA sentence types by defining custom message structures. ```APIDOC ## User-Defined Message Definitions `NMEAReader` and `NMEAMessage` accept a `userdefined` dictionary that supplements the built-in payload tables, enabling parsing and generation of proprietary or unreleased NMEA sentence types using the same type system (`DE`, `CH`, `IN`, `LA`, `LN`, `TM`, `DT`, etc.). ### Example Usage ```python from pynmeagps import NMEAReader, NMEAMessage, GET, DE, CH, IN # Define a custom sentence: $PXX1,,,,* NMEA_CUSTOM = { "XX1": { "roll": DE, # decimal float "pitch": DE, "yaw": DE, "status": CH, # character/string }, "XX2": { "svCount": IN, # integer "mode": CH, }, } # Parse a file containing custom sentences with open("custom_device.log", "rb") as stream: nmr = NMEAReader(stream, userdefined=NMEA_CUSTOM) for raw, parsed in nmr: if parsed and parsed.msgID == "XX1": print(f"roll={parsed.roll}, pitch={parsed.pitch}, yaw={parsed.yaw}") # roll=0.3455, pitch=1.5456, yaw=18.1844 # Generate a custom message msg = NMEAMessage("P", "XX1", GET, userdefined=NMEA_CUSTOM, roll=0.3455, pitch=1.5456, yaw=18.1844, status="SYNC") print(msg) # print(msg.serialize()) # b'$PXX1,0.3455,1.5456,18.1844,SYNC*XX\r\n' ``` ``` -------------------------------- ### NMEAReader - Stream Reader and Parser Source: https://context7.com/semuconsulting/pynmeagps/llms.txt The NMEAReader class reads and parses NMEA messages from various byte streams like serial ports, files, or sockets. It returns tuples of raw bytes and parsed NMEAMessage objects and can be used as a Python iterator. ```APIDOC ## NMEAReader ### Description Reads and parses NMEA messages from any stream supporting `read(n) -> bytes` (e.g., `serial.Serial`, binary file, or `socket.socket`). It returns `(raw_bytes, NMEAMessage)` tuples and implements a Python iterator interface. Non-NMEA bytes in the stream are silently skipped by default. ### Usage ```python from serial import Serial from pynmeagps import NMEAReader, NMEAStreamError, ERR_RAISE, VALCKSUM, VALMSGID # --- Serial port stream --- with Serial("/dev/ttyACM0", 9600, timeout=3) as stream: nmr = NMEAReader( stream, validate=VALCKSUM | VALMSGID, # validate checksum AND message ID quitonerror=ERR_RAISE, # raise on any parse error ) raw_data, parsed_data = nmr.read() if parsed_data: print(parsed_data) # # --- Binary file stream using iterator --- with open("nmeadata.log", "rb") as stream: nmr = NMEAReader(stream, nmeaonly=True) # raise NMEAStreamError on non-NMEA bytes for raw_data, parsed_data in nmr: if parsed_data.msgID == "GGA": print(f"lat={parsed_data.lat}, lon={parsed_data.lon}, alt={parsed_data.alt}") # --- TCP socket stream --- import socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect(("192.168.1.10", 2947)) # e.g. gpsd NMEA output nmr = NMEAReader(sock) for raw_data, parsed_data in nmr: print(parsed_data) ``` ``` -------------------------------- ### Iterate NMEA Messages from Binary File Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Use NMEAReader as an iterator for a binary file stream. Set nmeaonly=True to raise NMEAStreamError on non-NMEA bytes. Access parsed message attributes like lat, lon, and alt. ```python from serial import Serial from pynmeagps import NMEAReader, NMEAStreamError, ERR_RAISE, VALCKSUM, VALMSGID # --- Binary file stream using iterator --- with open("nmeadata.log", "rb") as stream: nmr = NMEAReader(stream, nmeaonly=True) # raise NMEAStreamError on non-NMEA bytes for raw_data, parsed_data in nmr: if parsed_data.msgID == "GGA": print(f"lat={parsed_data.lat}, lon={parsed_data.lon}, alt={parsed_data.alt}") ``` -------------------------------- ### Handle pynmeagps Exceptions Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Catch specific pynmeagps exceptions like NMEAParseError, NMEAStreamError, NMEAMessageError, and NMEATypeError for granular error handling during parsing, stream reading, message construction, and type validation. ```python from pynmeagps import ( NMEAReader, NMEAMessage, GET, NMEAParseError, # invalid checksum, bad msgID, bad mode NMEAStreamError, # non-NMEA data when nmeaonly=True NMEAMessageError, # unknown talker/msgID, immutability violation NMEATypeError, # wrong type for payload attribute VALCKSUM, VALMSGID, ERR_RAISE, ) # Catch parse errors from a file stream with open("mixed_data.log", "rb") as stream: nmr = NMEAReader(stream, validate=VALCKSUM, quitonerror=ERR_RAISE) try: for raw, msg in nmr: print(msg) except NMEAParseError as e: print(f"Parse error: {e}") except NMEAStreamError as e: print(f"Stream error: {e}") # Catch construction errors try: msg = NMEAMessage("XX", "GGA", GET) # invalid talker except NMEAMessageError as e: print(f"Message error: {e}") # Catch type errors try: msg = NMEAMessage("GN", "GLL", GET, lat="not-a-number") except NMEATypeError as e: print(f"Type error: {e}") # Catch immutability violation msg = NMEAMessage("GN", "GLL", GET, lat=51.5, lon=-0.1, status="A", posMode="A") try: msg.lat = 52.0 # NMEAMessage is immutable after __init__ except NMEAMessageError as e: print(f"Immutability error: {e}") ``` -------------------------------- ### Convert DMS string to decimal degrees Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Perform the reverse conversion from a Degrees-Minutes-Seconds (DMS) formatted string back to decimal degrees. ```python from pynmeagps import latlon2dms, latlon2dmm, dms2deg # DMS string → decimal degrees (reverse conversion) print(dms2deg("52°37′14.268″N")) # 51.345630 (approx) print(dms2deg("2°9′36.432″W")) # -2.16012 ``` -------------------------------- ### ISO 6709 Position Formatting (llh2iso6709) Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Convert a geodetic position (decimal latitude, longitude, and altitude) to the ISO 6709 standard string representation, which is commonly used in metadata and interchange formats. ```APIDOC ## llh2iso6709 — ISO 6709 Position Formatting Convert a geodetic position (decimal lat/lon + altitude) to the ISO 6709 standard string representation used in metadata and interchange formats. ```python from pynmeagps import llh2iso6709 # Everest Base Camp iso = llh2iso6709(27.5916, 86.5640, 8850.0) print(iso) # +27.5916+86.564+8850.0CRSWGS_84/ # Negative coordinates (southern/western hemisphere) iso2 = llh2iso6709(-33.8688, 151.2093, 50.0) print(iso2) # -33.8688+151.2093+50.0CRSWGS_84/ ``` ``` -------------------------------- ### Parse Unrecognized NMEA Sentences (VALMSGID Set) Source: https://github.com/semuconsulting/pynmeagps/blob/master/RELEASE_NOTES.md Parses an NMEA sentence with an unrecognized message ID while the `VALMSGID` flag is set. This will raise an `NMEAParseError` for unknown message IDs. ```python from pynmeagps import NMEAReader, VALMSGID msg = NMEAReader.parse("$GNACN,103607.00,ECN,E,A,W,A,test,C*67\r\n", validate=VALMSGID) print(msg) ``` ```text pynmeagps.exceptions.NMEAParseError: Unknown msgID GNACN, msgmode GET. ``` -------------------------------- ### High-precision NMEA message mode Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Enable high-precision mode for NMEA messages to output decimal minutes with 7 decimal places instead of the standard 5. ```python msghp = NMEAMessage("GN", "GLL", GET, lat=-43.123456789, lon=2.987654321, status="A", posMode="A", hpnmeamode=True) print(repr(msghp)) ``` -------------------------------- ### NMEAReader.parse() Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Parses an individual NMEA message from a string or bytes input. It can handle various NMEA message types and offers options for validation and error handling. ```APIDOC ## NMEAReader.parse(message, msgmode=0, validate=3, quitererror=0, userdefined=None) ### Description Parses an individual NMEA message from a string or bytes input. ### Method `NMEAReader.parse()` ### Parameters #### Keyword Arguments - **message** (string or bytes) - The NMEA message string or bytes to parse. - **msgmode** (int) - Optional. Message mode: 0 = GET (default), 1 = SET, 2 = POLL. - **validate** (int) - Optional. Validation flags: `VALCKSUM` (0x01) = validate checksum (default), `VALMSGID` (0x02) = validate msgId (raise error if unknown NMEA message). - **quitererror** (int) - Optional. Error handling: `ERR_IGNORE` (0) = ignore errors, `ERR_LOG` (1) = log and continue, `ERR_RAISE` (2) = (re)raise errors. - **userdefined** (dict) - Optional. A dictionary for user-defined payload definitions. ### Request Example ```python from pynmeagps import NMEAReader msg = NMEAReader.parse('$GNGLL,5327.04319,S,00214.41396,E,223232.00,A,A*68\r\n') print(msg) ``` ### Response #### Success Response - **NMEAMessage object** - An object representing the parsed NMEA message. ### Response Example ``` ``` ### Handling Unrecognized Messages If a message type is unrecognized and `VALMSGID` is not set, it's parsed into a `NOMINAL` structure. ### Request Example (Unrecognized Message) ```python from pynmeagps import NMEAReader, VALCKSUM msg = NMEAReader.parse('$GNACN,103607.00,ECN,E,A,W,A,test,C*67\r\n', validate=VALCKSUM) print(msg) ``` ### Response Example (Unrecognized Message) ``` ``` ``` -------------------------------- ### Speed Unit Conversion (knots2spd) Source: https://context7.com/semuconsulting/pynmeagps/llms.txt Convert a speed value from knots to other common units such as meters per second (m/s), feet per second (ft/s), miles per hour (mph), or kilometers per hour (km/h). This is particularly useful for processing NMEA data. ```APIDOC ## knots2spd — Speed Unit Conversion Convert a speed value from knots (as output by NMEA receivers in RMC, VTG messages) to m/s, ft/s, mph, or km/h. ```python from pynmeagps import knots2spd, NMEAReader # Standalone conversion print(knots2spd(37.84, "MS")) # 19.467 m/s print(knots2spd(37.84, "KMPH")) # 70.066 km/h print(knots2spd(37.84, "MPH")) # 43.534 mph print(knots2spd(37.84, "FS")) # 63.879 ft/s # Integrate with parsed NMEA data with open("track.log", "rb") as stream: for _, msg in NMEAReader(stream): if msg and msg.msgID == "RMC" and msg.status == "A": speed_ms = knots2spd(msg.spd, "MS") print(f"Speed: {speed_ms:.2f} m/s at {msg.time}") ``` ``` -------------------------------- ### Define NMEA message payload rules Source: https://github.com/semuconsulting/pynmeagps/blob/master/README.md Rules for defining NMEA message payloads, including attribute naming, type constraints, and handling of repeating groups. ```plaintext 1. attribute names must be unique within each message class 2. avoid reserved names 'msgID', 'talker', 'payload', 'checksum'. 3. attribute types must be one of the valid types (IN, DE, CH, etc.) 4. repeating groups must be defined as a tuple ('numr', {dict}), where: 'numr' is either: a. an integer representing a fixed number of repeats e.g. 32 b. a string representing the name of a preceding attribute containing the number of repeats e.g. 'numSv' c. 'None' for an indeterminate repeating group. Only one such group is permitted per payload and it must be at the end. ```