Library / SDK
Knio/pynmea2 avatar
Knio/pynmea2

pynmea2: parsing NMEA 0183 sentences from a GPS into Python objects

Python library for parsing the NMEA 0183 protocol (GPS)

682 stars229 forksPythonMIT

At a glance

What is it?
pynmea2 is a Python library for the NMEA 0183 protocol that turns a raw GPS sentence into a typed object with named properties, validates the checksum, and can also generate a sentence from an object. It suits a developer reading position data off a serial device or a log file, and it is a parser for one protocol rather than a full GNSS stack.
Who is it for?
pynmea2 fits a developer who already has NMEA sentences from a GPS receiver, a serial port or a log file, and who wants them as Python objects with named fields and a checksum check, and it does not fit someone who needs binary protocol support, satellite constellation status, or a fix on a receiver that speaks something other than NMEA 0183.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A sentence parser with named fields, and one protocol only

pynmea2 is a Python library for the NMEA 0183 protocol, the line based text format that GPS receivers use to report position, altitude, satellites and time. The project is a rewrite of pynmea by Becky Lewis, whose original project lived on Google Code, and the current homepage is at the Knio/pynmea2 repository on GitHub.

The core of the library is one function. You can parse individual NMEA sentences using the parse(data) function, which takes a string containing an NMEA 0183 sentence and returns a NMEASentence object. Note that the leading '$' is optional and trailing whitespace is ignored when parsing a sentence.

That last detail is a small sign of how the library was built. A parser that insists on the exact wire format is a parser that breaks on real data, where a receiver emits a line with a carriage return, or a log file has a blank line at the end. Tolerating both costs nothing and removes a class of bugs.

What the library does not do is worth stating too. It parses NMEA 0183 and nothing else. There is no support for binary protocols, no satellite constellation state, no NMEA 2000 sentences, and no support for the vendor extensions that many receivers emit. The package structure in setup.py shows three packages, pynmea2, pynmea2.types and pynmea2.types.proprietary, so there is room for proprietary message types, but the scope is a parser and nothing more.

The primary language is Python, the licence is MIT, the last push was on 2026-04-21, and the recent releases are 1.18.0 from 2021-04-13, 1.17.0 from 2021-03-22 and 1.16.0 from 2021-02-20.

Sentence types become attributes, and a GGA example

The NMEASentence object has different properties depending on its sentence type, and the README's example is a GGA fix, which is the sentence type most people meet first.

python
>>> 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")
>>> msg
<GGA(timestamp=datetime.time(18, 43, 53), lat='1929.045', lat_dir='S', lon='02410.506', lon_dir='E', gps_qual='1', num_sats='04', horizontal_dil='2.6', altitude=100.0, altitude_units='M', geo_sep='-33.9', geo_sep_units='M', age_gps_data='', ref_station_id='0000')>

Each field of the sentence becomes a named attribute, and the sentence type is a class, GGA here, so the type of the object tells you what the data means. The repr shows the whole payload at once, which is what you want when you are reading a stream of sentences and need to see what a device is actually emitting.

The properties are then accessible one at a time, and the example walks through them.

python
>>> msg.timestamp
datetime.time(18, 43, 53)
>>> msg.lat
'1929.045'
>>> msg.lat_dir
'S'

Note the two-part latitude. The protocol transmits coordinates as degrees, minutes and decimal minutes, so the raw fields are the string '1929.045' and the hemisphere 'S'. The library does not merge them, and the reason is on the next section.

The timestamp is parsed into a datetime.time, so the time component is a Python object rather than a string, but the numeric fields are left as the strings the protocol sent. That is a deliberate choice about fidelity, and it is worth knowing before you write code that assumes a latitude is already a float.

Coordinate helpers, and the DDDMM.MMMM to decimal degrees conversion

Because the raw fields are strings in the DDDMM.MMMM format, degrees, minutes and decimal minutes, the library provides helper properties that do the conversion.

The README explains that additional properties besides the ones explicitly in the message data may also exist, and gives latitude and longitude as helpers to access the geographic coordinates as Python floats, in decimal degrees, instead of the DDDMM.MMMM format used in the NMEA protocol. There are also latitude_minutes, latitude_seconds, longitude_minutes, and longitude_seconds, which allow easy creation of differently formatted location strings.

python
>>> msg.latitude
-19.4840833333
>>> msg.longitude
24.1751
>>> '%02d°%07.4f′' % (msg.latitude, msg.latitude_minutes)
'-19°29.0450′'
>>> '%02d°%02d′%07.4f″' % (msg.latitude, msg.latitude_minutes, msg.latitude_seconds)
"-19°29′02.7000″"

The signed value is worth noting. The raw sentence has a southern latitude, so the helper returns a negative number, which is what you want for anything that will be plotted on a map or sent to a geocoding API. The format examples show the other direction, turning the parsed value back into the degree, minute and second notation that a paper map or a display might want.

The design here is a good example of leaving the raw data alone and adding derived views on top. A library that rewrote the coordinates in place would save you a call but would lose the exact string the receiver sent, and for a protocol with fixed-width fields that string is sometimes the thing you actually want to log.

Checksums, and three policies for handling a corrupt line

The Checksums section is the part of the library with real defensive design, and it is worth understanding before you wire it into a data pipeline.

The parse function has an optional argument with three values: checksums equals 'check', which is the default, or 'required', or 'my_data_is_corrupt'. The table of outcomes is explicit.

A sentence with a valid checksum is accepted in all three modes. A sentence without a checksum is accepted by 'check' and by 'my_data_is_corrupt' but raises ChecksumError in 'required'. A sentence with an invalid checksum raises ChecksumError in both 'required' and 'check' but is accepted in 'my_data_is_corrupt'.

The third mode has an obvious-sounding name and an obvious purpose. It is the escape hatch for a device that sends data you know is unreliable, and the price is that you accept invalid checksums on faith.

The note attached to the table is the part to keep in mind. If a checksum is invalid, it is more likely than not that the sentence values themselves are corrupt, so be wary of using them without further validation. The corruption may also cause parsing errors which could raise ParseError even before the checksums parameter takes effect, so you will still have to handle an exception.

That last sentence is a practical warning. A caller who sets checksums='my_data_is_corrupt' to swallow bad checksums still has to catch ParseError, because a corrupt field can fail to parse before the checksum is ever examined. The parameter does not make a corrupt line safe to use, it only stops the checksum from being the thing that rejects it.

Generating a sentence, and reading from a file or a serial port

The library works in both directions, and the reverse direction is the part people do not expect from a parser.

You can create a NMEASentence object by calling the constructor with talker, message type, and data fields.

python
>>> import pynmea2
>>> msg = pynmea2.GGA('GP', 'GGA', ('184353.07', '1929.045', 'S', '02410.506', 'E', '1', '04', '2.6', '100.00', 'M', '-33.9', 'M', '', '0000'))

And you can generate an NMEA string from a NMEASentence object, by calling str on it.

python
>>> str(msg)
'$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D'

Note that the checksum is computed on the way out, since the string it produces ends with *6D and matches the sentence that was parsed at the start. That makes the library useful as a test fixture generator, as a way to synthesise a GPS feed for a simulator, and as a way to reproduce a sentence in a bug report.

For reading real data, the repository ships two examples. The file reading example opens a log and parses each line.

python
import pynmea2

file = open('examples/data.log', encoding='utf-8')

for line in file.readlines():
    try:
        msg = pynmea2.parse(line)
        print(repr(msg))
    except pynmea2.ParseError as e:
        print('Parse error: {}'.format(e))
        continue

The pySerial device example does the same over a serial port, with a text wrapper around the serial object and a five second timeout.

python
import io

import pynmea2
import serial


ser = serial.Serial('/dev/ttyS1', 9600, timeout=5.0)
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser))

The serial example also distinguishes a device error from a parse error, breaking out of the loop on the former and continuing on the latter, which is the right shape for a reader that has to survive a cable being unplugged mid-session.

What the release history says about the state of the project

The version numbers deserve a note, because the last push and the last release are a long way apart.

The recent releases are 1.18.0 from 2021-04-13, 1.17.0 from 2021-03-22 and 1.16.0 from 2021-02-20, and the last push was on 2026-04-21. So the tagged releases stopped in April 2021 while the repository has seen commits as recently as April 2026.

The likely explanation is visible in the setup.py, which loads the version from a source file rather than hard coding it. The module reads pynmea2/_version.py with a helper that uses importlib.machinery.SourceFileLoader, and the comment in that helper cites the Python 3.12 documentation on the imp module removal. The version is therefore generated from the git metadata, which is what a versioning tool does, and it means the working tree can report a version that no release has been published under.

For an adopter, the practical difference matters. A pip install of pynmea2 gives you the 1.18.0 published in 2021, and the README's compatibility statement is explicit: pynmea2 is compatible with Python 2.7 and Python 3.4 and later. That statement is old. A project that still declares support for Python 2.7 was not being tested against current interpreters, and the fact that the README has not been updated is itself a signal about how much attention the documentation receives now.

The licence is MIT, and the classifiers in setup.py reflect the same vintage, listing the Python 2 and 2.7 entries alongside the Python 3.4 through 3.9 entries. The repository also carries the NMEA0183.pdf specification document at the top level, which is a nice touch for anyone who wants to check a sentence format against the source.

Editorial conclusion

pynmea2 fits a developer who already has NMEA sentences from a GPS receiver, a serial port or a log file, and who wants them as Python objects with named fields and a checksum check, and it does not fit someone who needs binary protocol support, satellite constellation status, or a fix on a receiver that speaks something other than NMEA 0183. Before depending on it, decide how you want checksum failures handled, because the default check mode still accepts a sentence with no checksum at all, and a stricter required mode raises ChecksumError on the same line.

Frequently asked questions

How do I install pynmea2?

The recommended way to install pynmea2 is with pip, using pip install pynmea2. The library is MIT licensed and published on PyPI as pynmea2, and the README states it is compatible with Python 2.7 and Python 3.4 and later.

How does pynmea2 handle a sentence with a bad checksum?

The parse function takes a checksums argument with three values. The default 'check' accepts a sentence with no checksum but raises ChecksumError on an invalid one, 'required' raises ChecksumError when a checksum is missing or invalid, and 'my_data_is_corrupt' accepts both. The README warns that a corrupt sentence should be validated further, and that a ParseError can still be raised before the checksum parameter takes effect.

Can pynmea2 produce NMEA sentences as well as read them?

Yes. You can construct a NMEASentence by calling the constructor with talker, message type and data fields, and str on the object produces the NMEA string with its checksum, so the library can generate test fixtures or synthetic GPS data.

How do I get decimal degrees from a pynmea2 sentence?

The sentence type exposes helper properties beyond the raw message fields, including latitude and longitude as Python floats in decimal degrees, plus latitude_minutes, latitude_seconds, longitude_minutes and longitude_seconds for building differently formatted location strings.

Official sources

  1. Issues
  2. Knio/pynmea2 on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/knio-pynmea2.svg)](https://hysenlabs.com/projects/knio-pynmea2)

Was this useful?