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  }