gps: improve parsing and add tests to verify

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram
2023-01-16 22:07:08 +01:00
committed by Ron Evans
parent ecb343b3b3
commit f2637a87d2
3 changed files with 130 additions and 22 deletions
+14 -3
View File
@@ -42,6 +42,12 @@ func (ge GPSError) Unwrap() error {
return ge.Err
}
const (
minimumNMEALength = 7
startingDelimiter = '$'
checksumDelimiter = '*'
)
// Device wraps a connection to a GPS device.
type Device struct {
buffer []byte
@@ -87,11 +93,11 @@ func (gps *Device) readNextSentence() (sentence string) {
gps.sentence.Reset()
var b byte = ' '
for b != '$' {
for b != startingDelimiter {
b = gps.readNextByte()
}
for b != '*' {
for b != checksumDelimiter {
gps.sentence.WriteByte(b)
b = gps.readNextByte()
}
@@ -153,8 +159,13 @@ func (gps *Device) WriteBytes(bytes []byte) {
}
// validSentence checks if a sentence has been received uncorrupted
// For example, a valid NMEA sentence such as this:
// $GPGLL,3751.65,S,14507.36,E*77
// It has to start with a '$' character.
// It has to have a 5 character long sentence identifier.
// It has to end with a '*' character following by a checksum.
func validSentence(sentence string) error {
if len(sentence) < 7 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
if len(sentence) < minimumNMEALength || sentence[0] != startingDelimiter || sentence[len(sentence)-3] != checksumDelimiter {
return errInvalidNMEASentenceLength
}
var cs byte = 0
+19 -19
View File
@@ -60,11 +60,11 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
return fix, errInvalidGGASentence
}
fix.Altitude = findAltitude(fields[9])
fix.Satellites = findSatellites(fields[7])
fix.Longitude = findLongitude(fields[4], fields[5])
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Time = findTime(fields[1])
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Longitude = findLongitude(fields[4], fields[5])
fix.Satellites = findSatellites(fields[7])
fix.Altitude = findAltitude(fields[9])
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
return fix, nil
@@ -75,11 +75,11 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
return fix, errInvalidGLLSentence
}
fix.Latitude = findLatitude(fields[2], fields[3])
fix.Longitude = findLongitude(fields[4], fields[5])
fix.Time = findTime(fields[6])
fix.Latitude = findLatitude(fields[1], fields[2])
fix.Longitude = findLongitude(fields[3], fields[4])
fix.Time = findTime(fields[5])
fix.Valid = (fields[7] == "A")
fix.Valid = (fields[6] == "A")
return fix, nil
case "RMC":
@@ -89,12 +89,12 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
return fix, errInvalidRMCSentence
}
fix.Longitude = findLongitude(fields[5], fields[6])
fix.Latitude = findLatitude(fields[3], fields[4])
fix.Time = findTime(fields[1])
fix.Valid = (fields[2] == "A")
fix.Latitude = findLatitude(fields[3], fields[4])
fix.Longitude = findLongitude(fields[5], fields[6])
fix.Speed = findSpeed(fields[7])
fix.Heading = findHeading(fields[8])
fix.Valid = (len(fields[2]) > 0 && fields[2] == "A")
return fix, nil
}
@@ -113,8 +113,8 @@ func findTime(val string) time.Time {
m, _ := strconv.ParseInt(val[2:4], 10, 8)
s, _ := strconv.ParseInt(val[4:6], 10, 8)
ms := int64(0)
if len(val) == 10 {
ms, _ = strconv.ParseInt(val[7:10], 10, 16)
if len(val) > 6 {
ms, _ = strconv.ParseInt(val[7:], 10, 16)
}
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
@@ -135,11 +135,11 @@ func findAltitude(val string) int32 {
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
func findLatitude(val, hemi string) float32 {
if len(val) > 8 {
var dd = val[0:2]
var mm = val[2:]
var d, _ = strconv.ParseFloat(dd, 32)
var m, _ = strconv.ParseFloat(mm, 32)
var v = float32(d + (m / 60))
dd := val[0:2]
mm := val[2:]
d, _ := strconv.ParseFloat(dd, 32)
m, _ := strconv.ParseFloat(mm, 32)
v := float32(d + (m / 60))
if hemi == "S" {
v *= -1
}
@@ -148,7 +148,7 @@ func findLatitude(val, hemi string) float32 {
return 0.0
}
// findLatitude returns the longitude from an NMEA sentence:
// findLongitude returns the longitude from an NMEA sentence:
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
func findLongitude(val, hemi string) float32 {
if len(val) > 8 {
+97
View File
@@ -0,0 +1,97 @@
package gps
import (
"testing"
"time"
qt "github.com/frankban/quicktest"
)
func TestParseUnknownSentence(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPGSV,3,1,09,07,14,317,22,08,31,284,25,10,32,133,39,16,85,232,29*7F"
_, err := p.Parse(val)
c.Assert(err.Error(), qt.Contains, "unsupported NMEA sentence type")
}
func TestParseGGA(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPGGA,115739.00,4158.8441367,N,09147.4416929,"
fix, err := p.Parse(val)
if err != errInvalidGGASentence {
t.Error("should have errInvalidGGASentence error")
}
val = "$GPGGA,115739.00,4158.8441367,N,09147.4416929,W,4,13,0.9,255.747,M,-32.00,M,01,0000*6E"
fix, err = p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Latitude, qt.Equals, float32(41.980735778808594))
c.Assert(fix.Longitude, qt.Equals, float32(-91.79069519042969))
c.Assert(fix.Altitude, qt.Equals, int32(255))
}
func TestParseGLL(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPGLL,3953.88008971,N,10506.7531891"
_, err := p.Parse(val)
if err != errInvalidGLLSentence {
t.Error("should have errInvalidGLLSentence error")
}
val = "$GPGLL,5109.0262317,N,11401.8407304,W,202725.00,A,D*79"
fix, err := p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Latitude, qt.Equals, float32(51.15043640136719))
c.Assert(fix.Longitude, qt.Equals, float32(-114.03067779541016))
}
func TestParseRMC(t *testing.T) {
c := qt.New(t)
p := NewParser()
val := "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,"
_, err := p.Parse(val)
if err != errInvalidRMCSentence {
t.Error("should have errInvalidRMCSentence error")
}
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,130522,0.0,E,D*2B"
fix, err := p.Parse(val)
if err != nil {
t.Error("should have parsed")
}
c.Assert(fix.Latitude, qt.Equals, float32(51.15043640136719))
c.Assert(fix.Longitude, qt.Equals, float32(-114.03067779541016))
}
func TestTime(t *testing.T) {
c := qt.New(t)
val := ""
tm := findTime(val)
c.Assert(tm, qt.Equals, time.Time{})
val = "225446"
tm = findTime(val)
c.Assert(tm, qt.Equals, time.Date(0, 0, 0, 22, 54, 46, 0, time.UTC))
val = "124326.02752"
tm = findTime(val)
c.Assert(tm, qt.Equals, time.Date(0, 0, 0, 12, 43, 26, 2752, time.UTC))
}