pynmeagps

Python library for parsing and generating NMEA 0183 GNSS/GPS protocol messages.

https://github.com/semuconsulting/pynmeagps

Science Score: 54.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
    1 of 7 committers (14.3%) from academic institutions
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (11.7%) to scientific vocabulary

Keywords

gnss gps gps-library nmea nmea-library nmea-parser nmea-protocol nmea0183 pynmeagps
Last synced: 6 months ago · JSON representation ·

Repository

Python library for parsing and generating NMEA 0183 GNSS/GPS protocol messages.

Basic Info
  • Host: GitHub
  • Owner: semuconsulting
  • License: bsd-3-clause
  • Language: Python
  • Default Branch: master
  • Homepage:
  • Size: 619 KB
Statistics
  • Stars: 95
  • Watchers: 1
  • Forks: 29
  • Open Issues: 0
  • Releases: 52
Topics
gnss gps gps-library nmea nmea-library nmea-parser nmea-protocol nmea0183 pynmeagps
Created almost 5 years ago · Last pushed 6 months ago
Metadata Files
Readme Contributing Funding License Code of conduct Citation Security

README.md

pynmeagps

Current Status | Installation | Reading | Parsing | Generating | Serializing | Utilities | Examples | Extensibility | Command Line Utility | Graphical Client | Author & License

pynmeagps is an original Python 3 parser aimed primarily at the subset of the NMEA 0183 © v4 protocol relevant to GNSS/GPS receivers.

The intention is to make it as easy as possible to read, parse and utilise NMEA GNSS/GPS messages in Python applications.

The pynmeagps homepage is located at https://github.com/semuconsulting/pynmeagps.

Companion libraries are available which handle UBX © and RTCM3 © messages:

  • pyubx2 (installing pyubx2 via pip also installs pynmeagps and pyrtcm)
  • pyrtcm

Current Status

Status Release Build Codecov Release Date Last Commit Contributors Open Issues

The library implements a comprehensive set of outbound (GET) and inbound (SET/POLL) GNSS NMEA messages relating to GNSS/GPS and Maritime devices, but is readily extensible. Refer to NMEA_MSGIDS and NMEA_MSGIDS_PROP for the complete dictionary of standard and proprietary messages currently supported. While the NMEA 0183 protocol itself is proprietary, the definitions here have been collated from public domain sources.

Sphinx API Documentation in HTML format is available at https://www.semuconsulting.com/pynmeagps/.

Contributions welcome - please refer to CONTRIBUTING.MD.

Bug reports and Feature requests - please use the templates provided. For general queries and advice, post a message to one of the pynmeagps Discussions channels.


Installation

Python version PyPI version PyPI downloads

pynmeagps is compatible with Python 3.9 - 3.14. In the following, python3 & pip refer to the Python 3 executables. You may need to substitute python for python3, depending on your particular environment (on Windows it's generally python).

The recommended way to install the latest version of pynmeagps is with pip:

shell python3 -m pip install --upgrade pynmeagps

If required, pynmeagps can also be installed into a virtual environment, e.g.:

shell python3 -m venv env source env/bin/activate # (or env\Scripts\activate on Windows) python3 -m pip install --upgrade pynmeagps

For Conda users, pynmeagps is also available from conda forge:

Anaconda-Server Badge Anaconda-Server Badge

shell conda install -c conda-forge pynmeagps


Reading (Streaming)

class pynmeagps.nmeareader.NMEAReader(stream, **kwargs)

You can create an NMEAReader object by calling the constructor with an active stream object. The stream object can be any data stream which supports a read(n) -> bytes method (e.g. File or Serial, with or without a buffer wrapper). pynmeagps implements an internal SocketStream class to allow sockets to be read in the same way as other streams (see example below).

Individual input NMEA messages can then be read using the NMEAReader.read() function, which returns both the raw data (as bytes) and the parsed data (as an NMEAMessage object, via the parse() method). The function is thread-safe in so far as the incoming data stream object is thread-safe. NMEAReader also implements an iterator.

The constructor accepts the following optional keyword arguments:

  • msgmode: 0 = GET (default, i.e. output from receiver), 1 = SET (i.e. input to receiver), 2 = POLL (i.e. query to receiver in anticipation of response back)
  • nmeaonly: True = raise error if stream contains non-NMEA data, False = ignore non-NMEA data (default)
  • validate: validation flags VALCKSUM (0x01) = validate checksum (default), VALMSGID (0x02) = validate msgId (i.e. raise error if unknown NMEA message is received)
  • quitonerror: ERR_IGNORE (0) = ignore errors, ERR_LOG (1) = log continue, ERR_RAISE (2) = (re)raise (1)
  • userdefined: An optional user-defined payload definition dictionary, supplementing the existing NMEA_PAYLOADS_GET and NMEA_PAYLOADS_GET_PROP dictionaries (None).

Examples:

  • Serial input - this example will ignore any 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)

  • File input (using iterator) - this example will produce a NMEAStreamError 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)

  • Socket input (using iterator):

python import socket from pynmeagps import NMEAReader with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as stream: stream.connect(("localhost", 50007)) nmr = NMEAReader(stream) for raw_data, parsed_data in nmr: print(parsed_data)


Parsing

You can parse individual NMEA messages using the static NMEAReader.parse(message) function, which takes a string or bytes containing an NMEA message and returns an NMEAMessage object.

Note that latitude and longitude are parsed as signed decimal values for ease of use. Helper methods latlon2dms and latlon2dmm are available to convert decimal degrees to d°m′s.s″ or d°m.m′ display format.

Attributes within repeating groups are parsed with a two-digit suffix (svid01, svid02, etc.).

The parse() function accepts the following optional keyword arguments:

  • msgmode: 0 = GET (default), 1 = SET, 2 = POLL
  • validate: validation flags VALCKSUM (0x01) = validate checksum (default), VALMSGID (0x02) = validate msgId (i.e. raise error if unknown NMEA message is received)
  • quitonerror: ERR_IGNORE (0) = ignore errors, ERR_LOG (1) = log continue, ERR_RAISE (2) = (re)raise (1)
  • userdefined: An optional user-defined payload definition dictionary, supplementing the existing NMEA_PAYLOADS_GET and NMEA_PAYLOADS_GET_PROP dictionaries (None).

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) <NMEA(GNGLL, lat=-53.45072, NS=S, lon=2.240233, EW=E, time=22:32:32, status=A, posMode=A)>

The NMEAMessage object exposes different public attributes depending on its message ID, e.g. the RMC message has the following attributes:

```python from pynmeagps import latlon2dms, latlon2dmm print(msg) print(msg.msgID) print(msg.lat, msg.lon) print(msg.spd) print(latlon2dms((msg.lat, msg.lon))) print(latlon2dmm((msg.lat, msg.lon)))

'RMC' (52.62063, -2.16012) 37.84 ('52°37′14.268″N', '2°9′36.432″W') ('52°37.2378′N', '2°9.6072′W') ```

If the NMEA sentence type is unrecognised or not yet implemented (e.g. due to definition not yet being in the public domain) and the VALMSGID validation flag is NOT set, NMEAMessage will parse the message to a NOMINAL structure e.g.:

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) <NMEA(GNACN, NOMINAL, field_01=103607.00, field_02=ECN, field_03=E, field_04=A, field_05=W, field_06=A, field_07=test, field_08=C)>


Generating

class pynmeagps.nmeamessage.NMEAMessage(talker: str, msgID: str, msgmode: int, **kwargs)

You can create an NMEAMessage object by calling the constructor with the following parameters: 1. talker (must be a valid talker from pynmeagps.NMEA_TALKERS) 1. message id (must be a valid id from pynmeagps.NMEA_MSGIDS or pynmeagps.NMEA_MSGIDS_PROP) 1. msgmode (0=GET, 1=SET, 2=POLL) 1. hpnmeamode - boolean flag to signify high-precision NMEA mode (7 dp rather than 5) (False) 1. validate - integer flag for checksum and/or message type validation (0=VALNONE, 1=VALCKSUM, 2=VALMSGID) (1) 1. userdefined - an optional user-defined payload definition dictionary (None) 1. (optional) a series of keyword parameters representing the message payload

The 'msgmode' parameter signifies whether the message payload refers to a:

  • GET message (i.e. output from the receiver - NB these would normally be generated via the NMEAReader.read() or NMEAReader.parse() methods but can also be created manually)
  • SET message (i.e. command input to the receiver)
  • POLL message (i.e. query input to the receiver in anticipation of a response back)

The message payload can be defined via keyword arguments in one of two ways: 1. A single keyword parameter of payload containing the full payload as a list of string values (any other keyword parameters will be ignored). 2. One or more keyword parameters corresponding to individual message attributes. Any attributes not explicitly provided as keyword parameters will be set to a nominal value according to their type. For position messages, the NS or EW values will be derived from the sign of the lat or lon values and need not be provided, e.g. if lat = -32.4, NS will be "S", if lon = -1.34, EW will be "W" (any provided NS or EW values will be overridden accordingly).

e.g. Create a GLL message, passing the entire payload as a list of strings in native NMEA format:

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) <NMEA(GNGLL, lat=43.5, NS=N, lon=-2.75, EW=W, time=12:04:25.234000, status=A, posMode=A)>

e.g. Create GLL (GET) and GNQ (POLL) message, passing individual typed values as keywords, with any omitted keywords defaulting to nominal values (in the GLL example, the 'time' parameter has been omitted and has defaulted to the current time):

python from pynmeagps import NMEAMessage, GET msg = NMEAMessage('GN', 'GLL', GET, lat=43.5, lon=-2.75, status='A', posMode='A') print(msg) <NMEA(GNGLL, lat=43.5, NS='N', lon=-2.75, EW='W', time='12:04:25.234745', status='A', posMode='A')>

python from pynmeagps import NMEAMessage, POLL msg = NMEAMessage('EI', 'GNQ', POLL, msgId='RMC') print(msg) <NMEA(EIGNQ, msgId=RMC)>

By default, NMEA position message payloads store lat/lon to 5dp of minutes (i.e. (d)ddmm.mmmmm). An optional boolean keyword argument hpnmeamode increases this to 7dp (i.e. (d)ddmm.mmmmmmm) when set to True, e.g.

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) 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'])

NB: Once instantiated, an NMEAMessage object is immutable.


Serializing

The NMEAMessage class implements a serialize() method to convert an NMEAMessage object to a bytes array suitable for writing to an output stream.

python from serial import Serial from pynmeagps import NMEAMessage, POLL stream = Serial('COM6', 38400, timeout=3) msg = NMEAMessage('EI','GNQ', POLL, msgId='RMC') print(msg.serialize()) stream.write(msg.serialize()) b'$EIGNQ,RMC*24\r\n'


Utility Methods

pynmeagps provides the following utility methods:

  • latlon2dms - converts decimal lat/lon to degrees, minutes, decimal seconds format e.g. "53°20′45.6″N", "2°32′46.68″W"
  • latlon2dmm - converts decimal lat/lon to degrees, decimal minutes format e.g. "53°20.76′N", "2°32.778′W"
  • dms2deg - converts lat/lon in d.m(.s) string format to signed decimal degrees e.g. "51°20′45.6″S" -> -51.346
  • llh2iso6709 - converts lat/lon and altitude (hMSL) to ISO6709 format e.g. "+27.5916+086.5640+8850CRSWGS_84/"
  • ecef2llh - converts ECEF (X, Y, Z) coordinates to geodetic (lat, lon, ellipsoidal height) coordinates
  • llh2ecef - converts geodetic (lat, lon, ellipsoidal height) coordinates to ECEF (X, Y, Z) coordinates
  • haversine - finds great circle distance in km between two sets of (lat, lon) coordinates
  • planar - finds planar distance in m between two sets of (lat, lon) coordinates
  • bearing - finds bearing in degrees between two sets of (lat, lon) coordinates
  • area - finds spherical area bounded by two sets of (lat, lon) coordinates

See Sphinx documentation for details.


Examples

The following command line examples can be found in the /examples folder:

  1. nmeapoller.py illustrates how to read, write and display NMEA messages 'concurrently' using threads and queues. This represents a useful generic pattern for many end user applications.

  2. nmeafile.py illustrates how to implement an NMEA datalog file reader using pynmeagps.NMEAReader iterator functionality.

  3. nmeasocket.py illustrates how to implement a TCP Socket reader for NMEA messages using NMEAReader iterator functionality.

  4. gpxtracker.py illustrates a simple utility to convert an NMEA datalog file to a *.gpx track file using pynmeagps.NMEAReader.

  5. /webserver/nmeaserver.py illustrates a simple HTTP web server wrapper around pynmeagps.NMEAReader; it presents data from selected NMEA messages as a web page http://localhost:8080 or a RESTful API http://localhost:8080/gps.

1. utilities.py illustrates how to use various pynmeagps utility methods.

Extensibility

The NMEA protocol is principally defined in the modules nmeatypes_*.py as a series of dictionaries. Additional message types can be readily added to the appropriate dictionary. Message payload definitions must conform to the following rules:

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. {dict} is the nested dictionary of repeating items


Command Line Utility

A command line utility gnssstreamer is available via the pygnssutils package. This is capable of reading and parsing NMEA, UBX and RTCM3 data from a variety of input sources (e.g. serial, socket and file) and outputting to a variety of media in a variety of formats. See https://github.com/semuconsulting/pygnssutils for further details.

To install pygnssutils: python3 -m pip install --upgrade pygnssutils

For help with the gnssstreamer utility, type: gnssstreamer -h


Graphical Client

A python/tkinter graphical GPS client which supports NMEA, UBX and RTCM3 protocols is available at:

https://github.com/semuconsulting/PyGPSClient


Author & License Information

semuadmin@semuconsulting.com

License

pynmeagps is maintained entirely by unpaid volunteers. It receives no funding from advertising or corporate sponsorship. If you find the utility useful, please consider sponsoring the project with the price of a coffee...

Sponsor

Freedom for Ukraine

Owner

  • Name: SEMU Consulting
  • Login: semuconsulting
  • Kind: organization

Citation (CITATION.bib)

@Misc{pynmeagps,
  author       = {{SEMU Consulting}},
  howpublished = {GitHub repository},
  note         = {Viewed last: xxxx:xx:xx},
  title        = {Python library aimed primarily at the subset of the NMEA 0183 v4 protocol for parsing of GNSS messages.},
  year         = {2021},
  url          = {https://github.com/semuconsulting/pynmeagps},
}

GitHub Events

Total
  • Create event: 19
  • Issues event: 2
  • Release event: 8
  • Watch event: 13
  • Delete event: 17
  • Issue comment event: 1
  • Push event: 50
  • Pull request review event: 7
  • Pull request event: 11
  • Fork event: 1
Last Year
  • Create event: 19
  • Issues event: 2
  • Release event: 8
  • Watch event: 13
  • Delete event: 17
  • Issue comment event: 1
  • Push event: 50
  • Pull request review event: 7
  • Pull request event: 11
  • Fork event: 1

Committers

Last synced: 10 months ago

All Time
  • Total Commits: 370
  • Total Committers: 7
  • Avg Commits per committer: 52.857
  • Development Distribution Score (DDS): 0.049
Past Year
  • Commits: 78
  • Committers: 1
  • Avg Commits per committer: 78.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
semuadmin 2****n 352
semuadmin 9****2 7
cminton-hub 8****b 6
Erinn Looney-Triggs e****s@g****m 2
Albrecht Michler a****r@t****e 1
semuadmin s****n@n****m 1
Dorad c****a@f****m 1
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: 6 months ago

All Time
  • Total issues: 16
  • Total pull requests: 58
  • Average time to close issues: 1 day
  • Average time to close pull requests: 2 days
  • Total issue authors: 13
  • Total pull request authors: 6
  • Average comments per issue: 2.81
  • Average comments per pull request: 0.24
  • Merged pull requests: 53
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 1
  • Pull requests: 18
  • Average time to close issues: about 2 hours
  • Average time to close pull requests: about 14 hours
  • Issue authors: 1
  • Pull request authors: 1
  • Average comments per issue: 1.0
  • Average comments per pull request: 0.0
  • Merged pull requests: 15
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • flytrex-vadim (3)
  • semuadmin (2)
  • dekinet (1)
  • nmichaels-qualinx (1)
  • oe1rsa (1)
  • dbstf (1)
  • jdadams-usgs (1)
  • Calimerorulez (1)
  • nmichaels (1)
  • jsainio (1)
  • Buttosaii (1)
  • 0wulf (1)
  • mdresf (1)
  • cminton-hub (1)
Pull Request Authors
  • semuadmin (61)
  • cminton-hub (3)
  • jsainio (1)
  • Doradx (1)
  • erinn (1)
  • alblinks (1)
Top Labels
Issue Labels
bug (9) enhancement (3) question (2) backlog (1) expected behaviour (1)
Pull Request Labels
enhancement (45) bug (18) documentation (8)

Packages

  • Total packages: 1
  • Total downloads:
    • pypi 179,325 last-month
  • Total docker downloads: 125
  • Total dependent packages: 5
  • Total dependent repositories: 5
  • Total versions: 61
  • Total maintainers: 1
pypi.org: pynmeagps

NMEA protocol parser and generator

  • Versions: 61
  • Dependent Packages: 5
  • Dependent Repositories: 5
  • Downloads: 179,325 Last month
  • Docker Downloads: 125
Rankings
Dependent packages count: 1.6%
Downloads: 2.0%
Docker downloads count: 2.5%
Average: 3.2%
Dependent repos count: 6.6%
Maintainers (1)
Last synced: 6 months ago

Dependencies

.github/workflows/main.yml actions
  • actions/checkout v3 composite
  • actions/setup-python v4 composite
  • codecov/codecov-action v1 composite