mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-16 12:53:23 +00:00
gps: improve parsing and add tests to verify
Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
+14
-3
@@ -42,6 +42,12 @@ func (ge GPSError) Unwrap() error {
|
|||||||
return ge.Err
|
return ge.Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
minimumNMEALength = 7
|
||||||
|
startingDelimiter = '$'
|
||||||
|
checksumDelimiter = '*'
|
||||||
|
)
|
||||||
|
|
||||||
// Device wraps a connection to a GPS device.
|
// Device wraps a connection to a GPS device.
|
||||||
type Device struct {
|
type Device struct {
|
||||||
buffer []byte
|
buffer []byte
|
||||||
@@ -87,11 +93,11 @@ func (gps *Device) readNextSentence() (sentence string) {
|
|||||||
gps.sentence.Reset()
|
gps.sentence.Reset()
|
||||||
var b byte = ' '
|
var b byte = ' '
|
||||||
|
|
||||||
for b != '$' {
|
for b != startingDelimiter {
|
||||||
b = gps.readNextByte()
|
b = gps.readNextByte()
|
||||||
}
|
}
|
||||||
|
|
||||||
for b != '*' {
|
for b != checksumDelimiter {
|
||||||
gps.sentence.WriteByte(b)
|
gps.sentence.WriteByte(b)
|
||||||
b = gps.readNextByte()
|
b = gps.readNextByte()
|
||||||
}
|
}
|
||||||
@@ -153,8 +159,13 @@ func (gps *Device) WriteBytes(bytes []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validSentence checks if a sentence has been received uncorrupted
|
// 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 {
|
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
|
return errInvalidNMEASentenceLength
|
||||||
}
|
}
|
||||||
var cs byte = 0
|
var cs byte = 0
|
||||||
|
|||||||
+19
-19
@@ -60,11 +60,11 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
|
|||||||
return fix, errInvalidGGASentence
|
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.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)
|
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
||||||
|
|
||||||
return fix, nil
|
return fix, nil
|
||||||
@@ -75,11 +75,11 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
|
|||||||
return fix, errInvalidGLLSentence
|
return fix, errInvalidGLLSentence
|
||||||
}
|
}
|
||||||
|
|
||||||
fix.Latitude = findLatitude(fields[2], fields[3])
|
fix.Latitude = findLatitude(fields[1], fields[2])
|
||||||
fix.Longitude = findLongitude(fields[4], fields[5])
|
fix.Longitude = findLongitude(fields[3], fields[4])
|
||||||
fix.Time = findTime(fields[6])
|
fix.Time = findTime(fields[5])
|
||||||
|
|
||||||
fix.Valid = (fields[7] == "A")
|
fix.Valid = (fields[6] == "A")
|
||||||
|
|
||||||
return fix, nil
|
return fix, nil
|
||||||
case "RMC":
|
case "RMC":
|
||||||
@@ -89,12 +89,12 @@ func (parser *Parser) Parse(sentence string) (Fix, error) {
|
|||||||
return fix, errInvalidRMCSentence
|
return fix, errInvalidRMCSentence
|
||||||
}
|
}
|
||||||
|
|
||||||
fix.Longitude = findLongitude(fields[5], fields[6])
|
|
||||||
fix.Latitude = findLatitude(fields[3], fields[4])
|
|
||||||
fix.Time = findTime(fields[1])
|
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.Speed = findSpeed(fields[7])
|
||||||
fix.Heading = findHeading(fields[8])
|
fix.Heading = findHeading(fields[8])
|
||||||
fix.Valid = (len(fields[2]) > 0 && fields[2] == "A")
|
|
||||||
|
|
||||||
return fix, nil
|
return fix, nil
|
||||||
}
|
}
|
||||||
@@ -113,8 +113,8 @@ func findTime(val string) time.Time {
|
|||||||
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
||||||
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
||||||
ms := int64(0)
|
ms := int64(0)
|
||||||
if len(val) == 10 {
|
if len(val) > 6 {
|
||||||
ms, _ = strconv.ParseInt(val[7:10], 10, 16)
|
ms, _ = strconv.ParseInt(val[7:], 10, 16)
|
||||||
}
|
}
|
||||||
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
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
|
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
|
||||||
func findLatitude(val, hemi string) float32 {
|
func findLatitude(val, hemi string) float32 {
|
||||||
if len(val) > 8 {
|
if len(val) > 8 {
|
||||||
var dd = val[0:2]
|
dd := val[0:2]
|
||||||
var mm = val[2:]
|
mm := val[2:]
|
||||||
var d, _ = strconv.ParseFloat(dd, 32)
|
d, _ := strconv.ParseFloat(dd, 32)
|
||||||
var m, _ = strconv.ParseFloat(mm, 32)
|
m, _ := strconv.ParseFloat(mm, 32)
|
||||||
var v = float32(d + (m / 60))
|
v := float32(d + (m / 60))
|
||||||
if hemi == "S" {
|
if hemi == "S" {
|
||||||
v *= -1
|
v *= -1
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ func findLatitude(val, hemi string) float32 {
|
|||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// findLatitude returns the longitude from an NMEA sentence:
|
// findLongitude returns the longitude from an NMEA sentence:
|
||||||
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
|
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
|
||||||
func findLongitude(val, hemi string) float32 {
|
func findLongitude(val, hemi string) float32 {
|
||||||
if len(val) > 8 {
|
if len(val) > 8 {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user