{"id":23683907,"url":"https://github.com/semuconsulting/pynmeagps","last_synced_at":"2025-05-16T06:07:45.353Z","repository":{"id":37410431,"uuid":"345345829","full_name":"semuconsulting/pynmeagps","owner":"semuconsulting","description":"Python library for parsing and generating NMEA 0183 GNSS/GPS protocol messages.","archived":false,"fork":false,"pushed_at":"2025-05-13T16:36:24.000Z","size":620,"stargazers_count":91,"open_issues_count":0,"forks_count":28,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-13T17:48:02.325Z","etag":null,"topics":["gnss","gps","gps-library","nmea","nmea-library","nmea-parser","nmea-protocol","nmea0183","pynmeagps"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/semuconsulting.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.bib","codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"buy_me_a_coffee":"semuconsulting"}},"created_at":"2021-03-07T12:52:25.000Z","updated_at":"2025-05-13T16:36:28.000Z","dependencies_parsed_at":"2023-02-14T06:10:16.569Z","dependency_job_id":"78740b3b-0bf4-475b-aaea-15e94bb763f8","html_url":"https://github.com/semuconsulting/pynmeagps","commit_stats":{"total_commits":188,"total_committers":8,"mean_commits":23.5,"dds":"0.32978723404255317","last_synced_commit":"08e933a0f4d8841d775e795bd83afcf5ba95dd3e"},"previous_names":[],"tags_count":59,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semuconsulting%2Fpynmeagps","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semuconsulting%2Fpynmeagps/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semuconsulting%2Fpynmeagps/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/semuconsulting%2Fpynmeagps/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/semuconsulting","download_url":"https://codeload.github.com/semuconsulting/pynmeagps/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254478193,"owners_count":22077676,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gnss","gps","gps-library","nmea","nmea-library","nmea-parser","nmea-protocol","nmea0183","pynmeagps"],"created_at":"2024-12-29T20:29:48.389Z","updated_at":"2025-05-16T06:07:40.338Z","avatar_url":"https://github.com/semuconsulting.png","language":"Python","funding_links":["https://buymeacoffee.com/semuconsulting"],"categories":[],"sub_categories":[],"readme":"pynmeagps\r\n=========\r\n\r\n[Current Status](#currentstatus) |\r\n[Installation](#installation) |\r\n[Reading](#reading) |\r\n[Parsing](#parsing) |\r\n[Generating](#generating) |\r\n[Serializing](#serializing) |\r\n[Utilities](#utilities) |\r\n[Examples](#examples) |\r\n[Extensibility](#extensibility) |\r\n[Command Line Utility](#cli) |\r\n[Graphical Client](#gui) |\r\n[Author \u0026 License](#author)\r\n\r\n`pynmeagps` is an original Python 3 parser aimed *primarily* at the subset of the NMEA 0183 \u0026copy; v4 protocol relevant to GNSS/GPS receivers.\r\n\r\nThe intention is to make it as easy as possible to read, parse and utilise NMEA GNSS/GPS messages in Python applications. \r\n\r\nThe `pynmeagps` homepage is located at [https://github.com/semuconsulting/pynmeagps](https://github.com/semuconsulting/pynmeagps).\r\n\r\nCompanion libraries are available which handle UBX \u0026copy; and RTCM3 \u0026copy; messages:\r\n\r\n- [pyubx2](http://github.com/semuconsulting/pyubx2) (installing `pyubx2` via pip also installs `pynmeagps` and `pyrtcm`)\r\n- [pyrtcm](http:/github.com/semuconsulting/pyrtcm)\r\n\r\n---\r\n## \u003ca name=\"currentstatus\"\u003eCurrent Status\u003c/a\u003e\r\n\r\n![Status](https://img.shields.io/pypi/status/pynmeagps)\r\n![Release](https://img.shields.io/github/v/release/semuconsulting/pynmeagps?include_prereleases)\r\n![Build](https://img.shields.io/github/actions/workflow/status/semuconsulting/pynmeagps/main.yml?branch=master)\r\n![Codecov](https://img.shields.io/codecov/c/github/semuconsulting/pynmeagps)\r\n![Release Date](https://img.shields.io/github/release-date-pre/semuconsulting/pynmeagps)\r\n![Last Commit](https://img.shields.io/github/last-commit/semuconsulting/pynmeagps)\r\n![Contributors](https://img.shields.io/github/contributors/semuconsulting/pynmeagps.svg)\r\n![Open Issues](https://img.shields.io/github/issues-raw/semuconsulting/pynmeagps)\r\n\r\nThe 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](#extensibility). Refer to [`NMEA_MSGIDS`](https://github.com/semuconsulting/pynmeagps/blob/master/src/pynmeagps/nmeatypes_core.py#L224) and [`NMEA_MSGIDS_PROP`](https://github.com/semuconsulting/pynmeagps/blob/master/src/pynmeagps/nmeatypes_core.py#L367) for the complete dictionary of standard and proprietary messages currently supported. While the [NMEA 0183 protocol itself is proprietary](https://www.nmea.org/nmea-0183.html), the definitions here have been collated from public domain sources.\r\n\r\nSphinx API Documentation in HTML format is available at [https://www.semuconsulting.com/pynmeagps/](https://www.semuconsulting.com/pynmeagps/).\r\n\r\nContributions welcome - please refer to [CONTRIBUTING.MD](https://github.com/semuconsulting/pynmeagps/blob/master/CONTRIBUTING.md).\r\n\r\n[Bug reports](https://github.com/semuconsulting/pynmeagps/blob/master/.github/ISSUE_TEMPLATE/bug_report.md) and [Feature requests](https://github.com/semuconsulting/pynmeagps/blob/master/.github/ISSUE_TEMPLATE/feature_request.md) - please use the templates provided. For general queries and advice, post a message to one of the [pynmeagps Discussions](https://github.com/semuconsulting/pynmeagps/discussions) channels.\r\n\r\n---\r\n## \u003ca name=\"installation\"\u003eInstallation\u003c/a\u003e\r\n\r\n![Python version](https://img.shields.io/pypi/pyversions/pynmeagps.svg?style=flat)\r\n[![PyPI version](https://img.shields.io/pypi/v/pynmeagps.svg?style=flat)](https://pypi.org/project/pynmeagps/)\r\n![PyPI downloads](https://img.shields.io/pypi/dm/pynmeagps.svg?style=flat)\r\n\r\n`pynmeagps` is compatible with Python 3.9 - 3.13. In the following, `python3` \u0026 `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`*).\r\n\r\nThe recommended way to install the latest version of `pynmeagps` is with [pip](http://pypi.python.org/pypi/pip/):\r\n\r\n```shell\r\npython3 -m pip install --upgrade pynmeagps\r\n```\r\n\r\nIf required, `pynmeagps` can also be installed into a virtual environment, e.g.:\r\n\r\n```shell\r\npython3 -m venv env\r\nsource env/bin/activate # (or env\\Scripts\\activate on Windows)\r\npython3 -m pip install --upgrade pynmeagps\r\n```\r\n\r\nFor [Conda](https://docs.conda.io/en/latest/) users, `pynmeagps` is also available from [conda forge](https://github.com/conda-forge/pynmeagps-feedstock):\r\n\r\n[![Anaconda-Server Badge](https://anaconda.org/conda-forge/pynmeagps/badges/version.svg)](https://anaconda.org/conda-forge/pynmeagps)\r\n[![Anaconda-Server Badge](https://img.shields.io/conda/dn/conda-forge/pynmeagps)](https://anaconda.org/conda-forge/pynmeagps)\r\n\r\n```shell\r\nconda install -c conda-forge pynmeagps\r\n```\r\n\r\n---\r\n## \u003ca name=\"reading\"\u003eReading (Streaming)\u003c/a\u003e\r\n\r\n```\r\nclass pynmeagps.nmeareader.NMEAReader(stream, **kwargs)\r\n```\r\n\r\nYou can create an `NMEAReader` object by calling the constructor with an active stream object. \r\nThe stream object can be any data stream which supports a `read(n) -\u003e bytes` method (e.g. File or Serial, with \r\nor 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).\r\n\r\nIndividual 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.\r\n\r\nThe constructor accepts the following optional keyword arguments:\r\n\r\n* `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)\r\n* `nmeaonly`: True = raise error if stream contains non-NMEA data, False = ignore non-NMEA data (default)\r\n* `validate`: validation flags `VALCKSUM` (0x01) = validate checksum (default), `VALMSGID` (0x02) = validate msgId (i.e. raise error if unknown NMEA message is received)\r\n* `quitonerror`: `ERR_IGNORE` (0) = ignore errors,  `ERR_LOG` (1) = log continue, `ERR_RAISE` (2) = (re)raise (1)\r\n* `userdefined`: An optional user-defined payload definition dictionary, supplementing the existing `NMEA_PAYLOADS_GET` and `NMEA_PAYLOADS_GET_PROP` dictionaries (None).\r\n\r\nExamples:\r\n\r\n* Serial input - this example will ignore any non-NMEA data.\r\n\r\n```python\r\nfrom serial import Serial\r\nfrom pynmeagps import NMEAReader\r\nwith Serial('/dev/tty.usbmodem14101', 9600, timeout=3) as stream:\r\n  nmr = NMEAReader(stream)\r\n  raw_data, parsed_data = nmr.read()\r\n  if parsed_data is not None:\r\n    print(parsed_data)\r\n```\r\n\r\n* File input (using iterator) - this example will produce a `NMEAStreamError` if non-NMEA data is encountered.\r\n\r\n```python\r\nfrom pynmeagps import NMEAReader\r\nwith open('nmeadata.log', 'rb') as stream:\r\n  nmr = NMEAReader(stream, nmeaonly=True)\r\n  for raw_data, parsed_data in nmr: \r\n    print(parsed_data)\r\n```\r\n\r\n* Socket input (using iterator):\r\n\r\n```python\r\nimport socket\r\nfrom pynmeagps import NMEAReader\r\nwith socket.socket(socket.AF_INET, socket.SOCK_STREAM) as stream:\r\n  stream.connect((\"localhost\", 50007))\r\n  nmr = NMEAReader(stream)\r\n  for raw_data, parsed_data in nmr:\r\n    print(parsed_data)\r\n```\r\n\r\n---\r\n## \u003ca name=\"parsing\"\u003eParsing\u003c/a\u003e\r\n\r\nYou 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.\r\n\r\nNote 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.\r\n\r\nAttributes within repeating groups are parsed with a two-digit suffix (svid_01, svid_02, etc.).\r\n\r\nThe `parse()` function accepts the following optional keyword arguments:\r\n\r\n* `msgmode`: 0 = GET (default), 1 = SET, 2 = POLL\r\n* `validate`: validation flags `VALCKSUM` (0x01) = validate checksum (default), `VALMSGID` (0x02) = validate msgId (i.e. raise error if unknown NMEA message is received)\r\n* `quitonerror`: `ERR_IGNORE` (0) = ignore errors,  `ERR_LOG` (1) = log continue, `ERR_RAISE` (2) = (re)raise (1)\r\n* `userdefined`: An optional user-defined payload definition dictionary, supplementing the existing `NMEA_PAYLOADS_GET` and `NMEA_PAYLOADS_GET_PROP` dictionaries (None).\r\n\r\nExample:\r\n\r\n```python\r\nfrom pynmeagps import NMEAReader\r\nmsg = NMEAReader.parse('$GNGLL,5327.04319,S,00214.41396,E,223232.00,A,A*68\\r\\n')\r\nprint(msg)\r\n```\r\n```\r\n\u003cNMEA(GNGLL, lat=-53.45072, NS=S, lon=2.240233, EW=E, time=22:32:32, status=A, posMode=A)\u003e\r\n```\r\n\r\nThe `NMEAMessage` object exposes different public attributes depending on its message ID,\r\ne.g. the `RMC` message has the following attributes:\r\n\r\n```python\r\nfrom pynmeagps import latlon2dms, latlon2dmm\r\nprint(msg)\r\nprint(msg.msgID)\r\nprint(msg.lat, msg.lon)\r\nprint(msg.spd)\r\nprint(latlon2dms((msg.lat, msg.lon)))\r\nprint(latlon2dmm((msg.lat, msg.lon)))\r\n\r\n```\r\n```\r\n\u003cNMEA(GNRMC, time=22:18:38, status=A, lat=52.62063, NS=N, lon=-2.16012, EW=W, spd=37.84, cog=, date=2021-03-05, mv=, mvEW=, posMode=A)\u003e\r\n'RMC'\r\n(52.62063, -2.16012)\r\n37.84\r\n('52°37′14.268″N', '2°9′36.432″W')\r\n('52°37.2378′N', '2°9.6072′W')\r\n```\r\n\r\nIf 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,\r\n `NMEAMessage` will parse the message to a NOMINAL structure e.g.:\r\n\r\n```python\r\nfrom pynmeagps import NMEAReader, VALCKSUM\r\nmsg = NMEAReader.parse('$GNACN,103607.00,ECN,E,A,W,A,test,C*67\\r\\n', validate=VALCKSUM)\r\nprint(msg)\r\n```\r\n```\r\n\u003cNMEA(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)\u003e\r\n```\r\n\r\n---\r\n## \u003ca name=\"generating\"\u003eGenerating\u003c/a\u003e\r\n\r\n```\r\nclass pynmeagps.nmeamessage.NMEAMessage(talker: str, msgID: str, msgmode: int, **kwargs)\r\n```\r\n\r\nYou can create an `NMEAMessage` object by calling the constructor with the following parameters:\r\n1. talker (must be a valid talker from `pynmeagps.NMEA_TALKERS`)\r\n1. message id (must be a valid id from `pynmeagps.NMEA_MSGIDS` or `pynmeagps.NMEA_MSGIDS_PROP`)\r\n1. msgmode (0=GET, 1=SET, 2=POLL)\r\n1. hpnmeamode - boolean flag to signify high-precision NMEA mode (7 dp rather than 5) (False)\r\n1. validate - integer flag for checksum and/or message type validation (0=VALNONE, 1=VALCKSUM, 2=VALMSGID) (1)\r\n1. userdefined - an optional user-defined payload definition dictionary (None)\r\n1. (optional) a series of keyword parameters representing the message payload\r\n\r\nThe 'msgmode' parameter signifies whether the message payload refers to a:\r\n\r\n* 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)\r\n* SET message (i.e. command input to the receiver)\r\n* POLL message (i.e. query input to the receiver in anticipation of a response back)\r\n\r\nThe message payload can be defined via keyword arguments in one of two ways: \r\n1. A single keyword parameter of `payload` containing the full payload as a list of string values (any other keyword parameters will be ignored).\r\n2. 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_). \r\n\r\ne.g. Create a GLL message, passing the entire payload as a list of strings in native NMEA format:\r\n\r\n```python\r\nfrom pynmeagps import NMEAMessage, GET\r\npyld=['4330.00000','N','00245.000000','W','120425.234','A','A']\r\nmsg = NMEAMessage('GN', 'GLL', GET, payload=pyld)\r\nprint(msg)\r\n```\r\n```\r\n\u003cNMEA(GNGLL, lat=43.5, NS=N, lon=-2.75, EW=W, time=12:04:25.234000, status=A, posMode=A)\u003e\r\n```\r\n\r\ne.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):\r\n\r\n```python\r\nfrom pynmeagps import NMEAMessage, GET\r\nmsg = NMEAMessage('GN', 'GLL', GET, lat=43.5, lon=-2.75, status='A', posMode='A')\r\nprint(msg)\r\n```\r\n```\r\n\u003cNMEA(GNGLL, lat=43.5, NS='N', lon=-2.75, EW='W', time='12:04:25.234745', status='A', posMode='A')\u003e\r\n```\r\n\r\n```python\r\nfrom pynmeagps import NMEAMessage, POLL\r\nmsg = NMEAMessage('EI', 'GNQ', POLL, msgId='RMC')\r\nprint(msg)\r\n```\r\n```\r\n\u003cNMEA(EIGNQ, msgId=RMC)\u003e\r\n```\r\n\r\nBy 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.\r\n\r\n```python\r\nfrom pynmeagps import NMEAMessage, GET\r\nmsgsp = NMEAMessage('GN', 'GLL', GET, lat=43.123456789, lon=-2.987654321, status='A', posMode='A', hpnmeamode=0) # standard precision\r\nprint(msgsp)\r\nmsghp = NMEAMessage('GN', 'GLL', GET, lat=-43.123456789, lon=2.987654321, status='A', posMode='A', hpnmeamode=1) # high precision\r\nprint(msghp)\r\n```\r\n```\r\nNMEAMessage('GN','GLL', 0, payload=['4307.40741', 'N', '00259.25926', 'W', '095045.78', 'A', 'A'])\r\nNMEAMessage('GN','GLL', 0, payload=['4307.4074073', 'S', '00259.2592593', 'E', '094824.88', 'A', 'A'])\r\n```\r\n\r\n**NB:** Once instantiated, an `NMEAMessage` object is immutable.\r\n\r\n---\r\n## \u003ca name=\"serializing\"\u003eSerializing\u003c/a\u003e\r\n\r\nThe `NMEAMessage` class implements a `serialize()` method to convert an `NMEAMessage` object to a bytes array suitable for writing to an output stream.\r\n\r\n```python\r\nfrom serial import Serial\r\nfrom pynmeagps import NMEAMessage, POLL\r\nstream = Serial('COM6', 38400, timeout=3)\r\nmsg = NMEAMessage('EI','GNQ', POLL, msgId='RMC')\r\nprint(msg.serialize())\r\nstream.write(msg.serialize())\r\n```\r\n```\r\nb'$EIGNQ,RMC*24\\r\\n'\r\n```\r\n\r\n---\r\n## \u003ca name=\"utilities\"\u003eUtility Methods\u003c/a\u003e\r\n \r\n `pynmeagps` provides the following utility methods:\r\n\r\n - `latlon2dms` - converts decimal lat/lon to degrees, minutes, decimal seconds format e.g. \"53°20′45.6″N\", \"2°32′46.68″W\"\r\n - `latlon2dmm` - converts decimal lat/lon to degrees, decimal minutes format e.g. \"53°20.76′N\", \"2°32.778′W\"\r\n - `dms2deg` - converts lat/lon in d.m(.s) string format to signed decimal degrees e.g. \"51°20′45.6″S\" -\u003e -51.346\r\n - `llh2iso6709` - converts lat/lon and altitude (hMSL) to ISO6709 format e.g. \"+27.5916+086.5640+8850CRSWGS_84/\"\r\n - `ecef2llh` - converts ECEF (X, Y, Z) coordinates to geodetic (lat, lon, ellipsoidal height) coordinates\r\n - `llh2ecef` - converts geodetic (lat, lon, ellipsoidal height) coordinates to ECEF (X, Y, Z) coordinates\r\n - `haversine` - finds great circle distance in km between two sets of (lat, lon) coordinates\r\n - `planar` - finds planar distance in m between two sets of (lat, lon) coordinates\r\n - `bearing` - finds bearing in degrees between two sets of (lat, lon) coordinates\r\n - `area` - finds spherical area bounded by two sets of (lat, lon) coordinates\r\n\r\nSee [Sphinx documentation](https://www.semuconsulting.com/pynmeagps/pynmeagps.html#module-pynmeagaps.nmeahelpers) for details.\r\n\r\n---\r\n## \u003ca name=\"examples\"\u003eExamples\u003c/a\u003e\r\n\r\nThe following command line examples can be found in the `/examples` folder:\r\n\r\n1. `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.\r\n\r\n1. `nmeafile.py` illustrates how to implement an NMEA datalog file reader using `pynmeagps.NMEAReader` iterator functionality.\r\n\r\n1. `nmeasocket.py` illustrates how to implement a TCP Socket reader for NMEA messages using NMEAReader iterator functionality.\r\n\r\n1. `gpxtracker.py` illustrates a simple utility to convert an NMEA datalog file to a `*.gpx` track file using `pynmeagps.NMEAReader`.\r\n\r\n1. `/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.\r\n\r\n1. `utilities.py` illustrates how to use various `pynmeagps` utility methods.\r\n---\r\n## \u003ca name=\"extensibility\"\u003eExtensibility\u003c/a\u003e\r\n\r\nThe NMEA protocol is principally defined in the modules `nmeatypes_*.py` as a series of dictionaries. Additional message types \r\ncan be readily added to the appropriate dictionary. Message payload definitions must conform to the following rules:\r\n\r\n```\r\n1. attribute names must be unique within each message class\r\n2. avoid reserved names 'msgID', 'talker', 'payload', 'checksum'.\r\n3. attribute types must be one of the valid types (IN, DE, CH, etc.)\r\n4. repeating groups must be defined as a tuple ('numr', {dict}), where:\r\n   'numr' is either:\r\n     a. an integer representing a fixed number of repeats e.g. 32\r\n     b. a string representing the name of a preceding attribute containing the number of repeats e.g. 'numSv'\r\n     c. 'None' for an indeterminate repeating group. Only one such group is permitted per payload and it must be at the end.\r\n   {dict} is the nested dictionary of repeating items\r\n```\r\n\r\n---\r\n## \u003ca name=\"cli\"\u003eCommand Line Utility\u003c/a\u003e\r\n\r\nA 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.\r\n\r\nTo install `pygnssutils`:\r\n```\r\npython3 -m pip install --upgrade pygnssutils\r\n```\r\n\r\nFor help with the `gnssstreamer` utility, type:\r\n```\r\ngnssstreamer -h\r\n```\r\n\r\n---\r\n## \u003ca name=\"gui\"\u003eGraphical Client\u003c/a\u003e\r\n\r\nA python/tkinter graphical GPS client which supports NMEA, UBX and RTCM3 protocols is available at: \r\n\r\n[https://github.com/semuconsulting/PyGPSClient](https://github.com/semuconsulting/PyGPSClient)\r\n\r\n---\r\n## \u003ca name=\"author\"\u003eAuthor \u0026 License Information\u003c/a\u003e\r\n\r\nsemuadmin@semuconsulting.com\r\n\r\n![License](https://img.shields.io/github/license/semuconsulting/pynmeagps.svg)\r\n\r\n`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...\r\n\r\n[![Sponsor](https://github.com/semuconsulting/pyubx2/blob/master/images/sponsor.png?raw=true)](https://buymeacoffee.com/semuconsulting)\r\n\r\n[![Freedom for Ukraine](https://github.com/semuadmin/sandpit/blob/main/src/sandpit/resources/ukraine200.jpg?raw=true)](https://u24.gov.ua/)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemuconsulting%2Fpynmeagps","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsemuconsulting%2Fpynmeagps","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsemuconsulting%2Fpynmeagps/lists"}