diff --git a/Adafruit_GPS.cpp b/Adafruit_GPS.cpp index f0748dc..dcc83da 100755 --- a/Adafruit_GPS.cpp +++ b/Adafruit_GPS.cpp @@ -41,34 +41,16 @@ static boolean strStartsWith(const char* str, const char* prefix); /**************************************************************************/ boolean Adafruit_GPS::parse(char *nmea) { // do checksum check + if(!check(nmea)) return false; + // passed the check, so there's a valid source in thisSource and a valid sentence in thisSentence - // first look if we even have one - char *ast = strchr(nmea,'*'); - if (ast != NULL) { - uint16_t sum = parseHex(*(ast+1)) * 16; - sum += parseHex(*(ast+2)); - // check checksum - char *p = strchr(nmea,'$'); - if(p == NULL) return false; - else{ - for (char *p1 = p+1; p1 < ast; p1++) { - sum ^= *p1; - } - if (sum != 0) { - // bad checksum :( - return false; - } - } - } else { - return false; - } // look for a few common sentences - char *p = nmea; + char *p = nmea; // Pointer to move through the sentence -- good parsers are non-destructive + p = strchr(p, ',')+1; // Skip to the character after the next comma, then check sentence. - if (strStartsWith(nmea, "$GPGGA") || strStartsWith(nmea, "$GNGGA")) { + if (!strcmp(thisSentence,"GGA")) { // found GGA // get time - p = strchr(p, ',')+1; parseTime(p); // parse out latitude @@ -118,13 +100,11 @@ boolean Adafruit_GPS::parse(char *nmea) { { geoidheight = atof(p); } - return true; - } + } - if (strStartsWith(nmea, "$GPRMC") || strStartsWith(nmea, "$GNRMC")) { + else if (!strcmp(thisSentence,"RMC")) { // found RMC // get time - p = strchr(p, ',')+1; parseTime(p); // fix or no fix @@ -166,13 +146,11 @@ boolean Adafruit_GPS::parse(char *nmea) { year = (fulldate % 100); lastDate = sentTime; } - return true; } - if (strStartsWith(nmea, "$GPGLL") || strStartsWith(nmea, "$GNGLL")) { + else if (!strcmp(thisSentence,"GLL")) { // found GLL // parse out latitude - p = strchr(p, ',')+1; parseLat(p); p = strchr(p, ',')+1; if(!parseLatDir(p)) return false; @@ -190,69 +168,138 @@ boolean Adafruit_GPS::parse(char *nmea) { // fix or no fix p = strchr(p, ',')+1; if(!parseFix(p)) return false; - - return true; } -if (strStartsWith(nmea, "$GPGSA")) { - // found GSA - // parse out Auto selection, but ignore them - p = strchr(p, ',')+1; - // parse out 3d fixquality - p = strchr(p, ',')+1; - if (',' != *p) - { - fixquality_3d = atoi(p); + else if (!strcmp(thisSentence,"GSA")) { + // found GSA + // parse out Auto selection, but ignore them + // parse out 3d fixquality + p = strchr(p, ',')+1; + if (',' != *p) + { + fixquality_3d = atoi(p); + } + // skip 12 Satellite PDNs without interpreting them + for(int i = 0;i < 12;i++) p = strchr(p, ',')+1; + + //parse out PDOP + p = strchr(p, ',')+1; + if (',' != *p) + { + PDOP = atof(p); + } + // parse out HDOP, we also parse this from the GGA sentence. Chipset should report the same for both + p = strchr(p, ',')+1; + if (',' != *p) + { + HDOP = atof(p); + } + // parse out VDOP + p = strchr(p, ',')+1; + if (',' != *p) + { + VDOP = atof(p); + } } - // parse out Satellite PDNs, but ignore them - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - p = strchr(p, ',')+1; - - //parse out PDOP - p = strchr(p, ',')+1; - if (',' != *p) - { - PDOP = atof(p); - } - // parse out HDOP, we also parse this from the GGA sentence. Chipset should report the same for both - p = strchr(p, ',')+1; - if (',' != *p) - { - HDOP = atof(p); - } - // parse out VDOP - p = strchr(p, ',')+1; - if (',' != *p) - { - VDOP = atof(p); - } - return true; -} // we dont parse the remaining, yet! - return false; + else return false; + + // 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 != '$') return false; // doesn't start with $ + 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 Add *CS where CS is the two character hex checksum for all but + the first character in the string. The checksum is the result of an + exclusive or of all the characters in the string. Also useful if you + are creating new PMTK strings for controlling a GPS module and need a + checksum added. + @param buff Pointer to the string, which must be long enough + @return none +*/ +/**************************************************************************/ +void Adafruit_GPS::addChecksum(char *buff){ + char cs = 0; + int i = 1; + while(buff[i]){ + cs ^= buff[i]; + i++; + } + sprintf(buff,"%s*%02X",buff,cs); } /**************************************************************************/ @@ -911,20 +958,3 @@ boolean Adafruit_GPS::wakeup(void) { return false; // Returns false if not in standby mode, nothing to wakeup } } - -/**************************************************************************/ -/*! - @brief Checks whether a string starts with a specified prefix - @param str Pointer to a string - @param prefix Pointer to the prefix - @return True if str starts with prefix, false otherwise -*/ -/**************************************************************************/ -static boolean strStartsWith(const char* str, const char* prefix) -{ - while (*prefix) { - if (*prefix++ != *str++) - return false; - } - return true; -} diff --git a/Adafruit_GPS.h b/Adafruit_GPS.h index d823501..445f53f 100644 --- a/Adafruit_GPS.h +++ b/Adafruit_GPS.h @@ -31,6 +31,8 @@ #define GPS_MAX_I2C_TRANSFER 32 ///< The max number of bytes we'll try to read at once #define GPS_MAX_SPI_TRANSFER 100 ///< The max number of bytes we'll try to read at once #define MAXLINELENGTH 120 ///< how long are max NMEA lines to parse? +#define NMEA_MAX_SENTENCE_ID 20 // maximum length of a sentence ID name, including terminating 0 +#define NMEA_MAX_SOURCE_ID 3 // maximum length of a source ID name, including terminating 0 #include "Arduino.h" @@ -101,6 +103,15 @@ #define MAXWAITSENTENCE 10 ///< how long to wait when we're looking for a response /**************************************************************************/ +typedef enum { + NMEA_BAD = 0, // passed none of the checks + NMEA_HAS_DOLLAR = 1, // has a dollar sign in the first position + NMEA_HAS_CHECKSUM = 2, // has a valid checksum at the end + NMEA_HAS_NAME = 4, // there is a token after the $ followed by a comma + NMEA_HAS_SOURCE = 10, // has a recognized source ID + NMEA_HAS_SENTENCE = 20, // has a recognized sentence ID + NMEA_HAS_SENTENCE_P = 40 // has a recognized parseable sentence ID +} nmea_check_t; /**************************************************************************/ /*! @@ -131,7 +142,9 @@ class Adafruit_GPS : public Print{ size_t write(uint8_t); size_t available(void); + boolean check(char *nmea); boolean parse(char *); + void addChecksum(char *buff); float secondsSinceFix(); float secondsSinceTime(); float secondsSinceDate(); @@ -139,6 +152,12 @@ class Adafruit_GPS : public Print{ boolean wakeup(void); boolean standby(void); + int thisCheck = 0; // the results of the check on the current sentence + char thisSource[NMEA_MAX_SOURCE_ID] = {0}; // the first two letters of the current sentence, e.g. WI, GP + char thisSentence[NMEA_MAX_SENTENCE_ID] = {0}; // the next three letters of the current sentence, e.g. GLL, RMC + char lastSource[NMEA_MAX_SOURCE_ID] = {0}; // same for last correctly parsed sentence + char lastSentence[NMEA_MAX_SENTENCE_ID] = {0}; + uint8_t hour; ///< GMT hours uint8_t minute; ///< GMT minutes uint8_t seconds; ///< GMT seconds @@ -191,19 +210,26 @@ class Adafruit_GPS : public Print{ uint8_t LOCUS_percent; ///< Log life used percentage private: + const char * tokenOnList(char *token, const char **list); void parseTime(char *); void parseLat(char *); boolean parseLatDir(char *); void parseLon(char *); boolean parseLonDir(char *); boolean parseFix(char *); + // used by check() for validity tests, room for future expansion + const char *sources[5] = {"II", "WI", "GP", "GN", "ZZZ"}; + const char *sentences_parsed[5] = {"GGA", "GLL", "GSA", "RMC", "ZZZ"}; + const char *sentences_known[1] = {"ZZZ"}; + // Make all of these times far in the past by setting them near the middle of the // millis() range. Timing assumes that sentences are parsed promptly. - uint32_t lastFix = 2000000000L; // millis() when last fix received + uint32_t lastUpdate = 2000000000L; // millis() when last full sentence successfully parsed + uint32_t lastFix = 2000000000L; // millis() when last fix received uint32_t lastTime = 2000000000L; // millis() when last time received uint32_t lastDate = 2000000000L; // millis() when last date received uint32_t recvdTime = 2000000000L; // millis() when last full sentence received - uint32_t sentTime = 2000000000L; // millis() when first character of last full sentence received + uint32_t sentTime = 2000000000L; // millis() when first character of last full sentence received boolean paused; uint8_t parseResponse(char *response);