tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/gps/gpsparser.go (about) 1 package gps 2 3 import ( 4 "strconv" 5 "strings" 6 "time" 7 ) 8 9 // Parser for GPS NMEA sentences. 10 type Parser struct { 11 } 12 13 // Fix is a GPS location fix 14 type Fix struct { 15 // Valid if the fix was valid. 16 Valid bool 17 18 // Time that the fix was taken, in UTC time. 19 Time time.Time 20 21 // Latitude is the decimal latitude. Negative numbers indicate S. 22 Latitude float32 23 24 // Longitude is the decimal longitude. Negative numbers indicate E. 25 Longitude float32 26 27 // Altitude is only returned for GGA sentences. 28 Altitude int32 29 30 // Satellites is the number of visible satellites, but is only returned for GGA sentences. 31 Satellites int16 32 33 // Speed based on reported movement. Only returned for RMC sentences. 34 Speed float32 35 36 // Heading based on reported movement. Only returned for RMC sentences. 37 Heading float32 38 } 39 40 // NewParser returns a GPS NMEA Parser. 41 func NewParser() Parser { 42 return Parser{} 43 } 44 45 // Parse parses a NMEA sentence looking for fix info. 46 func (parser *Parser) Parse(sentence string) (Fix, error) { 47 var fix Fix 48 if sentence == "" { 49 return fix, errEmptyNMEASentence 50 } 51 if len(sentence) < 6 { 52 return fix, errInvalidNMEASentenceLength 53 } 54 typ := sentence[3:6] 55 switch typ { 56 case "GGA": 57 // https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm 58 fields := strings.Split(sentence, ",") 59 if len(fields) != 15 { 60 return fix, errInvalidGGASentence 61 } 62 63 fix.Time = findTime(fields[1]) 64 fix.Latitude = findLatitude(fields[2], fields[3]) 65 fix.Longitude = findLongitude(fields[4], fields[5]) 66 fix.Satellites = findSatellites(fields[7]) 67 fix.Altitude = findAltitude(fields[9]) 68 fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0) 69 70 return fix, nil 71 case "GLL": 72 // https://docs.novatel.com/OEM7/Content/Logs/GPGLL.htm 73 fields := strings.Split(sentence, ",") 74 if len(fields) != 8 { 75 return fix, errInvalidGLLSentence 76 } 77 78 fix.Latitude = findLatitude(fields[1], fields[2]) 79 fix.Longitude = findLongitude(fields[3], fields[4]) 80 fix.Time = findTime(fields[5]) 81 82 fix.Valid = (fields[6] == "A") 83 84 return fix, nil 85 case "RMC": 86 // https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm 87 fields := strings.Split(sentence, ",") 88 if len(fields) != 13 { 89 return fix, errInvalidRMCSentence 90 } 91 92 fix.Time = findTime(fields[1]) 93 fix.Valid = (fields[2] == "A") 94 fix.Latitude = findLatitude(fields[3], fields[4]) 95 fix.Longitude = findLongitude(fields[5], fields[6]) 96 fix.Speed = findSpeed(fields[7]) 97 fix.Heading = findHeading(fields[8]) 98 date := findDate(fields[9]) 99 fix.Time = fix.Time.AddDate(date.Year(), int(date.Month()), date.Day()) 100 101 return fix, nil 102 } 103 104 return fix, newGPSError(errUnknownNMEASentence, sentence, typ) 105 } 106 107 // findTime returns the time from an NMEA sentence: 108 // $--GGA,hhmmss.ss,,,,,,,,,,,,,*xx 109 func findTime(val string) time.Time { 110 if len(val) < 6 { 111 return time.Time{} 112 } 113 114 h, _ := strconv.ParseInt(val[0:2], 10, 8) 115 m, _ := strconv.ParseInt(val[2:4], 10, 8) 116 s, _ := strconv.ParseInt(val[4:6], 10, 8) 117 ms := int64(0) 118 if len(val) > 6 { 119 ms, _ = strconv.ParseInt(val[7:], 10, 16) 120 } 121 t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC) 122 123 return t 124 } 125 126 // findAltitude returns the altitude from an NMEA sentence: 127 // $--GGA,,,,,,,,,25.8,,,,,*63 128 func findAltitude(val string) int32 { 129 if len(val) > 0 { 130 var v, _ = strconv.ParseFloat(val, 32) 131 return int32(v) 132 } 133 return -99999 134 } 135 136 // findLatitude returns the Latitude from an NMEA sentence: 137 // $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh 138 func findLatitude(val, hemi string) float32 { 139 if len(val) > 8 { 140 dd := val[0:2] 141 mm := val[2:] 142 d, _ := strconv.ParseFloat(dd, 32) 143 m, _ := strconv.ParseFloat(mm, 32) 144 v := float32(d + (m / 60)) 145 if hemi == "S" { 146 v *= -1 147 } 148 return v 149 } 150 return 0.0 151 } 152 153 // findLongitude returns the longitude from an NMEA sentence: 154 // $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh 155 func findLongitude(val, hemi string) float32 { 156 if len(val) > 8 { 157 var ddd = val[0:3] 158 var mm = val[3:] 159 var d, _ = strconv.ParseFloat(ddd, 32) 160 var m, _ = strconv.ParseFloat(mm, 32) 161 var v = float32(d + (m / 60)) 162 if hemi == "W" { 163 v *= -1 164 } 165 return v 166 } 167 return 0.0 168 } 169 170 // findSatellites returns the satellites from an NMEA sentence: 171 // $--GGA,,,,,,,nn,,,,,,,*hh 172 func findSatellites(val string) (n int16) { 173 if len(val) > 0 { 174 var nn = val 175 var v, _ = strconv.ParseInt(nn, 10, 32) 176 n = int16(v) 177 return n 178 } 179 return 0 180 } 181 182 // findDate returns the date from an RMC NMEA sentence. 183 func findDate(val string) time.Time { 184 if len(val) < 6 { 185 return time.Time{} 186 } 187 188 d, _ := strconv.ParseInt(val[0:2], 10, 8) 189 m, _ := strconv.ParseInt(val[2:4], 10, 8) 190 y, _ := strconv.ParseInt(val[4:6], 10, 8) 191 t := time.Date(int(2000+y), time.Month(m), int(d), 0, 0, 0, 0, time.UTC) 192 193 return t 194 } 195 196 // findSpeed returns the speed from an RMC NMEA sentence. 197 func findSpeed(val string) float32 { 198 if len(val) > 0 { 199 var v, _ = strconv.ParseFloat(val, 32) 200 return float32(v) 201 } 202 return 0 203 } 204 205 // findHeading returns the speed from an RMC NMEA sentence. 206 func findHeading(val string) float32 { 207 if len(val) > 0 { 208 var v, _ = strconv.ParseFloat(val, 32) 209 return float32(v) 210 } 211 return 0 212 }