A NMEA parser library in pure Go

go-nmea

Build Status Go Report Card Coverage Status GoDoc

This is a NMEA library for the Go programming language (Golang).

Features

  • Parse individual NMEA 0183 sentences
  • Support for sentences with NMEA 4.10 "TAG Blocks"
  • Register custom parser for unsupported sentence types
  • User-friendly MIT license

Installing

To install go-nmea use go get:

go get github.com/adrianmo/go-nmea

This will then make the github.com/adrianmo/go-nmea package available to you.

Staying up to date

To update go-nmea to the latest version, use go get -u github.com/adrianmo/go-nmea.

Supported sentences

At this moment, this library supports the following sentence types:

Sentence type Description
RMC Recommended Minimum Specific GPS/Transit data
PMTK Messages for setting and reading commands for MediaTek gps modules.
GGA GPS Positioning System Fix Data
GSA GPS DOP and active satellites
GSV GPS Satellites in view
GLL Geographic Position, Latitude / Longitude and time
VTG Track Made Good and Ground Speed
ZDA Date & time data
HDT Actual vessel heading in degrees True
GNS Combined GPS fix for GPS, Glonass, Galileo, and BeiDou
PGRME Estimated Position Error (Garmin proprietary sentence)
THS Actual vessel heading in degrees True and status
VDM/VDO Encapsulated binary payload
WPL Waypoint location
RTE Route
VHW Water Speed and Heading
DPT Depth of Water
DBS Depth Below Surface
DBT Depth below transducer

If you need to parse a message that contains an unsupported sentence type you can implement and register your own message parser and get yourself unblocked immediately. Check the example below to know how to implement and register a custom message parser. However, if you think your custom message parser could be beneficial to other users we encourage you to contribute back to the library by submitting a PR and get it included in the list of supported sentences.

Examples

Built-in message parsing

package main

import (
	"fmt"
	"log"
	"github.com/adrianmo/go-nmea"
)

func main() {
	sentence := "$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70"
	s, err := nmea.Parse(sentence)
	if err != nil {
		log.Fatal(err)
	}
	if s.DataType() == nmea.TypeRMC {
		m := s.(nmea.RMC)
		fmt.Printf("Raw sentence: %v\n", m)
		fmt.Printf("Time: %s\n", m.Time)
		fmt.Printf("Validity: %s\n", m.Validity)
		fmt.Printf("Latitude GPS: %s\n", nmea.FormatGPS(m.Latitude))
		fmt.Printf("Latitude DMS: %s\n", nmea.FormatDMS(m.Latitude))
		fmt.Printf("Longitude GPS: %s\n", nmea.FormatGPS(m.Longitude))
		fmt.Printf("Longitude DMS: %s\n", nmea.FormatDMS(m.Longitude))
		fmt.Printf("Speed: %f\n", m.Speed)
		fmt.Printf("Course: %f\n", m.Course)
		fmt.Printf("Date: %s\n", m.Date)
		fmt.Printf("Variation: %f\n", m.Variation)
	}
}

Output:

$ go run main/main.go

Raw sentence: $GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70
Time: 22:05:16.0000
Validity: A
Latitude GPS: 5133.8200
Latitude DMS: 51° 33' 49.200000"
Longitude GPS: 042.2400
Longitude DMS: 0° 42' 14.400000"
Speed: 173.800000
Course: 231.800000
Date: 13/06/94
Variation: -4.200000

TAG Blocks

NMEA 4.10 TAG Block values can be accessed via the message's TagBlock struct:

package main

import (
	"fmt"
	"log"
	"time"
	"github.com/adrianmo/go-nmea"
)

func main() {
	sentence := "\\s:Satelite_1,c:1553390539*62\\!AIVDM,1,1,,A,13M@ah0025QdPDTCOl`K6`nV00Sv,0*52"
	s, err := nmea.Parse(sentence)
	if err != nil {
		log.Fatal(err)
	}
	parsed := s.(nmea.VDMVDO)
	fmt.Printf("TAG Block timestamp: %v\n", time.Unix(parsed.TagBlock.Time, 0))
	fmt.Printf("TAG Block source:    %v\n", parsed.TagBlock.Source)
}

Output (locale/time zone dependent):

$  go run main/main.go
 
TAG Block timestamp: 2019-03-24 14:22:19 +1300 NZDT
TAG Block source:    Satelite_1

Custom message parsing

If you need to parse a message not supported by the library you can implement your own message parsing. The following example implements a parser for the hypothetical XYZ NMEA sentence type.

package main

import (
	"fmt"

	"github.com/adrianmo/go-nmea"
)

// A type to hold the parsed record
type XYZType struct {
	nmea.BaseSentence
	Time    nmea.Time
	Counter int64
	Label   string
	Value   float64
}

func main() {
	// Do this once it will error if you register the same type multiple times
	err := nmea.RegisterParser("XYZ", func(s nmea.BaseSentence) (nmea.Sentence, error) {
		// This example uses the package builtin parsing helpers
		// you can implement your own parsing logic also
		p := nmea.NewParser(s)
		return XYZType{
			BaseSentence: s,
			Time:         p.Time(0, "time"),
			Label:        p.String(1, "label"),
			Counter:      p.Int64(2, "counter"),
			Value:        p.Float64(3, "value"),
		}, p.Err()
	})

	if err != nil {
		panic(err)
	}

	sentence := "$00XYZ,220516,A,23,5133.82,W*42"
	s, err := nmea.Parse(sentence)
	if err != nil {
		panic(err)
	}

	m, ok := s.(XYZType)
	if !ok {
		panic("Could not parse type XYZ")
	}

	fmt.Printf("Raw sentence: %v\n", m)
	fmt.Printf("Time: %s\n", m.Time)
	fmt.Printf("Label: %s\n", m.Label)
	fmt.Printf("Counter: %d\n", m.Counter)
	fmt.Printf("Value: %f\n", m.Value)
}

Output:

$ go run main/main.go

Raw sentence: $AAXYZ,220516,A,23,5133.82,W*42
Time: 22:05:16.0000
Label: A
Counter: 23
Value: 5133.820000

Contributing

Please feel free to submit issues or fork the repository and send pull requests to update the library and fix bugs, implement support for new sentence types, refactor code, etc.

License

Check LICENSE.

Comments
  • Implement support for VDM/VDO encapsulated binary messages

    Implement support for VDM/VDO encapsulated binary messages

    Hello,

    This pull request implements support for VDM/VDO encapsulated message parsing. It requires a change to sentence.go as these messages use '!' instead of '$' as start byte.

    VDM and VDO are the same message, depending on the circumstances or configuration the sender will produce one or the other, but they are handled identically. Currently I made one decoder that handles both messages, is this the way to go?

    Sincerely, Bertold

  • Default value for empty string

    Default value for empty string

    When a value is missing in a nmea string the float or int value in the parsed sentence is set to 0. This way it is hard to determine if the value is really 0 or the data was missing. E.g. $GPVTG,0.3,T,,M,,N,12.6,K*78 would result in GroundSpeedKnots: 0 and GroundSpeedKPH: 12.6. Now it is easy to guess that that GroundSpeedKnots is invalid, but for TrueTrack: 0.3 and MagneticTrack: 0 it is not possible.

    Is there a specific reason why this is done in this way?

    I would suggest to add getters to the sentences for all the value like:

    func (s VTG) GetTrueTrack() (float64, error) {
    	if s.hasValidTrueTrack {
    		return s.TrueTrack, nil
    	}
    	return 0, fmt.Errorf("TrueTrack is missing in the nmea string")
    }
    

    These getters can check an internal state for each value and return an error if the value was not available in the original nmea string. This will also not break existing code because it is still possible to use TrueTrack, MagneticTrack etc directly.

  • Accept other talkers than

    Accept other talkers than "GP"

    The NMEA 0183 protocol offers multiple talkers based on which equipment generated the message.
    GP indicates a GPS receiver, GL a GLONASS receiver, GN a combined GNSS receiver, IN an integrated navigation receiver and so forth. See here for examples of different talkers.

    As of today, go-nmea only accepts GP for most messages as talker. Is there an easy way of allowing different talkers for all NMEA messages, or is it necessary to implement specific functions for all talkers?

    A concrete example is the HDT, which in many cases is HEHDT and INHDT.

    An easy (but dirty) way is of course to manipulate the talker into GP.

  • Functions for talker and message type

    Functions for talker and message type

    To make it easier to use sentences where the talker is unknown, I've added functions to retrieve the talker and message type separately.
    This gives the opportunity to case switch the type of message, which e.g. would be nice to have when receiving $GNGGA and $GPGGA simultaneously.

  • expose xorChecksum

    expose xorChecksum

    expose xorChecksum to be able to use when creating MTK requests.

    discussion https://github.com/adrianmo/go-nmea/pull/60#pullrequestreview-271338475

    example usage: https://github.com/arribada/smartLoraTracker/blob/3ae623f282f79d2b2f08045381a9e037d9ff6ab8/tools/gpsLora/gpsLora.go#L57

    Signed-off-by: Krasi Georgiev [email protected]

  • Make `ParseSentence` function unexported

    Make `ParseSentence` function unexported

    Judging by #18, it looks like the ParseSentence function is causing confusion when using the library. Making it unexported would remove this potential confusion.

    In a first inspection, I didn't see any reason to export the function as it's only used internally.

    @icholy any thoughts?

  • Major Refactor

    Major Refactor

    This is a pretty big refactor that introduce breaking changes. Let me know what you think.

    Breaking changes

    • Add Date & Time types.
    • Convert numeric fields to either float64 or int64.
    • Rename Sentence to Sent
    • Rename Sent.GetSentence() to Sent.Sentence()
    • Rename SentenceI to Message
    • Rename NewLatLong to ParseLatLong

    Non-Breaking changes

    • Add ParseSentence(string)
    • Add Sent.Prefix() string
    • Add nmea: prefix to error messages.
    • Unify error message format.
    • Add parser to simplify sentence parsing.
  • added memorization of latitude/longitude direction

    added memorization of latitude/longitude direction

    If the library stores geographical coordinate values, it is also good to store their directions. Thanks to this, you can recover complete information about the coordinates:

    fmt.Printf("Latitude GPS: %s %s\n", nmea.FormatGPS(m.Latitude), m.LatDirection)
    fmt.Printf("Latitude DMS: %s %s\n", nmea.FormatDMS(m.Latitude), m.LatDirection)
    fmt.Printf("Longitude GPS: %s %s\n", nmea.FormatGPS(m.Longitude), m.LonDirection)
    fmt.Printf("Longitude DMS: %s %s\n", nmea.FormatDMS(m.Longitude), m.LonDirection)
    
    Latitude GPS: 5433.2508 N
    Latitude DMS: 54° 33' 15.048000" N
    Longitude GPS: 1825.6375 E
    Longitude DMS: 18° 25' 38.250000" E
    
  • Support custom message types

    Support custom message types

    I would like to extend this lib to support custom message parsing, is this something you would consider merging. We are using this to parse some custom message which are not part of the NMEA standard. The idea is to have a way to register parsers for new message types.

  • Doesn't handle null fields.

    Doesn't handle null fields.

    I have the following sentence:

    $GPRMC,142754.0,A,4302.539570,N,07920.379823,W,0.0,,070617,0.0,E,A*3F

    Which fails to parse with: GPRMC decode course error: due to the parser not handling null fields correctly. The easiest solution would be to use 0 when there's a null field, but idk if that's a good idea.

    https://www.trimble.com/OEM_ReceiverHelp/V4.44/en/NMEA-0183messages_MessageOverview.html

    All messages conform to the NMEA-0183 version 3.01 format. All begin with $ and end with a carriage return and a line feed. Data fields follow comma (,) delimiters and are variable in length. Null fields still follow comma (,) delimiters, but contain no information.

  • Check sentence starts with dollar ($) sign

    Check sentence starts with dollar ($) sign

    Actually, my issue is that I have some malformed NMEA sentences due to serial communication issues, which led to a crash in the library. My intention here is simply to avoid the crash, not necessarily to recover from it. However, reading a bit about NMEA, it seems that $ is a "start delimiter", so I agree with your solution to start reading from the last $. I'm checking the * after splitting. I also added some tests.

  • processing multiline / segmented messages

    processing multiline / segmented messages

    I could not find an example for processing AIS 5 message here. This type of message has more than one line for example:

    \g:1-2-3730,s:43576,c:1654340281,t:165434038119!AIVDM,2,1,7,,569EH`8000009aGUB20IF1UD4r1UF3OK7>22220N4PT38t0000000000,042

    \g:2-2-37305A!AIVDM,2,2,7,,000000000000000,262

    Is there any way to process such cases?

  • Allow sentence parsing customization (SentenceParser struct) ...

    Allow sentence parsing customization (SentenceParser struct) ...

    • Allow sentence parsing customization (callbacks for CRC and tagblocks) (SentenceParser struct) - See #96
    • Add Query sentence,
    • Expose all supported types and their parsers as a map (SupportedParsers),
    • Add 1.18 into CI flow

    BREAKING CHANGE:

    • rename MTK sentence to PMTK001, See #90

    I have added MTK change here as it is deeply tied to sentence parsing internals and has "hacks" to override BaseSentence.TalkerID meaning.

  • possibility to make CRC check optional for certain sentences

    possibility to make CRC check optional for certain sentences

    Older devices sometimes do not implement NMEA0183 correctly by omitting CRC. It would be nice if there would be way to make parser ignore CRC mismatches for certain sentences.

    We are retrofitting 2005 built vessel that has GPS device sending sentences without CRC. Our navigation engineers say it is not very rare to have older devices like that. So for example devices reading/receiving NMEA0183 sentences have sometimes option to disable CRC checks because of this sad reality.

  • MTK implementation is currently meant/suitable only for command PMTK001 but there are other messages/commands in MTK

    MTK implementation is currently meant/suitable only for command PMTK001 but there are other messages/commands in MTK

    I think MTK implementation is not correct. MTK is command protocol embedded into NMEA sentence. Fields that current MTK struct has are meant for MTK packet "PMTK001" but actually MTK packet can have variable amound of data fields.

    See:

    • https://www.rhydolabz.com/documents/25/PMTK_A11.pdf
    • https://www.sparkfun.com/datasheets/GPS/Modules/PMTK_Protocol.pdf
  • Add GSensord - not sure how to deal with checksums being optional

    Add GSensord - not sure how to deal with checksums being optional

    NB: I don't actually expect you to merge this request, I've broken the handling of missing checksums.

    I also think this might be a specific usecase and not a standards type issue.

    Basically the Navman MIVUE dashcams add a $GSENSORD sentence that doesn't have checksums.

    I did consider refactoring to make types checksum aware so they can complain if they're expecting a checksum or not, but I wasn't sure how you'd like to proceed.

    Probably best to just leave it out entirely, or have an unexpected type handler, but then I could just do that with a substring match before parsing.

    Sample data:

    $GSENSORD,0.060,0.120,-0.180
    $GPRMC,073815.000,A,2703.7507,S,15257.3940,E,30.35,36.48,060419,,,A*74
    $GPGGA,073815.000,2703.7507,S,15257.3940,E,1,12,0.8,8.4,M,39.6,M,,0000*4B
    $GPGSA,A,3,22,31,03,01,193,26,194,18,23,16,14,11,1.6,0.8,1.4*31
    $GPGSV,4,1,16,01,44,297,37,03,55,211,28,09,01,263,,11,19,310,22*7C
    $GPGSV,4,2,16,14,22,118,27,16,27,032,40,18,33,331,30,22,80,195,37*7D
    $GPGSV,4,3,16,23,28,245,32,26,40,065,42,31,42,142,40,32,25,248,*7A
    $GPGSV,4,4,16,42,57,343,29,193,78,210,24,194,39,337,36,195,15,346,27*4D
    $GSENSORD,-0.120,0.120,-0.250
    $GPRMC,073816.000,A,2703.7438,S,15257.3995,E,30.42,35.62,060419,,,A*79
    $GPGGA,073816.000,2703.7438,S,15257.3995,E,1,12,0.8,8.4,M,39.6,M,,0000*4D
    $GPGSA,A,3,22,31,03,01,193,26,194,18,23,16,14,11,1.6,0.8,1.4*31
    $GPGSV,4,1,16,01,44,297,37,03,55,211,27,09,01,263,,11,19,310,21*70
    $GPGSV,4,2,16,14,22,118,25,16,27,032,39,18,33,331,31,22,80,195,37*70
    $GPGSV,4,3,16,23,28,245,33,26,40,065,43,31,42,142,29,32,25,248,*75
    $GPGSV,4,4,16,42,57,343,30,193,78,210,24,194,39,337,36,195,15,346,29*4B
    
  • Document message specification

    Document message specification

    Since there's no public canonical nmea reference, we've been linking to different resources on the web. It would be better to maintain our own copy of the message specifications that we implement.

A parser combinator library for Go.

Takenoco A parser combinator library for Go. Examples CSV parser Dust - toy scripting language Usage Define the parser: package csv import ( "err

Oct 30, 2022
A general purpose syntax highlighter in pure Go

Chroma — A general purpose syntax highlighter in pure Go NOTE: As Chroma has just been released, its API is still in flux. That said, the high-level i

Dec 27, 2022
A full-featured regex engine in pure Go based on the .NET engine

regexp2 - full featured regular expressions for Go Regexp2 is a feature-rich RegExp engine for Go. It doesn't have constant time guarantees like the b

Jan 9, 2023
HTML, CSS and SVG static renderer in pure Go

Web render This module implements a static renderer for the HTML, CSS and SVG formats. It consists for the main part of a Golang port of the awesome W

Apr 19, 2022
A shell parser, formatter, and interpreter with bash support; includes shfmt

sh A shell parser, formatter, and interpreter. Supports POSIX Shell, Bash, and mksh. Requires Go 1.14 or later. Quick start To parse shell scripts, in

Dec 29, 2022
TOML parser for Golang with reflection.

THIS PROJECT IS UNMAINTAINED The last commit to this repo before writing this message occurred over two years ago. While it was never my intention to

Dec 30, 2022
A simple CSS parser and inliner in Go

douceur A simple CSS parser and inliner in Golang. Parser is vaguely inspired by CSS Syntax Module Level 3 and corresponding JS parser. Inliner only p

Dec 12, 2022
User agent string parser in golang

User agent parsing useragent is a library written in golang to parse user agent strings. Usage First install the library with: go get xojoc.pw/userage

Aug 2, 2021
Simple HCL (HashiCorp Configuration Language) parser for your vars.

HCL to Markdown About To write a good documentation for terraform module, quite often we just need to print all our input variables as a fancy table.

Dec 14, 2021
A markdown parser written in Go. Easy to extend, standard(CommonMark) compliant, well structured.

goldmark A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured. goldmark is compliant with CommonMark 0.29. Motivation

Dec 29, 2022
Unified diff parser and printer for Go

go-diff Diff parser and printer for Go. Installing go get -u github.com/sourcegraph/go-diff/diff Usage It doesn't actually compute a diff. It only rea

Dec 14, 2022
A PDF renderer for the goldmark markdown parser.
A PDF renderer for the goldmark markdown parser.

goldmark-pdf goldmark-pdf is a renderer for goldmark that allows rendering to PDF. Reference See https://pkg.go.dev/github.com/stephenafamo/goldmark-p

Jan 7, 2023
Experimental parser Angular template

Experimental parser Angular template This repository only shows what a parser on the Go might look like Benchmark 100k line of template Parser ms @ang

Dec 15, 2021
A dead simple parser package for Go
A dead simple parser package for Go

A dead simple parser package for Go V2 Introduction Tutorial Tag syntax Overview Grammar syntax Capturing Capturing boolean value Streaming Lexing Sta

Dec 30, 2022
Freestyle xml parser with golang

fxml - FreeStyle XML Parser This package provides a simple parser which reads a XML document and output a tree structure, which does not need a pre-de

Jul 1, 2022
An extension to the Goldmark Markdown Parser

Goldmark-Highlight An extension to the Goldmark Markdown Parser which adds parsing / rendering capabilities for rendering highlighted text. Highlighte

May 25, 2022
A simple json parser built using golang

jsonparser A simple json parser built using golang Installation: go get -u githu

Dec 29, 2021
Quick and simple parser for PFSense XML configuration files, good for auditing firewall rules

pfcfg-parser version 0.0.1 : 13 January 2022 A quick and simple parser for PFSense XML configuration files to generate a plain text file of the main c

Jan 13, 2022
Interpreted Programming Language built in Go. Lexer, Parser, AST, VM.

Gago | Programming Language Built in Go if you are looking for the docs, go here Gago is a interpreted programming language. It is fully written in Go

May 6, 2022