From 6bb15b938044eac83aa8ed3068cd3a252ed50c52 Mon Sep 17 00:00:00 2001 From: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> Date: Sat, 3 Aug 2019 23:43:29 +0300 Subject: [PATCH 01/11] att support for parsing MTK commands and update go mod files Signed-off-by: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> --- README.md | 1 + go.mod | 6 +++++- go.sum | 6 ++++++ mtk.go | 32 ++++++++++++++++++++++++++++++++ sentence.go | 16 +++++++++++++--- 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 go.sum create mode 100644 mtk.go diff --git a/README.md b/README.md index 3677adc..c42b22d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ After this command *go-nmea* is ready to use. Its source will be in: At this moment, this library supports the following sentence types: - [RMC](http://aprs.gids.nl/nmea/#rmc) - Recommended Minimum Specific GPS/Transit data +- [PMTK](https://www.rhydolabz.com/documents/25/PMTK_A11.pdf) - Messages for setting and reading commands for MediaTek gps modules. - [GGA](http://aprs.gids.nl/nmea/#gga) - GPS Positioning System Fix Data - [GSA](http://aprs.gids.nl/nmea/#gsa) - GPS DOP and active satellites - [GSV](http://aprs.gids.nl/nmea/#gsv) - GPS Satellites in view diff --git a/go.mod b/go.mod index c3bbdfe..f557452 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,7 @@ module github.com/adrianmo/go-nmea -require github.com/stretchr/testify v1.2.1 +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b10db54 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/mtk.go b/mtk.go new file mode 100644 index 0000000..55b00b3 --- /dev/null +++ b/mtk.go @@ -0,0 +1,32 @@ +package nmea + +import "strconv" + +const ( + // TypeMTK type for PMTK sentences + TypeMTK = "PMTK" +) + +// MTK is the Time, position, and fix related data of the receiver. +type MTK struct { + BaseSentence + Cmd, + Flag int +} + +// newMTK constructor +func newMTK(s BaseSentence) (MTK, error) { + cmd, err := strconv.Atoi(s.Fields[0]) + if err != nil { + return MTK{}, err + } + flag, err := strconv.Atoi(s.Fields[1]) + if err != nil { + return MTK{}, err + } + return MTK{ + BaseSentence: s, + Cmd: cmd, + Flag: flag, + }, nil +} diff --git a/sentence.go b/sentence.go index 69e92a5..198ddbb 100644 --- a/sentence.go +++ b/sentence.go @@ -69,7 +69,7 @@ func parseSentence(raw string) (BaseSentence, error) { fieldsRaw = raw[startIndex+1 : sumSepIndex] fields = strings.Split(fieldsRaw, FieldSep) checksumRaw = strings.ToUpper(raw[sumSepIndex+1:]) - checksum = xorChecksum(fieldsRaw) + checksum = XORChecksum(fieldsRaw) ) // Validate the checksum if checksum != checksumRaw { @@ -77,6 +77,7 @@ func parseSentence(raw string) (BaseSentence, error) { "nmea: sentence checksum mismatch [%s != %s]", checksum, checksumRaw) } talker, typ := parsePrefix(fields[0]) + return BaseSentence{ Talker: talker, Type: typ, @@ -88,6 +89,9 @@ func parseSentence(raw string) (BaseSentence, error) { // parsePrefix takes the first field and splits it into a talker id and data type. func parsePrefix(s string) (string, string) { + if strings.HasPrefix(s, "PMTK") { + return "PMTK", s[4:] + } if strings.HasPrefix(s, "P") { return "P", s[1:] } @@ -97,9 +101,9 @@ func parsePrefix(s string) (string, string) { return s[:2], s[2:] } -// xor all the bytes in a string an return it +// XORChecksum xor all the bytes in a string an return it // as an uppercase hex string -func xorChecksum(s string) string { +func XORChecksum(s string) string { var checksum uint8 for i := 0; i < len(s); i++ { checksum ^= s[i] @@ -114,6 +118,12 @@ func Parse(raw string) (Sentence, error) { return nil, err } if strings.HasPrefix(s.Raw, SentenceStart) { + // MTK message types share the same format + // so we return the same struct for all types. + switch s.Talker { + case TypeMTK: + return newMTK(s) + } switch s.Type { case TypeRMC: return newRMC(s) From 8a6df20f93841e069bca1ebc5fbcb3882dfab15e Mon Sep 17 00:00:00 2001 From: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> Date: Wed, 7 Aug 2019 16:40:35 +0300 Subject: [PATCH 02/11] use the parser Signed-off-by: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> --- mtk.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/mtk.go b/mtk.go index 55b00b3..8d1763b 100644 --- a/mtk.go +++ b/mtk.go @@ -1,7 +1,5 @@ package nmea -import "strconv" - const ( // TypeMTK type for PMTK sentences TypeMTK = "PMTK" @@ -11,22 +9,17 @@ const ( type MTK struct { BaseSentence Cmd, - Flag int + Flag int64 } // newMTK constructor func newMTK(s BaseSentence) (MTK, error) { - cmd, err := strconv.Atoi(s.Fields[0]) - if err != nil { - return MTK{}, err - } - flag, err := strconv.Atoi(s.Fields[1]) - if err != nil { - return MTK{}, err - } + p := newParser(s) + cmd := p.Int64(0, "command") + flag := p.Int64(1, "flag") return MTK{ BaseSentence: s, Cmd: cmd, Flag: flag, - }, nil + }, p.Err() } From 1e7fa76ad0a0d32134e6e5f6066e0239bcdd314a Mon Sep 17 00:00:00 2001 From: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> Date: Thu, 8 Aug 2019 02:36:41 +0300 Subject: [PATCH 03/11] revert xorChecksum expose Signed-off-by: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> --- sentence.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sentence.go b/sentence.go index 198ddbb..2f8068c 100644 --- a/sentence.go +++ b/sentence.go @@ -69,7 +69,7 @@ func parseSentence(raw string) (BaseSentence, error) { fieldsRaw = raw[startIndex+1 : sumSepIndex] fields = strings.Split(fieldsRaw, FieldSep) checksumRaw = strings.ToUpper(raw[sumSepIndex+1:]) - checksum = XORChecksum(fieldsRaw) + checksum = xorChecksum(fieldsRaw) ) // Validate the checksum if checksum != checksumRaw { @@ -77,7 +77,6 @@ func parseSentence(raw string) (BaseSentence, error) { "nmea: sentence checksum mismatch [%s != %s]", checksum, checksumRaw) } talker, typ := parsePrefix(fields[0]) - return BaseSentence{ Talker: talker, Type: typ, @@ -101,9 +100,9 @@ func parsePrefix(s string) (string, string) { return s[:2], s[2:] } -// XORChecksum xor all the bytes in a string an return it +// xorChecksum xor all the bytes in a string an return it // as an uppercase hex string -func XORChecksum(s string) string { +func xorChecksum(s string) string { var checksum uint8 for i := 0; i < len(s); i++ { checksum ^= s[i] From 9cd0d321de5364fa9435d8561c4c373b6de7dcd3 Mon Sep 17 00:00:00 2001 From: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> Date: Thu, 8 Aug 2019 02:38:39 +0300 Subject: [PATCH 04/11] expose xorChecksum Signed-off-by: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> --- sentence.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentence.go b/sentence.go index 69e92a5..a77e34e 100644 --- a/sentence.go +++ b/sentence.go @@ -69,7 +69,7 @@ func parseSentence(raw string) (BaseSentence, error) { fieldsRaw = raw[startIndex+1 : sumSepIndex] fields = strings.Split(fieldsRaw, FieldSep) checksumRaw = strings.ToUpper(raw[sumSepIndex+1:]) - checksum = xorChecksum(fieldsRaw) + checksum = XORChecksum(fieldsRaw) ) // Validate the checksum if checksum != checksumRaw { @@ -97,9 +97,9 @@ func parsePrefix(s string) (string, string) { return s[:2], s[2:] } -// xor all the bytes in a string an return it +// XORChecksum xor all the bytes in a string an return it // as an uppercase hex string -func xorChecksum(s string) string { +func XORChecksum(s string) string { var checksum uint8 for i := 0; i < len(s); i++ { checksum ^= s[i] From b870b8320412c6a75dc617410094b0f5a719db3e Mon Sep 17 00:00:00 2001 From: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> Date: Fri, 9 Aug 2019 14:04:08 +0300 Subject: [PATCH 05/11] add mtk test Signed-off-by: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> --- mtk_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 mtk_test.go diff --git a/mtk_test.go b/mtk_test.go new file mode 100644 index 0000000..7ce6770 --- /dev/null +++ b/mtk_test.go @@ -0,0 +1,50 @@ +package nmea + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var mtktests = []struct { + name string + raw string + err string + msg MTK +}{ + { + name: "good: Packet Type: 001 PMTK_ACK", + raw: "$PMTK001,604,3*" + xorChecksum("PMTK001,604,3"), + msg: MTK{ + Cmd: 604, + Flag: 3, + }, + }, + { + name: "missing flag", + raw: "$PMTK001,604*" + xorChecksum("PMTK001,604"), + err: "nmea: PMTK001 invalid flag: index out of range", + }, + { + name: "missing cmd", + raw: "$PMTK001*" + xorChecksum("PMTK001"), + err: "nmea: PMTK001 invalid command: index out of range", + }, +} + +func TestMTK(t *testing.T) { + for _, tt := range mtktests { + t.Run(tt.name, func(t *testing.T) { + m, err := Parse(tt.raw) + if tt.err != "" { + assert.Error(t, err) + assert.EqualError(t, err, tt.err) + } else { + assert.NoError(t, err) + mtk := m.(MTK) + mtk.BaseSentence = BaseSentence{} + assert.Equal(t, tt.msg, mtk) + } + }) + } +} From fb6e0124c6cb29785c18c4b8b7529242eeaf71e3 Mon Sep 17 00:00:00 2001 From: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> Date: Sun, 25 Aug 2019 11:51:48 +0300 Subject: [PATCH 06/11] added estimated fix which is returned by MTK3339 http://lefebure.com/articles/nmea-gga/ mentioned few other types as well, but probably can add these only if needed. Signed-off-by: Krasi Georgiev <8903888+krasi-georgiev@users.noreply.github.com> --- gga.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gga.go b/gga.go index 2737475..a27f22b 100644 --- a/gga.go +++ b/gga.go @@ -15,6 +15,8 @@ const ( RTK = "4" // FRTK float RTK fix FRTK = "5" + // EST estimated fix. + EST = "6" ) // GGA is the Time, position, and fix related data of the receiver. @@ -41,7 +43,7 @@ func newGGA(s BaseSentence) (GGA, error) { Time: p.Time(0, "time"), Latitude: p.LatLong(1, 2, "latitude"), Longitude: p.LatLong(3, 4, "longitude"), - FixQuality: p.EnumString(5, "fix quality", Invalid, GPS, DGPS, PPS, RTK, FRTK), + FixQuality: p.EnumString(5, "fix quality", Invalid, GPS, DGPS, PPS, RTK, FRTK, EST), NumSatellites: p.Int64(6, "number of satellites"), HDOP: p.Float64(7, "hdop"), Altitude: p.Float64(8, "altitude"), From 3f3946054caed24fb5e1d8ce797cad3302b527ce Mon Sep 17 00:00:00 2001 From: Kamil Krawczyk Date: Fri, 27 Sep 2019 20:57:58 +0200 Subject: [PATCH 07/11] added latitude range checking --- parser.go | 9 +++++++++ parser_test.go | 36 ++++++++++++++++++++++++++++++++++++ types.go | 3 --- types_test.go | 1 - 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/parser.go b/parser.go index 7f102c3..c9eae67 100644 --- a/parser.go +++ b/parser.go @@ -178,6 +178,15 @@ func (p *parser) LatLong(i, j int, context string) float64 { if err != nil { p.SetErr(context, err.Error()) } + + if (b == North || b == South) && (v < -90.0 || 90.0 < v) { + p.SetErr(context, "latitude is not in range (-90, 90)") + return 0 + } else if (b == West || b == East) && (v < -180.0 || 180.0 < v) { + p.SetErr(context, "longitude is not in range (-180, 180)") + return 0 + } + return v } diff --git a/parser_test.go b/parser_test.go index 255757a..a521af8 100644 --- a/parser_test.go +++ b/parser_test.go @@ -259,6 +259,42 @@ var parsertests = []struct { return p.Date(0, "context") }, }, + { + name: "LatLong", + fields: []string{"5000.0000", "N"}, + expected: 50.0, + parse: func(p *parser) interface{} { + return p.LatLong(0, 1, "context") + }, + }, + { + name: "LatLong - latitude out of range", + fields: []string{"9100.0000", "N"}, + expected: 0.0, + hasErr: true, + parse: func(p *parser) interface{} { + return p.LatLong(0, 1, "context") + }, + }, + { + name: "LatLong - longitude out of range", + fields: []string{"18100.0000", "W"}, + expected: 0.0, + hasErr: true, + parse: func(p *parser) interface{} { + return p.LatLong(0, 1, "context") + }, + }, + { + name: "LatLong with existing error", + fields: []string{"5000.0000", "W"}, + expected: 0.0, + hasErr: true, + parse: func(p *parser) interface{} { + p.SetErr("context", "value") + return p.LatLong(0, 1, "context") + }, + }, } func TestParser(t *testing.T) { diff --git a/types.go b/types.go index d097e10..0d899e6 100644 --- a/types.go +++ b/types.go @@ -49,9 +49,6 @@ func ParseLatLong(s string) (float64, error) { } else { return 0, fmt.Errorf("cannot parse [%s], unknown format", s) } - if l < -180.0 || 180.0 < l { - return 0, errors.New("coordinate is not in range -180, 180") - } return l, nil } diff --git a/types_test.go b/types_test.go index 6c77094..5a301b2 100644 --- a/types_test.go +++ b/types_test.go @@ -18,7 +18,6 @@ func TestParseLatLong(t *testing.T) { {"33\u00B0 12' 34.3423\"", 33.209540, false}, // dms {"3345.1232 N", 33.752054, false}, // gps {"151.234532", 151.234532, false}, // decimal - {"200.000", 0, true}, // out of range } for _, tt := range tests { t.Run(tt.value, func(t *testing.T) { From 52da6e28594c29e5bb76b3c444d16d809eac9f69 Mon Sep 17 00:00:00 2001 From: Kamil Krawczyk Date: Wed, 2 Oct 2019 20:16:20 +0200 Subject: [PATCH 08/11] functions returning a direction sign have been added --- types.go | 16 ++++++++++++++++ types_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/types.go b/types.go index 0d899e6..ded6b24 100644 --- a/types.go +++ b/types.go @@ -243,3 +243,19 @@ func ParseDate(ddmmyy string) (Date, error) { } return Date{true, dd, mm, yy}, nil } + +// LatDir returns the latitude direction symbol +func LatDir(l float64) string { + if l < 0.0 { + return South + } + return North +} + +// LonDir returns the longitude direction symbol +func LonDir(l float64) string { + if l < 0.0 { + return East + } + return West +} diff --git a/types_test.go b/types_test.go index 5a301b2..29b22b6 100644 --- a/types_test.go +++ b/types_test.go @@ -219,3 +219,33 @@ func TestDateString(t *testing.T) { t.Fatalf("got %s expected %s", s, expected) } } + +func TestLatDir(t *testing.T) { + tests := []struct { + value float64 + expected string + }{ + {50.0, "N"}, + {-50.0, "S"}, + } + for _, tt := range tests { + if s := LatDir(tt.value); s != tt.expected { + t.Fatalf("got %s expected %s", s, tt.expected) + } + } +} + +func TestLonDir(t *testing.T) { + tests := []struct { + value float64 + expected string + }{ + {100.0, "W"}, + {-100.0, "E"}, + } + for _, tt := range tests { + if s := LonDir(tt.value); s != tt.expected { + t.Fatalf("got %s expected %s", s, tt.expected) + } + } +} From 955e3a22ef36d6b23b3c72914d85cc566802bb99 Mon Sep 17 00:00:00 2001 From: kvartborg Date: Tue, 4 Feb 2020 12:47:12 +0100 Subject: [PATCH 09/11] Add VHW water speed and heading --- README.md | 1 + sentence.go | 2 ++ vhw.go | 28 ++++++++++++++++++++++++++++ vhw_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 vhw.go create mode 100644 vhw_test.go diff --git a/README.md b/README.md index c42b22d..46c6c68 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ At this moment, this library supports the following sentence types: - [VDM/VDO](http://catb.org/gpsd/AIVDM.html) - Encapsulated binary payload - [WPL](http://aprs.gids.nl/nmea/#wpl) - Waypoint location - [RTE](http://aprs.gids.nl/nmea/#rte) - Route +- [VHW](https://www.tronico.fi/OH6NT/docs/NMEA0183.pdf) - Water Speed and Heading ## Example diff --git a/sentence.go b/sentence.go index 1f6abfe..9258d05 100644 --- a/sentence.go +++ b/sentence.go @@ -150,6 +150,8 @@ func Parse(raw string) (Sentence, error) { return newWPL(s) case TypeRTE: return newRTE(s) + case TypeVHW: + return newVHW(s) } } if strings.HasPrefix(s.Raw, SentenceStartEncapsulated) { diff --git a/vhw.go b/vhw.go new file mode 100644 index 0000000..7177378 --- /dev/null +++ b/vhw.go @@ -0,0 +1,28 @@ +package nmea + +const ( + // TypeVHW type for VHW sentences + TypeVHW = "VHW" +) + +// VHW contains information about water speed and heading +type VHW struct { + BaseSentence + TrueHeading float64 + MagneticHeading float64 + SpeedThroughWaterKnots float64 + SpeedThroughWaterKPH float64 +} + +// newVHW constructor +func newVHW(s BaseSentence) (VHW, error) { + p := newParser(s) + p.AssertType(TypeVHW) + return VHW{ + BaseSentence: s, + TrueHeading: p.Float64(0, "true heading"), + MagneticHeading: p.Float64(2, "magnetic heading"), + SpeedThroughWaterKnots: p.Float64(4, "speed through water in knots"), + SpeedThroughWaterKPH: p.Float64(6, "speed through water in kilometers per hour"), + }, p.Err() +} diff --git a/vhw_test.go b/vhw_test.go new file mode 100644 index 0000000..03565bc --- /dev/null +++ b/vhw_test.go @@ -0,0 +1,47 @@ +package nmea + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var vhw = []struct { + name string + raw string + err string + msg VHW +}{ + { + name: "good sentence", + raw: "$VWVHW,45.0,T,43.0,M,3.5,N,6.4,K*56", + msg: VHW{ + TrueHeading: 45.0, + MagneticHeading: 43.0, + SpeedThroughWaterKnots: 3.5, + SpeedThroughWaterKPH: 6.4, + }, + }, + { + name: "bad sentence", + raw: "$VWVHW,T,45.0,43.0,M,3.5,N,6.4,K*56", + err: "nmea: VWVHW invalid true heading: T", + }, +} + +func TestVHW(t *testing.T) { + for _, tt := range vhw { + t.Run(tt.name, func(t *testing.T) { + m, err := Parse(tt.raw) + if tt.err != "" { + assert.Error(t, err) + assert.EqualError(t, err, tt.err) + } else { + assert.NoError(t, err) + wpl := m.(VHW) + wpl.BaseSentence = BaseSentence{} + assert.Equal(t, tt.msg, wpl) + } + }) + } +} From d05ce7cd01b0da7dab4e5e9e666fe4c2c84f467e Mon Sep 17 00:00:00 2001 From: kvartborg Date: Tue, 4 Feb 2020 13:03:54 +0100 Subject: [PATCH 10/11] Correct copy paste typo --- vhw_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vhw_test.go b/vhw_test.go index 03565bc..dee2b1c 100644 --- a/vhw_test.go +++ b/vhw_test.go @@ -38,9 +38,9 @@ func TestVHW(t *testing.T) { assert.EqualError(t, err, tt.err) } else { assert.NoError(t, err) - wpl := m.(VHW) - wpl.BaseSentence = BaseSentence{} - assert.Equal(t, tt.msg, wpl) + vhw := m.(VHW) + vhw.BaseSentence = BaseSentence{} + assert.Equal(t, tt.msg, vhw) } }) } From 7ae4adf73682dce67ba3f3445a930662749e50b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Moreno?= Date: Thu, 13 Feb 2020 11:02:23 +0100 Subject: [PATCH 11/11] Fix checksum ref after branch update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adrián Moreno --- tagblock.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tagblock.go b/tagblock.go index d7f7a07..864b34c 100644 --- a/tagblock.go +++ b/tagblock.go @@ -89,7 +89,7 @@ func parseTagBlock(raw string) (TagBlock, string, error) { var ( fieldsRaw = tags[0:sumSepIndex] checksumRaw = strings.ToUpper(tags[sumSepIndex+1:]) - checksum = xorChecksum(fieldsRaw) + checksum = Checksum(fieldsRaw) err error )