Home

Awesome

go-nmea

CI Go Report Card Coverage Status GoDoc

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

Features

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

Sentence with link is supported by this library. NMEA0183 sentences list is based on IEC 61162-1:2016 (Edition 5.0 2016-08) table of contents.

SentenceDescriptionReferences
AAMWaypoint arrival alarmgpsd
ABKAIS addressed and binary broadcast acknowledgement
ABMAIS addressed binary and safety related message
ACAAIS channel assignment message
ACKAcknowledge alarm
ACNAlert command
ACSAIS channel management information source
AIRAIS interrogation request
AKDAcknowledge detail alarm condition
ALAReport detailed alarm condition
ALCCyclic alert list
ALFAlert sentence
ALRSet alarm state
APBHeading/track controller (autopilot) sentence Bgpsd
ARCAlert command refused
BBMAIS broadcast binary message
BECBearing and distance to waypoint, Dead reckoning1
BODBearing origin to destinationgpsd
BWCBearing and distance to waypoint, Great circlegpsd
BWRBearing and distance to waypoint, Rhumb linegpsd
BWWBearing waypoint to waypointgpsd
CURWater current layer, Multi-layer water current data
DBKDepth Below Keel (obsolete, use DPT instead)gpsd
DBSDepth below transducergpsd
DBTDepth below transducergpsd
DDCDisplay dimming control
DORDoor status detection
DPTDepthgpsd
DSCDigital selective calling information
DSEExpanded digital selective calling
DTMDatum referencegpsd
EPVCommand or report equipment property value
ETLEngine telegraph operation status
EVEGeneral event message
FIRFire detection
FSIFrequency set information
GBSGNSS satellite fault detection
GENGeneric binary information
GFAGNSS fix accuracy and integrity
GGAGlobal positioning system (GPS) fix data1
GLLGeographic position, Latitude/longitude1
GNSGNSS fix datagpsd
GRSGNSS range residuals
GSAGNSS DOP and active satellites1
GSTGNSS pseudorange noise statistics
GSVGNSS satellites in view1
HBTHeartbeat supervision sentence
HCRHeading correction report
HDGHeading, deviation and variationgpsd
HDMHeading - Magneticgpsd
HDTHeading truegpsd
HMRHeading monitor receive
HMSHeading monitor set
HRMheel angle, roll period and roll amplitude measurement device
HSCHeading steering commandgpsd
HSSHull stress surveillance systems
HTCHeading/track control command
HTDHeading /track control data
LR1AIS long-range reply sentence 1
LR2AIS long-range reply sentence 2
LR3AIS long-range reply sentence 3
LRFAIS long-range function
LRIAIS long-range interrogation
MDAMeteorological Compositegpsd
MTAAir Temperature (obsolete, use XDR instead)
MOBMan over board notification
MSKMSK receiver interface
MSSMSK receiver signal status
MTWWater temperaturegpsd
MWDWind direction and speed1
MWVWind speed and anglegpsd
NAKNegative acknowledgement
NRMNAVTEX receiver mask
NRXNAVTEX received message
NSRNavigation status report
OSDOwn ship datagpsd
POSDevice position and ship dimensions report or configuration command
PRCPropulsion remote control status
RLMReturn link message
RMARecommended minimum specific LORAN-C data
RMBRecommended minimum navigation informationgpsd
RMCRecommended minimum specific GNSS data1
RORRudder order status
ROTRate of turngpsd
RRTReport route transfer
RPMRevolutionsgpsd
RSARudder sensor anglegpsd
RSDRadar system datagpsd
RTERoutes1
SFIScanning frequency information
SMISafetyNET Message, All Ships/NavArea
SM2SafetyNET Message, Coastal Warning Area
SM3SafetyNET Message, Circular Area address
SM4SafetyNET Message, Rectangular Area Address
SMBIMO SafetyNET Message Body
SPWSecurity password sentence
SSDAIS ship static data
STNMultiple data ID
THSTrue heading and status1
TLBTarget label
TLLTarget latitude and longitudegpsd
TRCThruster control data
TRLAIS transmitter-non-functioning log
TRDThruster response data
TTDTracked target data
TTMTracked target messagegpsd
TUTTransmission of multi-language text
TXTText transmissionNMEA
UIDUser identification code transmission
VBWDual ground/water speedgpsd
VDMAIS VHF data-link messagegpsd
VDOAIS VHF data-link own-vessel reportgpsd
VDRSet and driftgpsd
VERVersion
VHWWater speed and heading1
VLWDual ground/water distancegpsd
VPWSpeed measured parallel to windgpsd
VSDAIS voyage static data
VTGCourse over ground and ground speed1
VWRRelative Wind Speed and Anglegpsd
VWTTrue Wind Speed and Angle
WATWater level detection
WCVWaypoint closure velocity
WNCDistance waypoint to waypoint
WPLWaypoint location1
XDRTransducer measurementsgpsd
XTECross-track error, measured
XTRCross-track error, dead reckoning
ZDATime and date1
ZDLTime and distance to variable point
ZFOUTC and time from origin waypoint
ZTGUTC and time to destination waypoint
Proprietary sentence typeDescriptionReferences
PNGTransfer NMEA2000 frame as NMEA0183 sentence (ShipModul MiniPlex-3)1
PCDINTransfer NMEA2000 frame as NMEA0183 sentence (SeaSmart.Net Protocol)1
PGRMEEstimated Position Error (Garmin proprietary sentence)1
PHTROVessel pitch and roll (Xsens IMU/VRU/AHRS)
PMTK001Acknowledgement of previously sent command/packet1
PRDIDVessel pitch, roll and heading (Xsens IMU/VRU/AHRS)
PSKPDPTDepth of Water for multiple transducer installation
PSONCMSQuaternion, acceleration, rate of turn, magnetic field, sensor temperature (Xsens IMU/VRU/AHRS)

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

Customize sentence parser

Parser logic can be customized by creating nmea.SentenceParser instance and by providing callback implementations.

p := nmea.SentenceParser{
    CustomParsers: nil,
    ParsePrefix:   nil,
    CheckCRC:      nil,
    OnTagBlock:    nil,
}
s, err := p.Parse("$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70")

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)
	}

	switch m := s.(type) {
	case XYZType:
		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)
	default:
		panic("Could not parse XYZ sentence")
	}
}

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

Message parsing with optional values

Some messages have optional fields. By default, omitted numeric values are set to 0. In situations where you need finer control to distinguish between an undefined value and an actual 0, you can register types overriding existing sentences, using nmea.Int64 and nmea.Float64 instead of int64 and float64. The matching parsing methods are (*Parser).NullInt64 and (*Parser).NullFloat64. Both nmea.Int64 and nmea.Float64 contains a numeric field Value which is defined only if the field Valid is true.

See below example for a modified VTG sentence parser:

package main

import (
	"fmt"

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

// VTG represents track & speed data.
// http://aprs.gids.nl/nmea/#vtg
type VTG struct {
	nmea.BaseSentence
	TrueTrack        nmea.Float64
	MagneticTrack    nmea.Float64
	GroundSpeedKnots nmea.Float64
	GroundSpeedKPH   nmea.Float64
}

func main() {
	nmea.MustRegisterParser("VTG", func(s nmea.BaseSentence) (nmea.Sentence, error) {
		p := nmea.NewParser(s)
		return VTG{
			BaseSentence:     s,
			TrueTrack:        p.NullFloat64(0, "true track"),
			MagneticTrack:    p.NullFloat64(2, "magnetic track"),
			GroundSpeedKnots: p.NullFloat64(4, "ground speed (knots)"),
			GroundSpeedKPH:   p.NullFloat64(6, "ground speed (km/h)"),
		}, p.Err()
	})

	sentence := "$GPVTG,140.88,T,,M,8.04,N,14.89,K,D*05"
	s, err := nmea.Parse(sentence)
	if err != nil {
		panic(err)
	}

	m, ok := s.(VTG)
	if !ok {
		panic("Could not parse VTG sentence")
	}
	fmt.Printf("Raw sentence: %v\n", m)
	fmt.Printf("TrueTrack: %v\n", m.TrueTrack)
	fmt.Printf("MagneticTrack: %v\n", m.MagneticTrack)
	fmt.Printf("GroundSpeedKnots: %v\n", m.GroundSpeedKnots)
	fmt.Printf("GroundSpeedKPH: %v\n", m.GroundSpeedKPH)
}

Output:

$ go run main/main.go

Raw sentence: $GPVTG,140.88,T,,M,8.04,N,14.89,K,D*05
TrueTrack: {140.88 true}
MagneticTrack: {0 false}
GroundSpeedKnots: {8.04 true}
GroundSpeedKPH: {14.89 true}

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.