misc: updated go-nmea dependency and refactored code for v1.1.0

This commit is contained in:
evilsocket 2018-07-31 16:19:59 +02:00
parent f8ede4ddbe
commit 3d1936ef61
No known key found for this signature in database
GPG key ID: 1564D7F30393A456
22 changed files with 262 additions and 205 deletions

5
Gopkg.lock generated
View file

@ -2,11 +2,12 @@
[[projects]] [[projects]]
digest = "1:fa526d5f6ec66a1833c687768639251d6db3bc3b7f32abd0265ae9625a9233de" digest = "1:4132a4623657c2ba93a7cf83dccc6869b3e3bb91dc2afefa7c7032e10ceeaa12"
name = "github.com/adrianmo/go-nmea" name = "github.com/adrianmo/go-nmea"
packages = ["."] packages = ["."]
pruneopts = "UT" pruneopts = "UT"
revision = "22095aa1b48050243d3eb9a001ca80eb91a0c6fa" revision = "a32116e4989e2b0e17c057ee378b4d5246add74e"
version = "v1.1.0"
[[projects]] [[projects]]
branch = "master" branch = "master"

View file

@ -93,6 +93,10 @@
branch = "master" branch = "master"
name = "github.com/tarm/serial" name = "github.com/tarm/serial"
[[constraint]]
name = "github.com/adrianmo/go-nmea"
version = "1.1.0"
[prune] [prune]
go-tests = true go-tests = true
unused-packages = true unused-packages = true

View file

@ -129,13 +129,10 @@ func (gps *GPS) Start() error {
for gps.Running() { for gps.Running() {
if line, err := gps.readLine(); err == nil { if line, err := gps.readLine(); err == nil {
if info, err := nmea.Parse(line); err == nil { if s, err := nmea.Parse(line); err == nil {
s := info.Sentence()
// http://aprs.gids.nl/nmea/#gga // http://aprs.gids.nl/nmea/#gga
if s.Type == "GNGGA" { if m, ok := s.(nmea.GNGGA); ok {
gps.Session.GPS = info.(nmea.GNGGA) gps.Session.GPS = m
} else {
log.Debug("Skipping message %s: %v", s.Type, s)
} }
} else { } else {
log.Debug("Error parsing line '%s': %s", line, err) log.Debug("Error parsing line '%s': %s", line, err)

View file

@ -5,7 +5,10 @@
language: go language: go
go: go:
- 1.7 - 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- tip - tip
matrix: matrix:

View file

@ -27,6 +27,7 @@ At this moment, this library supports the following sentence types:
- [GPVTG](http://aprs.gids.nl/nmea/#vtg) - Track Made Good and Ground Speed - [GPVTG](http://aprs.gids.nl/nmea/#vtg) - Track Made Good and Ground Speed
- [GPZDA](http://aprs.gids.nl/nmea/#zda) - Date & time data - [GPZDA](http://aprs.gids.nl/nmea/#zda) - Date & time data
- [PGRME](http://aprs.gids.nl/nmea/#rme) - Estimated Position Error (Garmin proprietary sentence) - [PGRME](http://aprs.gids.nl/nmea/#rme) - Estimated Position Error (Garmin proprietary sentence)
- [GPHDT](http://aprs.gids.nl/nmea/#hdt) - Actual vessel heading in degrees True
## Example ## Example
@ -35,18 +36,50 @@ At this moment, this library supports the following sentence types:
package main package main
import ( import (
"fmt" "fmt"
"github.com/adrianmo/go-nmea" "log"
"github.com/adrianmo/go-nmea"
) )
func main() { func main() {
m, err := nmea.Parse("$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70") sentence := "$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70"
if err == nil { s, err := nmea.Parse(sentence)
fmt.Printf("%+v\n", m) if err != nil {
} log.Fatal(err)
}
m := s.(nmea.GPRMC)
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
```
## Contributions ## Contributions
Please, feel free to implement support for new sentences, fix bugs, refactor code, etc. and send a pull-request to update the library. Please, feel free to implement support for new sentences, fix bugs, refactor code, etc. and send a pull-request to update the library.

1
vendor/github.com/adrianmo/go-nmea/VERSION generated vendored Normal file
View file

@ -0,0 +1 @@
v1.1.0

View file

@ -8,7 +8,7 @@ const (
// GLGSV represents the GPS Satellites in view // GLGSV represents the GPS Satellites in view
// http://aprs.gids.nl/nmea/#glgsv // http://aprs.gids.nl/nmea/#glgsv
type GLGSV struct { type GLGSV struct {
Sent BaseSentence
TotalMessages int64 // Total number of messages of this type in this cycle TotalMessages int64 // Total number of messages of this type in this cycle
MessageNumber int64 // Message number MessageNumber int64 // Message number
NumberSVsInView int64 // Total number of SVs in view NumberSVsInView int64 // Total number of SVs in view
@ -23,11 +23,11 @@ type GLGSVInfo struct {
SNR int64 // SNR, 00-99 dB (null when not tracking) SNR int64 // SNR, 00-99 dB (null when not tracking)
} }
// NewGLGSV constructor // newGLGSV constructor
func NewGLGSV(s Sent) (GLGSV, error) { func newGLGSV(s BaseSentence) (GLGSV, error) {
p := newParser(s, PrefixGLGSV) p := newParser(s, PrefixGLGSV)
m := GLGSV{ m := GLGSV{
Sent: s, BaseSentence: s,
TotalMessages: p.Int64(0, "total number of messages"), TotalMessages: p.Int64(0, "total number of messages"),
MessageNumber: p.Int64(1, "message number"), MessageNumber: p.Int64(1, "message number"),
NumberSVsInView: p.Int64(2, "number of SVs in view"), NumberSVsInView: p.Int64(2, "number of SVs in view"),

View file

@ -7,10 +7,10 @@ const (
// GNGGA is the Time, position, and fix related data of the receiver. // GNGGA is the Time, position, and fix related data of the receiver.
type GNGGA struct { type GNGGA struct {
Sent BaseSentence
Time Time // Time of fix. Time Time // Time of fix.
Latitude LatLong // Latitude. Latitude float64 // Latitude.
Longitude LatLong // Longitude. Longitude float64 // Longitude.
FixQuality string // Quality of fix. FixQuality string // Quality of fix.
NumSatellites int64 // Number of satellites in use. NumSatellites int64 // Number of satellites in use.
HDOP float64 // Horizontal dilution of precision. HDOP float64 // Horizontal dilution of precision.
@ -20,16 +20,16 @@ type GNGGA struct {
DGPSId string // DGPS reference station ID. DGPSId string // DGPS reference station ID.
} }
// NewGNGGA constructor // newGNGGA constructor
func NewGNGGA(s Sent) (GNGGA, error) { func newGNGGA(s BaseSentence) (GNGGA, error) {
p := newParser(s, PrefixGNGGA) p := newParser(s, PrefixGNGGA)
return GNGGA{ return GNGGA{
Sent: s, BaseSentence: s,
Time: p.Time(0, "time"), Time: p.Time(0, "time"),
Latitude: p.LatLong(1, 2, "latitude"), Latitude: p.LatLong(1, 2, "latitude"),
Longitude: p.LatLong(3, 4, "longitude"), Longitude: p.LatLong(3, 4, "longitude"),
FixQuality: p.EnumString(5, "fix quality", Invalid, GPS, DGPS), FixQuality: p.EnumString(5, "fix quality", Invalid, GPS, DGPS, PPS, RTK, FRTK),
NumSatellites: p.Int64(6, "number of satelites"), NumSatellites: p.Int64(6, "number of satellites"),
HDOP: p.Float64(7, "hdop"), HDOP: p.Float64(7, "hdop"),
Altitude: p.Float64(8, "altitude"), Altitude: p.Float64(8, "altitude"),
Separation: p.Float64(10, "separation"), Separation: p.Float64(10, "separation"),

View file

@ -8,30 +8,30 @@ const (
// GNRMC is the Recommended Minimum Specific GNSS data. // GNRMC is the Recommended Minimum Specific GNSS data.
// http://aprs.gids.nl/nmea/#rmc // http://aprs.gids.nl/nmea/#rmc
type GNRMC struct { type GNRMC struct {
Sent BaseSentence
Time Time // Time Stamp Time Time // Time Stamp
Validity string // validity - A-ok, V-invalid Validity string // validity - A-ok, V-invalid
Latitude LatLong // Latitude Latitude float64 // Latitude
Longitude LatLong // Longitude Longitude float64 // Longitude
Speed float64 // Speed in knots Speed float64 // Speed in knots
Course float64 // True course Course float64 // True course
Date Date // Date Date Date // Date
Variation float64 // Magnetic variation Variation float64 // Magnetic variation
} }
// NewGNRMC constructor // newGNRMC constructor
func NewGNRMC(s Sent) (GNRMC, error) { func newGNRMC(s BaseSentence) (GNRMC, error) {
p := newParser(s, PrefixGNRMC) p := newParser(s, PrefixGNRMC)
m := GNRMC{ m := GNRMC{
Sent: s, BaseSentence: s,
Time: p.Time(0, "time"), Time: p.Time(0, "time"),
Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC), Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC),
Latitude: p.LatLong(2, 3, "latitude"), Latitude: p.LatLong(2, 3, "latitude"),
Longitude: p.LatLong(4, 5, "longitude"), Longitude: p.LatLong(4, 5, "longitude"),
Speed: p.Float64(6, "speed"), Speed: p.Float64(6, "speed"),
Course: p.Float64(7, "course"), Course: p.Float64(7, "course"),
Date: p.Date(8, "date"), Date: p.Date(8, "date"),
Variation: p.Float64(9, "variation"), Variation: p.Float64(9, "variation"),
} }
if p.EnumString(10, "direction", West, East) == West { if p.EnumString(10, "direction", West, East) == West {
m.Variation = 0 - m.Variation m.Variation = 0 - m.Variation

3
vendor/github.com/adrianmo/go-nmea/go.mod generated vendored Normal file
View file

@ -0,0 +1,3 @@
module github.com/adrianmo/go-nmea
require github.com/stretchr/testify v1.2.1

View file

@ -9,15 +9,21 @@ const (
GPS = "1" GPS = "1"
// DGPS fix quality // DGPS fix quality
DGPS = "2" DGPS = "2"
// PPS fix
PPS = "3"
// RTK real time kinematic fix
RTK = "4"
// FRTK float RTK fix
FRTK = "5"
) )
// GPGGA represents fix data. // GPGGA represents fix data.
// http://aprs.gids.nl/nmea/#gga // http://aprs.gids.nl/nmea/#gga
type GPGGA struct { type GPGGA struct {
Sent BaseSentence
Time Time // Time of fix. Time Time // Time of fix.
Latitude LatLong // Latitude. Latitude float64 // Latitude.
Longitude LatLong // Longitude. Longitude float64 // Longitude.
FixQuality string // Quality of fix. FixQuality string // Quality of fix.
NumSatellites int64 // Number of satellites in use. NumSatellites int64 // Number of satellites in use.
HDOP float64 // Horizontal dilution of precision. HDOP float64 // Horizontal dilution of precision.
@ -27,17 +33,17 @@ type GPGGA struct {
DGPSId string // DGPS reference station ID. DGPSId string // DGPS reference station ID.
} }
// NewGPGGA parses the GPGGA sentence into this struct. // newGPGGA parses the GPGGA sentence into this struct.
// e.g: $GPGGA,034225.077,3356.4650,S,15124.5567,E,1,03,9.7,-25.0,M,21.0,M,,0000*58 // e.g: $GPGGA,034225.077,3356.4650,S,15124.5567,E,1,03,9.7,-25.0,M,21.0,M,,0000*58
func NewGPGGA(s Sent) (GPGGA, error) { func newGPGGA(s BaseSentence) (GPGGA, error) {
p := newParser(s, PrefixGPGGA) p := newParser(s, PrefixGPGGA)
return GPGGA{ return GPGGA{
Sent: s, BaseSentence: s,
Time: p.Time(0, "time"), Time: p.Time(0, "time"),
Latitude: p.LatLong(1, 2, "latitude"), Latitude: p.LatLong(1, 2, "latitude"),
Longitude: p.LatLong(3, 4, "longitude"), Longitude: p.LatLong(3, 4, "longitude"),
FixQuality: p.EnumString(5, "fix quality", Invalid, GPS, DGPS), FixQuality: p.EnumString(5, "fix quality", Invalid, GPS, DGPS, PPS, RTK, FRTK),
NumSatellites: p.Int64(6, "number of satelites"), NumSatellites: p.Int64(6, "number of satellites"),
HDOP: p.Float64(7, "hdap"), HDOP: p.Float64(7, "hdap"),
Altitude: p.Float64(8, "altitude"), Altitude: p.Float64(8, "altitude"),
Separation: p.Float64(10, "separation"), Separation: p.Float64(10, "separation"),

View file

@ -12,21 +12,21 @@ const (
// GPGLL is Geographic Position, Latitude / Longitude and time. // GPGLL is Geographic Position, Latitude / Longitude and time.
// http://aprs.gids.nl/nmea/#gll // http://aprs.gids.nl/nmea/#gll
type GPGLL struct { type GPGLL struct {
Sent BaseSentence
Latitude LatLong // Latitude Latitude float64 // Latitude
Longitude LatLong // Longitude Longitude float64 // Longitude
Time Time // Time Stamp Time Time // Time Stamp
Validity string // validity - A-valid Validity string // validity - A-valid
} }
// NewGPGLL constructor // newGPGLL constructor
func NewGPGLL(s Sent) (GPGLL, error) { func newGPGLL(s BaseSentence) (GPGLL, error) {
p := newParser(s, PrefixGPGLL) p := newParser(s, PrefixGPGLL)
return GPGLL{ return GPGLL{
Sent: s, BaseSentence: s,
Latitude: p.LatLong(0, 1, "latitude"), Latitude: p.LatLong(0, 1, "latitude"),
Longitude: p.LatLong(2, 3, "longitude"), Longitude: p.LatLong(2, 3, "longitude"),
Time: p.Time(4, "time"), Time: p.Time(4, "time"),
Validity: p.EnumString(5, "validity", ValidGLL, InvalidGLL), Validity: p.EnumString(5, "validity", ValidGLL, InvalidGLL),
}, p.Err() }, p.Err()
} }

View file

@ -18,7 +18,7 @@ const (
// GPGSA represents overview satellite data. // GPGSA represents overview satellite data.
// http://aprs.gids.nl/nmea/#gsa // http://aprs.gids.nl/nmea/#gsa
type GPGSA struct { type GPGSA struct {
Sent BaseSentence
Mode string // The selection mode. Mode string // The selection mode.
FixType string // The fix type. FixType string // The fix type.
SV []string // List of satellite PRNs used for this fix. SV []string // List of satellite PRNs used for this fix.
@ -27,17 +27,17 @@ type GPGSA struct {
VDOP float64 // Vertical dilution of precision. VDOP float64 // Vertical dilution of precision.
} }
// NewGPGSA parses the GPGSA sentence into this struct. // newGPGSA parses the GPGSA sentence into this struct.
func NewGPGSA(s Sent) (GPGSA, error) { func newGPGSA(s BaseSentence) (GPGSA, error) {
p := newParser(s, PrefixGPGSA) p := newParser(s, PrefixGPGSA)
m := GPGSA{ m := GPGSA{
Sent: s, BaseSentence: s,
Mode: p.EnumString(0, "selection mode", Auto, Manual), Mode: p.EnumString(0, "selection mode", Auto, Manual),
FixType: p.EnumString(1, "fix type", FixNone, Fix2D, Fix3D), FixType: p.EnumString(1, "fix type", FixNone, Fix2D, Fix3D),
} }
// Satellites in view. // Satellites in view.
for i := 2; i < 14; i++ { for i := 2; i < 14; i++ {
if v := p.String(i, "satelite in view"); v != "" { if v := p.String(i, "satellite in view"); v != "" {
m.SV = append(m.SV, v) m.SV = append(m.SV, v)
} }
} }

View file

@ -8,7 +8,7 @@ const (
// GPGSV represents the GPS Satellites in view // GPGSV represents the GPS Satellites in view
// http://aprs.gids.nl/nmea/#gpgsv // http://aprs.gids.nl/nmea/#gpgsv
type GPGSV struct { type GPGSV struct {
Sent BaseSentence
TotalMessages int64 // Total number of messages of this type in this cycle TotalMessages int64 // Total number of messages of this type in this cycle
MessageNumber int64 // Message number MessageNumber int64 // Message number
NumberSVsInView int64 // Total number of SVs in view NumberSVsInView int64 // Total number of SVs in view
@ -23,11 +23,11 @@ type GPGSVInfo struct {
SNR int64 // SNR, 00-99 dB (null when not tracking) SNR int64 // SNR, 00-99 dB (null when not tracking)
} }
// NewGPGSV constructor // newGPGSV constructor
func NewGPGSV(s Sent) (GPGSV, error) { func newGPGSV(s BaseSentence) (GPGSV, error) {
p := newParser(s, PrefixGPGSV) p := newParser(s, PrefixGPGSV)
m := GPGSV{ m := GPGSV{
Sent: s, BaseSentence: s,
TotalMessages: p.Int64(0, "total number of messages"), TotalMessages: p.Int64(0, "total number of messages"),
MessageNumber: p.Int64(1, "message number"), MessageNumber: p.Int64(1, "message number"),
NumberSVsInView: p.Int64(2, "number of SVs in view"), NumberSVsInView: p.Int64(2, "number of SVs in view"),

25
vendor/github.com/adrianmo/go-nmea/gphdt.go generated vendored Normal file
View file

@ -0,0 +1,25 @@
package nmea
const (
// PrefixGPHDT prefix of GPHDT sentence type
PrefixGPHDT = "GPHDT"
)
// GPHDT is the Actual vessel heading in degrees True.
// http://aprs.gids.nl/nmea/#hdt
type GPHDT struct {
BaseSentence
Heading float64 // Heading in degrees
True bool // Heading is relative to true north
}
// newGPHDT constructor
func newGPHDT(s BaseSentence) (GPHDT, error) {
p := newParser(s, PrefixGPHDT)
m := GPHDT{
BaseSentence: s,
Heading: p.Float64(0, "heading"),
True: p.EnumString(1, "true", "T") == "T",
}
return m, p.Err()
}

View file

@ -12,30 +12,30 @@ const (
// GPRMC is the Recommended Minimum Specific GNSS data. // GPRMC is the Recommended Minimum Specific GNSS data.
// http://aprs.gids.nl/nmea/#rmc // http://aprs.gids.nl/nmea/#rmc
type GPRMC struct { type GPRMC struct {
Sent BaseSentence
Time Time // Time Stamp Time Time // Time Stamp
Validity string // validity - A-ok, V-invalid Validity string // validity - A-ok, V-invalid
Latitude LatLong // Latitude Latitude float64 // Latitude
Longitude LatLong // Longitude Longitude float64 // Longitude
Speed float64 // Speed in knots Speed float64 // Speed in knots
Course float64 // True course Course float64 // True course
Date Date // Date Date Date // Date
Variation float64 // Magnetic variation Variation float64 // Magnetic variation
} }
// NewGPRMC constructor // newGPRMC constructor
func NewGPRMC(s Sent) (GPRMC, error) { func newGPRMC(s BaseSentence) (GPRMC, error) {
p := newParser(s, PrefixGPRMC) p := newParser(s, PrefixGPRMC)
m := GPRMC{ m := GPRMC{
Sent: s, BaseSentence: s,
Time: p.Time(0, "time"), Time: p.Time(0, "time"),
Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC), Validity: p.EnumString(1, "validity", ValidRMC, InvalidRMC),
Latitude: p.LatLong(2, 3, "latitude"), Latitude: p.LatLong(2, 3, "latitude"),
Longitude: p.LatLong(4, 5, "longitude"), Longitude: p.LatLong(4, 5, "longitude"),
Speed: p.Float64(6, "speed"), Speed: p.Float64(6, "speed"),
Course: p.Float64(7, "course"), Course: p.Float64(7, "course"),
Date: p.Date(8, "date"), Date: p.Date(8, "date"),
Variation: p.Float64(9, "variation"), Variation: p.Float64(9, "variation"),
} }
if p.EnumString(10, "variation", West, East) == West { if p.EnumString(10, "variation", West, East) == West {
m.Variation = 0 - m.Variation m.Variation = 0 - m.Variation

View file

@ -8,19 +8,19 @@ const (
// GPVTG represents track & speed data. // GPVTG represents track & speed data.
// http://aprs.gids.nl/nmea/#vtg // http://aprs.gids.nl/nmea/#vtg
type GPVTG struct { type GPVTG struct {
Sent BaseSentence
TrueTrack float64 TrueTrack float64
MagneticTrack float64 MagneticTrack float64
GroundSpeedKnots float64 GroundSpeedKnots float64
GroundSpeedKPH float64 GroundSpeedKPH float64
} }
// NewGPVTG parses the GPVTG sentence into this struct. // newGPVTG parses the GPVTG sentence into this struct.
// e.g: $GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43 // e.g: $GPVTG,360.0,T,348.7,M,000.0,N,000.0,K*43
func NewGPVTG(s Sent) (GPVTG, error) { func newGPVTG(s BaseSentence) (GPVTG, error) {
p := newParser(s, PrefixGPVTG) p := newParser(s, PrefixGPVTG)
return GPVTG{ return GPVTG{
Sent: s, BaseSentence: s,
TrueTrack: p.Float64(0, "true track"), TrueTrack: p.Float64(0, "true track"),
MagneticTrack: p.Float64(2, "magnetic track"), MagneticTrack: p.Float64(2, "magnetic track"),
GroundSpeedKnots: p.Float64(4, "ground speed (knots)"), GroundSpeedKnots: p.Float64(4, "ground speed (knots)"),

View file

@ -8,7 +8,7 @@ const (
// GPZDA represents date & time data. // GPZDA represents date & time data.
// http://aprs.gids.nl/nmea/#zda // http://aprs.gids.nl/nmea/#zda
type GPZDA struct { type GPZDA struct {
Sent BaseSentence
Time Time Time Time
Day int64 Day int64
Month int64 Month int64
@ -17,11 +17,11 @@ type GPZDA struct {
OffsetMinutes int64 // Local time zone offset from GMT, minutes OffsetMinutes int64 // Local time zone offset from GMT, minutes
} }
// NewGPZDA constructor // newGPZDA constructor
func NewGPZDA(s Sent) (GPZDA, error) { func newGPZDA(s BaseSentence) (GPZDA, error) {
p := newParser(s, PrefixGPZDA) p := newParser(s, PrefixGPZDA)
return GPZDA{ return GPZDA{
Sent: s, BaseSentence: s,
Time: p.Time(0, "time"), Time: p.Time(0, "time"),
Day: p.Int64(1, "day"), Day: p.Int64(1, "day"),
Month: p.Int64(2, "month"), Month: p.Int64(2, "month"),

View file

@ -8,21 +8,21 @@ import (
// parser provides a simple way of accessing and parsing // parser provides a simple way of accessing and parsing
// sentence fields // sentence fields
type parser struct { type parser struct {
Sent BaseSentence
prefix string prefix string
err error err error
} }
// newParser constructor // newParser constructor
func newParser(s Sent, prefix string) *parser { func newParser(s BaseSentence, prefix string) *parser {
p := &parser{Sent: s, prefix: prefix} p := &parser{BaseSentence: s, prefix: prefix}
if p.Type != prefix { if p.Type != prefix {
p.SetErr("prefix", p.Type) p.SetErr("prefix", p.Type)
} }
return p return p
} }
// Err returns the first error encounterd during the parser's usage. // Err returns the first error encountered during the parser's usage.
func (p *parser) Err() error { func (p *parser) Err() error {
return p.err return p.err
} }
@ -48,10 +48,10 @@ func (p *parser) String(i int, context string) string {
} }
// EnumString returns the field value at the specified index. // EnumString returns the field value at the specified index.
// An error occurs if the value is not one of the options. // An error occurs if the value is not one of the options and not empty.
func (p *parser) EnumString(i int, context string, options ...string) string { func (p *parser) EnumString(i int, context string, options ...string) string {
s := p.String(i, context) s := p.String(i, context)
if p.err != nil { if p.err != nil || s == "" {
return "" return ""
} }
for _, o := range options { for _, o := range options {
@ -64,7 +64,7 @@ func (p *parser) EnumString(i int, context string, options ...string) string {
} }
// Int64 returns the int64 value at the specified index. // Int64 returns the int64 value at the specified index.
// If the value is an emtpy string, 0 is returned. // If the value is an empty string, 0 is returned.
func (p *parser) Int64(i int, context string) int64 { func (p *parser) Int64(i int, context string) int64 {
s := p.String(i, context) s := p.String(i, context)
if p.err != nil { if p.err != nil {
@ -126,7 +126,7 @@ func (p *parser) Date(i int, context string) Date {
} }
// LatLong returns the coordinate value of the specified fields. // LatLong returns the coordinate value of the specified fields.
func (p *parser) LatLong(i, j int, context string) LatLong { func (p *parser) LatLong(i, j int, context string) float64 {
a := p.String(i, context) a := p.String(i, context)
b := p.String(j, context) b := p.String(j, context)
if p.err != nil { if p.err != nil {

View file

@ -10,14 +10,14 @@ const (
// PGRME is Estimated Position Error (Garmin proprietary sentence) // PGRME is Estimated Position Error (Garmin proprietary sentence)
// http://aprs.gids.nl/nmea/#rme // http://aprs.gids.nl/nmea/#rme
type PGRME struct { type PGRME struct {
Sent BaseSentence
Horizontal float64 // Estimated horizontal position error (HPE) in metres Horizontal float64 // Estimated horizontal position error (HPE) in metres
Vertical float64 // Estimated vertical position error (VPE) in metres Vertical float64 // Estimated vertical position error (VPE) in metres
Spherical float64 // Overall spherical equivalent position error in meters Spherical float64 // Overall spherical equivalent position error in meters
} }
// NewPGRME constructor // newPGRME constructor
func NewPGRME(s Sent) (PGRME, error) { func newPGRME(s BaseSentence) (PGRME, error) {
p := newParser(s, PrefixPGRME) p := newParser(s, PrefixPGRME)
horizontal := p.Float64(0, "horizontal error") horizontal := p.Float64(0, "horizontal error")
@ -30,9 +30,9 @@ func NewPGRME(s Sent) (PGRME, error) {
_ = p.EnumString(5, "spherical error unit", ErrorUnit) _ = p.EnumString(5, "spherical error unit", ErrorUnit)
return PGRME{ return PGRME{
Sent: s, BaseSentence: s,
Horizontal: horizontal, Horizontal: horizontal,
Vertical: vertial, Vertical: vertial,
Spherical: spherical, Spherical: spherical,
}, p.Err() }, p.Err()
} }

View file

@ -16,43 +16,35 @@ const (
ChecksumSep = "*" ChecksumSep = "*"
) )
// Message interface for all NMEA sentence // Sentence interface for all NMEA sentence
type Message interface { type Sentence interface {
fmt.Stringer fmt.Stringer
Sentence() Sent
Prefix() string Prefix() string
Validate() error
} }
// Sent contains the information about the NMEA sentence // BaseSentence contains the information about the NMEA sentence
type Sent struct { type BaseSentence struct {
Type string // The sentence type (e.g $GPGSA) Type string // The sentence type (e.g $GPGSA)
Fields []string // Array of fields Fields []string // Array of fields
Checksum string // The Checksum Checksum string // The Checksum
Raw string // The raw NMEA sentence received Raw string // The raw NMEA sentence received
} }
// Sentence returns the Messages Sent
func (s Sent) Sentence() Sent { return s }
// Prefix returns the type of the message // Prefix returns the type of the message
func (s Sent) Prefix() string { return s.Type } func (s BaseSentence) Prefix() string { return s.Type }
// String formats the sentence into a string // String formats the sentence into a string
func (s Sent) String() string { return s.Raw } func (s BaseSentence) String() string { return s.Raw }
// Validate returns an error if the sentence is not valid // parseSentence parses a raw message into it's fields
func (s Sent) Validate() error { return nil } func parseSentence(raw string) (BaseSentence, error) {
// ParseSentence parses a raw message into it's fields
func ParseSentence(raw string) (Sent, error) {
startIndex := strings.Index(raw, SentenceStart) startIndex := strings.Index(raw, SentenceStart)
if startIndex != 0 { if startIndex != 0 {
return Sent{}, fmt.Errorf("nmea: sentence does not start with a '$'") return BaseSentence{}, fmt.Errorf("nmea: sentence does not start with a '$'")
} }
sumSepIndex := strings.Index(raw, ChecksumSep) sumSepIndex := strings.Index(raw, ChecksumSep)
if sumSepIndex == -1 { if sumSepIndex == -1 {
return Sent{}, fmt.Errorf("nmea: sentence does not contain checksum separator") return BaseSentence{}, fmt.Errorf("nmea: sentence does not contain checksum separator")
} }
var ( var (
fieldsRaw = raw[startIndex+1 : sumSepIndex] fieldsRaw = raw[startIndex+1 : sumSepIndex]
@ -62,10 +54,10 @@ func ParseSentence(raw string) (Sent, error) {
) )
// Validate the checksum // Validate the checksum
if checksum != checksumRaw { if checksum != checksumRaw {
return Sent{}, fmt.Errorf( return BaseSentence{}, fmt.Errorf(
"nmea: sentence checksum mismatch [%s != %s]", checksum, checksumRaw) "nmea: sentence checksum mismatch [%s != %s]", checksum, checksumRaw)
} }
return Sent{ return BaseSentence{
Type: fields[0], Type: fields[0],
Fields: fields[1:], Fields: fields[1:],
Checksum: checksumRaw, Checksum: checksumRaw,
@ -84,34 +76,36 @@ func xorChecksum(s string) string {
} }
// Parse parses the given string into the correct sentence type. // Parse parses the given string into the correct sentence type.
func Parse(raw string) (Message, error) { func Parse(raw string) (Sentence, error) {
s, err := ParseSentence(raw) s, err := parseSentence(raw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch s.Type { switch s.Type {
case PrefixGPRMC: case PrefixGPRMC:
return NewGPRMC(s) return newGPRMC(s)
case PrefixGNRMC: case PrefixGNRMC:
return NewGNRMC(s) return newGNRMC(s)
case PrefixGPGGA: case PrefixGPGGA:
return NewGPGGA(s) return newGPGGA(s)
case PrefixGNGGA: case PrefixGNGGA:
return NewGNGGA(s) return newGNGGA(s)
case PrefixGPGSA: case PrefixGPGSA:
return NewGPGSA(s) return newGPGSA(s)
case PrefixGPGLL: case PrefixGPGLL:
return NewGPGLL(s) return newGPGLL(s)
case PrefixGPVTG: case PrefixGPVTG:
return NewGPVTG(s) return newGPVTG(s)
case PrefixGPZDA: case PrefixGPZDA:
return NewGPZDA(s) return newGPZDA(s)
case PrefixPGRME: case PrefixPGRME:
return NewPGRME(s) return newPGRME(s)
case PrefixGPGSV: case PrefixGPGSV:
return NewGPGSV(s) return newGPGSV(s)
case PrefixGLGSV: case PrefixGLGSV:
return NewGLGSV(s) return newGLGSV(s)
case PrefixGPHDT:
return newGPHDT(s)
default: default:
return nil, fmt.Errorf("nmea: sentence type '%s' not implemented", s.Type) return nil, fmt.Errorf("nmea: sentence type '%s' not implemented", s.Type)
} }

View file

@ -9,7 +9,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"unicode" "unicode"
// "unicode/utf8"
) )
const ( const (
@ -31,42 +30,6 @@ const (
West = "W" West = "W"
) )
// LatLong type
type LatLong float64
// PrintGPS returns the GPS format for the given LatLong.
func (l LatLong) PrintGPS() string {
padding := ""
value := float64(l)
degrees := math.Floor(math.Abs(value))
fraction := (math.Abs(value) - degrees) * 60
if fraction < 10 {
padding = "0"
}
return fmt.Sprintf("%d%s%.4f", int(degrees), padding, fraction)
}
// PrintDMS returns the degrees, minutes, seconds format for the given LatLong.
func (l LatLong) PrintDMS() string {
val := math.Abs(float64(l))
degrees := int(math.Floor(val))
minutes := int(math.Floor(60 * (val - float64(degrees))))
seconds := 3600 * (val - float64(degrees) - (float64(minutes) / 60))
return fmt.Sprintf("%d\u00B0 %d' %f\"", degrees, minutes, seconds)
}
//ValidRange validates if the range is between -180 and +180.
func (l LatLong) ValidRange() bool {
return -180.0 <= l && l <= 180.0
}
// IsNear returns whether the coordinate is near the other coordinate,
// by no further than the given distance away.
func (l LatLong) IsNear(o LatLong, max float64) bool {
return math.Abs(float64(l-o)) <= max
}
// ParseLatLong parses the supplied string into the LatLong. // ParseLatLong parses the supplied string into the LatLong.
// //
// Supported formats are: // Supported formats are:
@ -74,27 +37,30 @@ func (l LatLong) IsNear(o LatLong, max float64) bool {
// - Decimal (e.g. 33.23454) // - Decimal (e.g. 33.23454)
// - GPS (e.g 15113.4322S) // - GPS (e.g 15113.4322S)
// //
func ParseLatLong(s string) (LatLong, error) { func ParseLatLong(s string) (float64, error) {
var l LatLong var l float64
var err error if v, err := ParseDMS(s); err == nil {
invalid := LatLong(0.0) // The invalid value to return. l = v
if l, err = ParseDMS(s); err == nil { } else if v, err := ParseGPS(s); err == nil {
return l, nil l = v
} else if l, err = ParseGPS(s); err == nil { } else if v, err := ParseDecimal(s); err == nil {
return l, nil l = v
} else if l, err = ParseDecimal(s); err == nil { } else {
return l, nil return 0, fmt.Errorf("cannot parse [%s], unknown format", s)
} }
if !l.ValidRange() { if l < -180.0 || 180.0 < l {
return invalid, errors.New("coordinate is not in range -180, 180") return 0, errors.New("coordinate is not in range -180, 180")
} }
return invalid, fmt.Errorf("cannot parse [%s], unknown format", s) return l, nil
} }
// ParseGPS parses a GPS/NMEA coordinate. // ParseGPS parses a GPS/NMEA coordinate.
// e.g 15113.4322S // e.g 15113.4322S
func ParseGPS(s string) (LatLong, error) { func ParseGPS(s string) (float64, error) {
parts := strings.Split(s, " ") parts := strings.Split(s, " ")
if len(parts) != 2 {
return 0, fmt.Errorf("invalid format: %s", s)
}
dir := parts[1] dir := parts[1]
value, err := strconv.ParseFloat(parts[0], 64) value, err := strconv.ParseFloat(parts[0], 64)
if err != nil { if err != nil {
@ -106,28 +72,39 @@ func ParseGPS(s string) (LatLong, error) {
value = degrees + minutes/60 value = degrees + minutes/60
if dir == North || dir == East { if dir == North || dir == East {
return LatLong(value), nil return value, nil
} else if dir == South || dir == West { } else if dir == South || dir == West {
return LatLong(0 - value), nil return 0 - value, nil
} else { } else {
return 0, fmt.Errorf("invalid direction [%s]", dir) return 0, fmt.Errorf("invalid direction [%s]", dir)
} }
} }
// FormatGPS formats a GPS/NMEA coordinate
func FormatGPS(l float64) string {
padding := ""
degrees := math.Floor(math.Abs(l))
fraction := (math.Abs(l) - degrees) * 60
if fraction < 10 {
padding = "0"
}
return fmt.Sprintf("%d%s%.4f", int(degrees), padding, fraction)
}
// ParseDecimal parses a decimal format coordinate. // ParseDecimal parses a decimal format coordinate.
// e.g: 151.196019 // e.g: 151.196019
func ParseDecimal(s string) (LatLong, error) { func ParseDecimal(s string) (float64, error) {
// Make sure it parses as a float. // Make sure it parses as a float.
l, err := strconv.ParseFloat(s, 64) l, err := strconv.ParseFloat(s, 64)
if err != nil || s[0] != '-' && len(strings.Split(s, ".")[0]) > 3 { if err != nil || s[0] != '-' && len(strings.Split(s, ".")[0]) > 3 {
return LatLong(0.0), errors.New("parse error (not decimal coordinate)") return 0.0, errors.New("parse error (not decimal coordinate)")
} }
return LatLong(l), nil return l, nil
} }
// ParseDMS parses a coordinate in degrees, minutes, seconds. // ParseDMS parses a coordinate in degrees, minutes, seconds.
// - e.g. 33° 23' 22" // - e.g. 33° 23' 22"
func ParseDMS(s string) (LatLong, error) { func ParseDMS(s string) (float64, error) {
degrees := 0 degrees := 0
minutes := 0 minutes := 0
seconds := 0.0 seconds := 0.0
@ -138,42 +115,55 @@ func ParseDMS(s string) (LatLong, error) {
var err error var err error
for i, r := range s { for i, r := range s {
if unicode.IsNumber(r) || r == '.' { switch {
case unicode.IsNumber(r) || r == '.':
if !endNumber { if !endNumber {
tmpBytes = append(tmpBytes, s[i]) tmpBytes = append(tmpBytes, s[i])
} else { } else {
return 0, errors.New("parse error (no delimiter)") return 0, errors.New("parse error (no delimiter)")
} }
} else if unicode.IsSpace(r) && len(tmpBytes) > 0 { case unicode.IsSpace(r) && len(tmpBytes) > 0:
endNumber = true endNumber = true
} else if r == Degrees { case r == Degrees:
if degrees, err = strconv.Atoi(string(tmpBytes)); err != nil { if degrees, err = strconv.Atoi(string(tmpBytes)); err != nil {
return 0, errors.New("parse error (degrees)") return 0, errors.New("parse error (degrees)")
} }
tmpBytes = tmpBytes[:0] tmpBytes = tmpBytes[:0]
endNumber = false endNumber = false
} else if s[i] == Minutes { case s[i] == Minutes:
if minutes, err = strconv.Atoi(string(tmpBytes)); err != nil { if minutes, err = strconv.Atoi(string(tmpBytes)); err != nil {
return 0, errors.New("parse error (minutes)") return 0, errors.New("parse error (minutes)")
} }
tmpBytes = tmpBytes[:0] tmpBytes = tmpBytes[:0]
endNumber = false endNumber = false
} else if s[i] == Seconds { case s[i] == Seconds:
if seconds, err = strconv.ParseFloat(string(tmpBytes), 64); err != nil { if seconds, err = strconv.ParseFloat(string(tmpBytes), 64); err != nil {
return 0, errors.New("parse error (seconds)") return 0, errors.New("parse error (seconds)")
} }
tmpBytes = tmpBytes[:0] tmpBytes = tmpBytes[:0]
endNumber = false endNumber = false
} else if unicode.IsSpace(r) && len(tmpBytes) == 0 { case unicode.IsSpace(r) && len(tmpBytes) == 0:
continue continue
} else { default:
return 0, fmt.Errorf("parse error (unknown symbol [%d])", s[i]) return 0, fmt.Errorf("parse error (unknown symbol [%d])", s[i])
} }
} }
val := LatLong(float64(degrees) + (float64(minutes) / 60.0) + (float64(seconds) / 60.0 / 60.0)) if len(tmpBytes) > 0 {
return 0, fmt.Errorf("parse error (trailing data [%s])", string(tmpBytes))
}
val := float64(degrees) + (float64(minutes) / 60.0) + (float64(seconds) / 60.0 / 60.0)
return val, nil return val, nil
} }
// FormatDMS returns the degrees, minutes, seconds format for the given LatLong.
func FormatDMS(l float64) string {
val := math.Abs(l)
degrees := int(math.Floor(val))
minutes := int(math.Floor(60 * (val - float64(degrees))))
seconds := 3600 * (val - float64(degrees) - (float64(minutes) / 60))
return fmt.Sprintf("%d\u00B0 %d' %f\"", degrees, minutes, seconds)
}
// Time type // Time type
type Time struct { type Time struct {
Valid bool Valid bool