mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
1513808425
Signed-off-by: deadprogram <ron@hybridgroup.com>
216 lines
5.1 KiB
Go
216 lines
5.1 KiB
Go
package gps
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Parser for GPS NMEA sentences.
|
|
type Parser struct {
|
|
}
|
|
|
|
// Fix is a GPS location fix
|
|
type Fix struct {
|
|
// Valid if the fix was valid.
|
|
Valid bool
|
|
|
|
// Time that the fix was taken, in UTC time.
|
|
Time time.Time
|
|
|
|
// Latitude is the decimal latitude. Negative numbers indicate S.
|
|
Latitude float32
|
|
|
|
// Longitude is the decimal longitude. Negative numbers indicate E.
|
|
Longitude float32
|
|
|
|
// Altitude is only returned for GGA sentences.
|
|
Altitude int32
|
|
|
|
// Satellites is the number of visible satellites, but is only returned for GGA sentences.
|
|
Satellites int16
|
|
|
|
// Speed based on reported movement. Only returned for RMC sentences.
|
|
Speed float32
|
|
|
|
// Heading based on reported movement. Only returned for RMC sentences.
|
|
Heading float32
|
|
}
|
|
|
|
// NewParser returns a GPS NMEA Parser.
|
|
func NewParser() Parser {
|
|
return Parser{}
|
|
}
|
|
|
|
// Parse parses a NMEA sentence looking for fix info.
|
|
func (parser *Parser) Parse(sentence string) (Fix, error) {
|
|
var fix Fix
|
|
if sentence == "" {
|
|
return fix, errEmptyNMEASentence
|
|
}
|
|
if len(sentence) < 6 {
|
|
return fix, errInvalidNMEASentenceLength
|
|
}
|
|
typ := sentence[3:6]
|
|
switch typ {
|
|
case "GGA":
|
|
// https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm
|
|
fields := strings.Split(sentence, ",")
|
|
if len(fields) != 15 {
|
|
return fix, errInvalidGGASentence
|
|
}
|
|
|
|
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
|
|
case "GLL":
|
|
// https://docs.novatel.com/OEM7/Content/Logs/GPGLL.htm
|
|
fields := strings.Split(sentence, ",")
|
|
if len(fields) != 8 {
|
|
return fix, errInvalidGLLSentence
|
|
}
|
|
|
|
fix.Latitude = findLatitude(fields[1], fields[2])
|
|
fix.Longitude = findLongitude(fields[3], fields[4])
|
|
fix.Time = findTime(fields[5])
|
|
|
|
fix.Valid = (fields[6] == "A")
|
|
|
|
return fix, nil
|
|
case "RMC":
|
|
// https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm
|
|
fields := strings.Split(sentence, ",")
|
|
if len(fields) != 13 {
|
|
return fix, errInvalidRMCSentence
|
|
}
|
|
|
|
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])
|
|
date := findDate(fields[9])
|
|
fix.Time = date.Add(time.Duration(fix.Time.Hour())*time.Hour +
|
|
time.Duration(fix.Time.Minute())*time.Minute +
|
|
time.Duration(fix.Time.Second())*time.Second +
|
|
time.Duration(fix.Time.Nanosecond())*time.Nanosecond)
|
|
|
|
return fix, nil
|
|
}
|
|
|
|
return fix, errInvalidNMEASentanceFormat
|
|
}
|
|
|
|
// findTime returns the time from an NMEA sentence:
|
|
// $--GGA,hhmmss.ss,,,,,,,,,,,,,*xx
|
|
func findTime(val string) time.Time {
|
|
if len(val) < 6 {
|
|
return time.Time{}
|
|
}
|
|
|
|
h, _ := strconv.ParseInt(val[0:2], 10, 8)
|
|
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
|
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
|
ms := int64(0)
|
|
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)
|
|
|
|
return t
|
|
}
|
|
|
|
// findAltitude returns the altitude from an NMEA sentence:
|
|
// $--GGA,,,,,,,,,25.8,,,,,*63
|
|
func findAltitude(val string) int32 {
|
|
if len(val) > 0 {
|
|
var v, _ = strconv.ParseFloat(val, 32)
|
|
return int32(v)
|
|
}
|
|
return -99999
|
|
}
|
|
|
|
// findLatitude returns the Latitude from an NMEA sentence:
|
|
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
|
|
func findLatitude(val, hemi string) float32 {
|
|
if len(val) > 8 {
|
|
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
|
|
}
|
|
return v
|
|
}
|
|
return 0.0
|
|
}
|
|
|
|
// findLongitude returns the longitude from an NMEA sentence:
|
|
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
|
|
func findLongitude(val, hemi string) float32 {
|
|
if len(val) > 8 {
|
|
var ddd = val[0:3]
|
|
var mm = val[3:]
|
|
var d, _ = strconv.ParseFloat(ddd, 32)
|
|
var m, _ = strconv.ParseFloat(mm, 32)
|
|
var v = float32(d + (m / 60))
|
|
if hemi == "W" {
|
|
v *= -1
|
|
}
|
|
return v
|
|
}
|
|
return 0.0
|
|
}
|
|
|
|
// findSatellites returns the satellites from an NMEA sentence:
|
|
// $--GGA,,,,,,,nn,,,,,,,*hh
|
|
func findSatellites(val string) (n int16) {
|
|
if len(val) > 0 {
|
|
var nn = val
|
|
var v, _ = strconv.ParseInt(nn, 10, 32)
|
|
n = int16(v)
|
|
return n
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// findDate returns the date from an RMC NMEA sentence.
|
|
func findDate(val string) time.Time {
|
|
if len(val) < 6 {
|
|
return time.Time{}
|
|
}
|
|
|
|
d, _ := strconv.ParseInt(val[0:2], 10, 8)
|
|
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
|
y, _ := strconv.ParseInt(val[4:6], 10, 8)
|
|
t := time.Date(int(2000+y), time.Month(m), int(d), 0, 0, 0, 0, time.UTC)
|
|
|
|
return t
|
|
}
|
|
|
|
// findSpeed returns the speed from an RMC NMEA sentence.
|
|
func findSpeed(val string) float32 {
|
|
if len(val) > 0 {
|
|
var v, _ = strconv.ParseFloat(val, 32)
|
|
return float32(v)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// findHeading returns the speed from an RMC NMEA sentence.
|
|
func findHeading(val string) float32 {
|
|
if len(val) > 0 {
|
|
var v, _ = strconv.ParseFloat(val, 32)
|
|
return float32(v)
|
|
}
|
|
return 0
|
|
}
|