zimwip / go-nmea

A NMEA parser library in pure Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

go-nmea

CI 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
AAM Waypoint Arrival Alarm
ALA System Faults and Alarms
APB Autopilot Sentence "B"
BEC Bearing and distance to waypoint (dead reckoning)
BOD Bearing waypoint to waypoint (origin to destination)
BWC Bearing and distance to waypoint (great circle)
BWR Bearing and distance to waypoint (Rhumb Line)
BWW Bearing from destination waypoint to origin waypoint
DBK Depth Below Keel (obsolete, use DPT instead)
DBS Depth Below Surface (obsolete, use DPT instead)
DBT Depth below transducer
DOR Door Status Detection
DPT Depth of Water
DSC Digital Selective Calling Information
DSE Expanded digital selective calling
DTM Datum Reference
EVE General Event Message
FIR Fire Detection event with time and location
GGA GPS Positioning System Fix Data
GLL Geographic Position, Latitude / Longitude and time
GNS Combined GPS fix for GPS, Glonass, Galileo, and BeiDou
GSA GPS DOP and active satellites
GSV GPS Satellites in view
HDG Heading, Deviation & Variation
HDM Heading - Magnetic
HDT Actual vessel heading in degrees True
HSC Heading steering command
MDA Meteorological Composite
MTA Air Temperature (obsolete, use XDR instead)
MTW Mean Temperature of Water
MWD Wind Direction and Speed
MWV Wind Speed and Angle
OSD Own Ship Data
RMB Recommended Minimum Navigation Information
RMC Recommended Minimum Specific GPS/Transit data
ROT Rate of turn
RPM Engine or Shaft revolutions and pitch
RSA Rudder Sensor Angle
RSD RADAR System Data
RTE Route
THS Actual vessel heading in degrees True and status
TLL Target latitude and longitude
TTM Tracked Target Message
TXT Sentence is for the transmission of text messages
VBW Dual Ground/Water Speed
VDM/VDO Encapsulated binary payload (commonly used with AIS data)
VDR Set and Drift
VHW Water Speed and Heading
VLW Distance Traveled through Water
VPW Speed Measured Parallel to Wind
VTG Track Made Good and Ground Speed
VWR Relative Wind Speed and Angle
VWT True Wind Speed and Angle
WPL Waypoint location
XDR Transducer Measurement
ZDA Date & time data
Proprietary sentence type Description
PGRME Estimated Position Error (Garmin proprietary sentence)
PHTRO Vessel pitch and roll (Xsens IMU/VRU/AHRS)
PMTK Messages for setting and reading commands for MediaTek gps modules.
PRDID Vessel pitch, roll and heading (Xsens IMU/VRU/AHRS)
PSKPDPT Depth of Water for multiple transducer installation
PSONCMS Quaternion, 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

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.

About

A NMEA parser library in pure Go

License:MIT License


Languages

Language:Go 99.8%Language:Makefile 0.2%