/**************************************************************************/ /*! @file NMEA_parse.cpp @mainpage Adafruit Ultimate GPS Breakout @section intro Introduction This is the Adafruit GPS library - the ultimate GPS library for the ultimate GPS module! Tested and works great with the Adafruit Ultimate GPS module using MTK33x9 chipset ------> http://www.adafruit.com/products/746 Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! @section author Author Written by Limor Fried/Ladyada for Adafruit Industries. @section license License BSD license, check license.txt for more information All text above must be included in any redistribution */ /**************************************************************************/ #include /**************************************************************************/ /*! @brief Parse a standard NMEA string and update the relevant variables. Sentences start with a $, then a two character source identifier, then a three character sentence identifier that defines the format, then a comma and more comma separated fields defined by the sentence name. There are many sentences listed that are not yet supported, including proprietary sentences that start with P, like the $PMTK commands to the GPS modules. See the build() function and http://fort21.ru/download/NMEAdescription.pdf for sentence descriptions. Encapsulated data sentences are supported by NMEA-183, and start with ! instead of $. https://gpsd.gitlab.io/gpsd/AIVDM.html provides details about encapsulated data sentences used in AIS. parse() permits, but does not require Carriage Return and Line Feed at the end of sentences. The end of the sentence is recognized by the * for the checksum. parse() will not recognize a sentence without a valid checksum. NMEA_EXTENSIONS must be defined in order to parse more than basic GPS module sentences. @param nmea Pointer to the NMEA string @return True if successfully parsed, false if fails check or parsing */ /**************************************************************************/ bool Adafruit_GPS::parse(char *nmea) { if (!check(nmea)) return false; // passed the check, so there's a valid source in thisSource and a valid // sentence in thisSentence char *p = nmea; // Pointer to move through the sentence -- good parsers are // non-destructive p = strchr(p, ',') + 1; // Skip to char after the next comma, then check. // This may look inefficient, but an M0 will get down the list in about 1 us / // strcmp()! Put the GPS sentences from Adafruit_GPS at the top to make // pruning excess code easier. Otherwise, keep them alphabetical for ease of // reading. if (!strcmp(thisSentence, "GGA")) { //************************************GGA // Adafruit from Actisense NGW-1 from SH CP150C parseTime(p); p = strchr(p, ',') + 1; // parse time with specialized function // parse out both latitude and direction, then go to next field, or fail if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat)) newDataValue(NMEA_LAT, latitudeDegrees); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; // parse out both longitude and direction, then go to next field, or fail if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon)) newDataValue(NMEA_LON, longitudeDegrees); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) { // if it's a , (or a * at end of sentence) the value is // not included fixquality = atoi(p); // needs additional processing if (fixquality > 0) { fix = true; lastFix = sentTime; } else fix = false; } p = strchr(p, ',') + 1; // then move on to the next // Most can just be parsed with atoi() or atof(), then move on to the next. if (!isEmpty(p)) satellites = atoi(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_HDOP, HDOP = atof(p)); p = strchr(p, ',') + 1; if (!isEmpty(p)) altitude = atof(p); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; // skip the units if (!isEmpty(p)) geoidheight = atof(p); // skip the rest } else if (!strcmp(thisSentence, "RMC")) { //*****************************RMC // in Adafruit from Actisense NGW-1 from SH CP150C parseTime(p); p = strchr(p, ',') + 1; parseFix(p); p = strchr(p, ',') + 1; // parse out both latitude and direction, then go to next field, or fail if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat)) newDataValue(NMEA_LAT, latitudeDegrees); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; // parse out both longitude and direction, then go to next field, or fail if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon)) newDataValue(NMEA_LON, longitudeDegrees); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_SOG, speed = atof(p)); p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_COG, angle = atof(p)); p = strchr(p, ',') + 1; if (!isEmpty(p)) { uint32_t fulldate = atof(p); day = fulldate / 10000; month = (fulldate % 10000) / 100; year = (fulldate % 100); lastDate = sentTime; } // skip the rest } else if (!strcmp(thisSentence, "GLL")) { //*****************************GLL // in Adafruit from Actisense NGW-1 from SH CP150C // parse out both latitude and direction, then go to next field, or fail if (parseCoord(p, &latitudeDegrees, &latitude, &latitude_fixed, &lat)) newDataValue(NMEA_LAT, latitudeDegrees); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; // parse out both longitude and direction, then go to next field, or fail if (parseCoord(p, &longitudeDegrees, &longitude, &longitude_fixed, &lon)) newDataValue(NMEA_LON, longitudeDegrees); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; parseTime(p); p = strchr(p, ',') + 1; parseFix(p); // skip the rest } else if (!strcmp(thisSentence, "GSA")) { //*****************************GSA // in Adafruit from Actisense NGW-1 p = strchr(p, ',') + 1; // skip selection mode if (!isEmpty(p)) fixquality_3d = atoi(p); p = strchr(p, ',') + 1; // skip 12 Satellite PDNs without interpreting them for (int i = 0; i < 12; i++) p = strchr(p, ',') + 1; if (!isEmpty(p)) PDOP = atof(p); p = strchr(p, ',') + 1; // parse out HDOP, we also parse this from the GGA sentence. Chipset should // report the same for both if (!isEmpty(p)) newDataValue(NMEA_HDOP, HDOP = atof(p)); p = strchr(p, ',') + 1; if (!isEmpty(p)) VDOP = atof(p); // last before checksum } #ifdef NMEA_EXTENSIONS // Sentences not required for basic GPS functionality else if (!strcmp(thisSentence, "APB")) { //*******************************APB // from Actisense NGW-1 from SH CP150C return false; } else if (!strcmp(thisSentence, "DBT")) { //*****************************DBT // from Actisense NGW-1 // feet, metres, fathoms below transducer coerced to water depth from // surface in metres if (!isEmpty(p)) newDataValue(NMEA_DEPTH, atof(p) * 0.3048 + depthToTransducer); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_DEPTH, atof(p) + depthToTransducer); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_DEPTH, atof(p) * 6 * 0.3048 + depthToTransducer); } else if (!strcmp(thisSentence, "DPT")) { //*****************************DPT // from Actisense NGW-1 return false; } else if (!strcmp(thisSentence, "GSV")) { //*****************************GSV // from Actisense NGW-1 return false; } else if (!strcmp(thisSentence, "HDG")) { //*****************************HDG // from Actisense NGW-1 from SH CP150C return false; } else if (!strcmp(thisSentence, "HDM")) { //*****************************HDM if (!isEmpty(p)) newDataValue(NMEA_HDG, atof(p)); // skip the rest } else if (!strcmp(thisSentence, "HDT")) { //*****************************HDT if (!isEmpty(p)) newDataValue(NMEA_HDT, atof(p)); // skip the rest } else if (!strcmp(thisSentence, "MDA")) { //*****************************MDA // from Actisense NGW-1 if (!isEmpty(p)) newDataValue(NMEA_BAROMETER, atof(p) * 3386.39); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_BAROMETER, atof(p) * 100000); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; nmea_float_t T = 100000.; char u = 'C'; if (!isEmpty(p)) T = atof(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) u = *p; p = strchr(p, ',') + 1; if (u != 'C') { T = (T - 32) / 1.8; u = 'C'; } // coerce to C if (T < 1000) newDataValue(NMEA_TEMPERATURE_AIR, T); T = 100000.; u = 'C'; if (!isEmpty(p)) T = atof(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) u = *p; p = strchr(p, ',') + 1; if (u != 'C') { T = (T - 32) / 1.8; u = 'C'; } if (T < 1000) newDataValue(NMEA_TEMPERATURE_WATER, T); if (!isEmpty(p)) newDataValue(NMEA_HUMIDITY, atof(p)); // skip the rest } else if (!strcmp(thisSentence, "MTW")) { //*****************************MTW nmea_float_t T = 100000.; char u = 'C'; if (!isEmpty(p)) T = atof(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) u = *p; // last before checksum if (u != 'C') { T = (T - 32) / 1.8; u = 'C'; } if (T < 1000) newDataValue(NMEA_TEMPERATURE_WATER, T); } else if (!strcmp(thisSentence, "MWD")) { //*****************************MWD // from Actisense NGW-1 return false; } else if (!strcmp(thisSentence, "MWV")) { //*****************************MWV // from Actisense NGW-1 nmea_float_t ang = 100000.; char ref = 'T'; if (!isEmpty(p)) ang = atof(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) ref = *p; p = strchr(p, ',') + 1; nmea_float_t spd = 100000.; if (!isEmpty(p)) spd = atof(p); p = strchr(p, ',') + 1; char units = 'N'; if (!isEmpty(p)) units = *p; p = strchr(p, ',') + 1; char stat = 'A'; if (!isEmpty(p)) stat = *p; // last before checksum if (units == 'K') { spd /= 1.6; units = 'M'; } if (units == 'M') { spd *= 5280. / 6000.; units = 'N'; } if (ang > 180.) ang -= 360.; if (ref == 'R') { if (ang < 1000. && stat == 'A') newDataValue(NMEA_AWA, ang); if (spd < 1000. && stat == 'A') newDataValue(NMEA_AWS, spd); } else { if (ang < 1000. && stat == 'A') newDataValue(NMEA_TWA, ang); if (spd < 1000. && stat == 'A') newDataValue(NMEA_TWS, spd); } } else if (!strcmp(thisSentence, "RMB")) { //*****************************RMB // from Actisense NGW-1 from SH CP150C p = strchr(p, ',') + 1; // skip status nmea_float_t xte = 100000.; char xteDir = 'X'; if (!isEmpty(p)) xte = atof(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) xteDir = *p; p = strchr(p, ',') + 1; if (xte < 10000. && xteDir != 'X') { if (xteDir == 'L') xte *= -1.; newDataValue(NMEA_XTE, xte); } if (!isEmpty(p)) parseStr(toID, p, NMEA_MAX_WP_ID); p = strchr(p, ',') + 1; if (!isEmpty(p)) parseStr(fromID, p, NMEA_MAX_WP_ID); p = strchr(p, ',') + 1; nmea_float_t latitudeWP = 0; // All the same position data for the next way point nmea_float_t longitudeWP = 0; int32_t latitude_fixedWP = 0; int32_t longitude_fixedWP = 0; nmea_float_t latitudeDegreesWP = 0; nmea_float_t longitudeDegreesWP = 0; char latWP = 'N'; char lonWP = 'W'; // parse out both latitude and direction for WayPoint, then go to next // field, or fail if (!isEmpty(p)) { if (!parseCoord(p, &latitudeDegreesWP, &latitudeWP, &latitude_fixedWP, &latWP)) return false; else newDataValue(NMEA_LATWP, latitudeDegreesWP); } p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; // parse out both longitude and direction for WayPoint, then go to next // field, or fail if (!isEmpty(p)) { if (!parseCoord(p, &longitudeDegreesWP, &longitudeWP, &longitude_fixedWP, &lonWP)) return false; else newDataValue(NMEA_LONWP, longitudeDegreesWP); } p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_DISTWP, atof(p)); p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_COGWP, atof(p)); p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_VMGWP, atof(p)); // skip arrival flag } else if (!strcmp(thisSentence, "ROT")) { //*****************************ROT return false; } else if (!strcmp(thisSentence, "RPM")) { //*****************************RPM return false; } else if (!strcmp(thisSentence, "RSA")) { //*****************************RSA // from Actisense NGW-1 return false; } else if (!strcmp(thisSentence, "TXT")) { //*****************************TXT if (!isEmpty(p)) txtTot = atoi(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) txtN = atoi(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) txtID = atoi(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) parseStr(txtTXT, p, 61); // copy the text to NMEA TXT max of 61 characters } else if (!strcmp(thisSentence, "VDR")) { //*****************************VDR // from Actisense NGW-1 return false; } else if (!strcmp(thisSentence, "VHW")) { //*****************************VHW // from Actisense NGW-1 if (!isEmpty(p)) newDataValue(NMEA_HDT, atof(p)); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_HDG, atof(p)); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_VTW, atof(p)); // skip the other units } else if (!strcmp(thisSentence, "VLW")) { //*****************************VLW // from Actisense NGW-1 if (!isEmpty(p)) newDataValue(NMEA_LOG, atof(p)); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) newDataValue(NMEA_LOGR, atof(p)); // skip the other units } else if (!strcmp(thisSentence, "VPW")) { //*****************************VPW // knots, metres/s coerced to knots nmea_float_t vmg = 100000.; if (!isEmpty(p)) vmg = atof(p); p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; if (!isEmpty(p)) vmg = atof(p) * 0.3048 * 3600. / 6000.; // skip units if (vmg < 1000.) newDataValue(NMEA_VMG, vmg); } else if (!strcmp(thisSentence, "VTG")) { //*****************************VTG // from Actisense NGW-1 from SH CP150C return false; } else if (!strcmp(thisSentence, "VWR")) { //*****************************VWR // from Actisense NGW-1 nmea_float_t ang = 1000.; if (!isEmpty(p)) ang = atof(p); p = strchr(p, ',') + 1; char ref = ' '; if (!isEmpty(p)) ref = *p; p = strchr(p, ',') + 1; if (ref == 'L') ang *= -1; if (ang < 1000.) newDataValue(NMEA_AWA, ang); nmea_float_t ws = 0.0; char units = 'X'; if (!isEmpty(p)) ws = atof(p); p = strchr(p, ',') + 1; // knots if (!isEmpty(p)) units = *p; p = strchr(p, ',') + 1; if (!isEmpty(p)) ws = atof(p); p = strchr(p, ',') + 1; // meters / second if (!isEmpty(p)) units = *p; p = strchr(p, ',') + 1; // M if (!isEmpty(p)) ws = atof(p); p = strchr(p, ',') + 1; // kilometers / hour can be converted back to knots if (!isEmpty(p)) units = *p; // last before checksum if (units == 'M') { ws *= 3.6; units = 'K'; } // convert m/s to km/h if (units == 'K') { ws /= 1.6; units = 'M'; } // convert km/h to miles / h if (units == 'M') { ws *= 5280. / 6000.; units = 'N'; } // convert miles / hr to knots if (units == 'N') newDataValue(NMEA_AWS, ws); // store the final result } else if (!strcmp(thisSentence, "WCV")) { //*****************************WCV // from SH CP150C if (!isEmpty(p)) newDataValue(NMEA_VMGWP, atof(p)); // skip the rest } else if (!strcmp(thisSentence, "XTE")) { //*****************************XTE // from Actisense NGW-1 from SH CP150C p = strchr(p, ',') + 1; // skip status 1 p = strchr(p, ',') + 1; // skip status 2 nmea_float_t xte = 100000.; char xteDir = 'X'; if (!isEmpty(p)) xte = atof(p); p = strchr(p, ',') + 1; if (!isEmpty(p)) xteDir = *p; p = strchr(p, ',') + 1; if (xte < 10000. && xteDir != 'X') { if (xteDir == 'L') xte *= -1.; newDataValue(NMEA_XTE, xte); } // skip units } else if (!strcmp(thisSentence, "ZDA")) { //*****************************ZDA // from Actisense NGW-1 return false; } #endif // NMEA_EXTENSIONS else { return false; // didn't find the required sentence definition } // Record the successful parsing of where the last data came from and when strcpy(lastSource, thisSource); strcpy(lastSentence, thisSentence); lastUpdate = millis(); return true; } /**************************************************************************/ /*! @brief Check an NMEA string for basic format, valid source ID and valid and valid sentence ID. Update the values of thisCheck, thisSource and thisSentence. @param nmea Pointer to the NMEA string @return True if well formed, false if it has problems */ /**************************************************************************/ boolean Adafruit_GPS::check(char *nmea) { thisCheck = 0; // new check if (*nmea != '$' && *nmea != '!') return false; // doesn't start with $ or ! else thisCheck += NMEA_HAS_DOLLAR; // do checksum check -- first look if we even have one -- ignore all but last // * char *ast = nmea; // not strchr(nmea,'*'); for first * while (*ast) ast++; // go to the end while (*ast != '*' && ast > nmea) ast--; // then back to * if it's there if (*ast != '*') return false; // there is no asterisk else { uint16_t sum = parseHex(*(ast + 1)) * 16; // extract checksum sum += parseHex(*(ast + 2)); char *p = nmea; // check checksum for (char *p1 = p + 1; p1 < ast; p1++) sum ^= *p1; if (sum != 0) return false; // bad checksum :( else thisCheck += NMEA_HAS_CHECKSUM; } // extract source of variable length char *p = nmea + 1; const char *src = tokenOnList(p, sources); if (src) { strcpy(thisSource, src); thisCheck += NMEA_HAS_SOURCE; } else return false; p += strlen(src); // extract sentence id and check if parsed const char *snc = tokenOnList(p, sentences_parsed); if (snc) { strcpy(thisSentence, snc); thisCheck += NMEA_HAS_SENTENCE_P + NMEA_HAS_SENTENCE; } else { // check if known snc = tokenOnList(p, sentences_known); if (snc) { strcpy(thisSentence, snc); thisCheck += NMEA_HAS_SENTENCE; return false; } } return true; // passed all the tests } /**************************************************************************/ /*! @brief Check if a token at the start of a string is on a list. @param token Pointer to the string @param list A list of strings, with the final entry starting "ZZ" @return Pointer to the found token, or NULL if it fails */ /**************************************************************************/ const char *Adafruit_GPS::tokenOnList(char *token, const char **list) { int i = 0; // index in the list while (strncmp(list[i], "ZZ", 2) && i < 1000) { // stop at terminator and don't crash without it // test for a match on the sentence name if (!strncmp((const char *)list[i], (const char *)token, strlen(list[i]))) return list[i]; i++; } return NULL; // couldn't find a match } /**************************************************************************/ /*! @brief Check if an NMEA string is valid and is on a list, perhaps to decide if it should be passed to a particular NMEA device. @param nmea Pointer to the NMEA string @param list A list of strings, with the final entry "ZZ" @return True if on the list, false if it fails check or is not on the list */ /**************************************************************************/ bool Adafruit_GPS::onList(char *nmea, const char **list) { if (!check(nmea)) // sets thisSentence if valid return false; // not a valid sentence // stop at terminator with first two letters ZZ and don't crash without it for (int i = 0; strncmp(list[i], "ZZ", 2) && i < 1000; i++) { // test for a match on the sentence name if (!strcmp((const char *)list[i], (const char *)thisSentence)) return true; } return false; // couldn't find a match } /**************************************************************************/ /*! @brief Parse a part of an NMEA string for lat or lon angle and direction. Works for either DDMM.mmmm,N (latitude) or DDDMM.mmmm,W (longitude) format. Insensitive to number of decimal places present. Only fills the variables if it succeeds and the variable pointer is not NULL. This allows calling to fill only the variables of interest. Does rudimentary validation on angle range. Supersedes private functions parseLat(), parseLon(), parseLatDir(), parseLonDir(), all previously called from parse(). @param pStart Pointer to the location of the token in the NMEA string @param angle Pointer to the angle to fill with value in degrees/minutes as received from the GPS (DDDMM.MMMM), unsigned @param angle_fixed Pointer to the fix point version latitude in decimal degrees * 10000000, signed @param angleDegrees Pointer to the angle to fill with decimal degrees, signed. As actual double on SAMD, etc. resolution is better than the fixed point version. @param dir Pointer to character to fill the direction N/S/E/W @return true if successful, false if failed or no value */ /**************************************************************************/ bool Adafruit_GPS::parseCoord(char *pStart, nmea_float_t *angleDegrees, nmea_float_t *angle, int32_t *angle_fixed, char *dir) { char *p = pStart; if (!isEmpty(p)) { // get the number in DDDMM.mmmm format and break into components char degreebuff[10]; char *e = strchr(p, '.'); if (e == NULL || e - p > 6) return false; // no decimal point in range strncpy(degreebuff, p, e - p); // get DDDMM long dddmm = atol(degreebuff); long degrees = (dddmm / 100); // truncate the minutes long minutes = dddmm - degrees * 100; // remove the degrees p = e; // start from the decimal point nmea_float_t decminutes = atof(e); // the fraction after the decimal point p = strchr(p, ',') + 1; // go to the next field // get the NSEW direction as a character char nsew = 'X'; if (!isEmpty(p)) nsew = *p; // field is not empty else return false; // no direction provided // set the various numerical formats to their values long fixed = degrees * 10000000 + (minutes * 10000000) / 60 + (decminutes * 10000000) / 60; nmea_float_t ang = degrees * 100 + minutes + decminutes; nmea_float_t deg = fixed / (nmea_float_t)10000000.; if (nsew == 'S' || nsew == 'W') { // fixed and deg are signed, but DDDMM.mmmm is not fixed = -fixed; deg = -deg; } // reject directions that are not NSEW if (nsew != 'N' && nsew != 'S' && nsew != 'E' && nsew != 'W') return false; // reject angles that are out of range if (nsew == 'N' || nsew == 'S') if(abs(deg) > 90) return false; if(abs(deg) > 180) return false; // store in locations passed as args if (angle != NULL) *angle = ang; if (angle_fixed != NULL) *angle_fixed = fixed; if (angleDegrees != NULL) *angleDegrees = deg; if (dir != NULL) *dir = nsew; } else return false; // no number return true; } /**************************************************************************/ /*! @brief Parse a string token from pointer p to the next comma, asterisk or end of string. @param buff Pointer to the buffer to store the string in @param p Pointer into a string @param n Max permitted size of string including terminating 0 @return Pointer to the string buffer */ /**************************************************************************/ char *Adafruit_GPS::parseStr(char *buff, char *p, int n) { char *e = strchr(p, ','); int len = 0; if (e) { len = min(e - p, n - 1); strncpy(buff, p, len); // copy up to the comma buff[len] = 0; } else { e = strchr(p, '*'); if (e) { len = min(e - p, n - 1); strncpy(buff, p, len); // or up to the * buff[e - p] = 0; } else { len = min((int)strlen(p), n - 1); strncpy(buff, p, len); // or to the end or max capacity } } return buff; } /**************************************************************************/ /*! @brief Parse a part of an NMEA string for time. Independent of number of decimal places after the '.' @param p Pointer to the location of the token in the NMEA string @return true if successful, false otherwise */ /**************************************************************************/ bool Adafruit_GPS::parseTime(char *p) { if (!isEmpty(p)) { // get time uint32_t time = atol(p); hour = time / 10000; minute = (time % 10000) / 100; seconds = (time % 100); p = strchr(p, '.'); milliseconds = atof(p) * 1000; lastTime = sentTime; return true; } return false; } /**************************************************************************/ /*! @brief Parse a part of an NMEA string for whether there is a fix @param p Pointer to the location of the token in the NMEA string @return True if we parsed it, false if it has invalid data */ /**************************************************************************/ boolean Adafruit_GPS::parseFix(char *p) { if (!isEmpty(p)) { if (p[0] == 'A') { fix = true; lastFix = sentTime; } else if (p[0] == 'V') fix = false; else return false; return true; } return false; } /**************************************************************************/ /*! @brief Is the field empty, or should we try conversion? Won't work for a text field that starts with an asterisk or a comma, but that probably violates the NMEA-183 standard. @param pStart Pointer to the location of the token in the NMEA string @return true if empty field, false if something there */ /**************************************************************************/ bool Adafruit_GPS::isEmpty(char *pStart) { if (',' != *pStart && '*' != *pStart && pStart != NULL) return false; else return true; } /**************************************************************************/ /*! @brief Parse a hex character and return the appropriate decimal value @param c Hex character, e.g. '0' or 'B' @return Integer value of the hex character. Returns 0 if c is not a proper character */ /**************************************************************************/ // read a Hex value and return the decimal equivalent uint8_t Adafruit_GPS::parseHex(char c) { if (c < '0') return 0; if (c <= '9') return c - '0'; if (c < 'A') return 0; if (c <= 'F') return (c - 'A') + 10; // if (c > 'F') return 0; }