From f02d93b7499b30736f5264e37b3a100991fa3732 Mon Sep 17 00:00:00 2001 From: Rick Sellens Date: Sat, 18 Jan 2020 09:07:15 -0500 Subject: [PATCH] Redo Pull Request 108 Created new fork in hopes of making things mergeable --- Adafruit_GPS.cpp | 243 +++++++++++++++++++++++++++++++++++++++++++---- Adafruit_GPS.h | 29 +++++- 2 files changed, 253 insertions(+), 19 deletions(-) diff --git a/Adafruit_GPS.cpp b/Adafruit_GPS.cpp index 38c8f27..390550d 100755 --- a/Adafruit_GPS.cpp +++ b/Adafruit_GPS.cpp @@ -73,7 +73,7 @@ boolean Adafruit_GPS::parse(char *nmea) { return false; p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { fixquality = atoi(p); if (fixquality > 0) { fix = true; @@ -83,23 +83,23 @@ boolean Adafruit_GPS::parse(char *nmea) { } p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { satellites = atoi(p); } p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { HDOP = atof(p); } p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { altitude = atof(p); } p = strchr(p, ',') + 1; p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { geoidheight = atof(p); } } @@ -130,18 +130,18 @@ boolean Adafruit_GPS::parse(char *nmea) { // speed p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { speed = atof(p); } // angle p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { angle = atof(p); } p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { uint32_t fulldate = atof(p); day = fulldate / 10000; month = (fulldate % 10000) / 100; @@ -180,7 +180,7 @@ boolean Adafruit_GPS::parse(char *nmea) { // parse out Auto selection, but ignore them // parse out 3d fixquality p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { fixquality_3d = atoi(p); } // skip 12 Satellite PDNs without interpreting them @@ -189,22 +189,31 @@ boolean Adafruit_GPS::parse(char *nmea) { // parse out PDOP p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(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) { + if (!isEmpty(p)) { HDOP = atof(p); } // parse out VDOP p = strchr(p, ',') + 1; - if (',' != *p) { + if (!isEmpty(p)) { VDOP = atof(p); } } +#ifdef NMEA_EXTENSIONS //********************************************Sentences not required for basic GPS functionality + 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 + } +#endif // NMEA_EXTENSIONS + // we dont parse the remaining, yet! else return false; @@ -296,6 +305,51 @@ const char *Adafruit_GPS::tokenOnList(char *token, const char **list) { return NULL; // couldn't find a match } +/**************************************************************************/ +/*! + @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 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 Add *CS where CS is the two character hex checksum for all but @@ -345,7 +399,7 @@ void Adafruit_GPS::parseLat(char *p) { int32_t degree; long minutes; char degreebuff[10]; - if (',' != *p) { + if (!isEmpty(p)) { strncpy(degreebuff, p, 2); p += 2; degreebuff[2] = '\0'; @@ -394,7 +448,7 @@ void Adafruit_GPS::parseLon(char *p) { int32_t degree; long minutes; char degreebuff[10]; - if (',' != *p) { + if (!isEmpty(p)) { strncpy(degreebuff, p, 3); p += 3; degreebuff[3] = '\0'; @@ -419,7 +473,7 @@ void Adafruit_GPS::parseLon(char *p) { */ /**************************************************************************/ boolean Adafruit_GPS::parseLonDir(char *p) { - if (',' != *p) { + if (!isEmpty(p)) { if (p[0] == 'W') { lon = 'W'; longitudeDegrees *= -1.0; @@ -981,4 +1035,161 @@ static boolean strStartsWith(const char *str, const char *prefix) { return false; } return true; -} \ No newline at end of file +} + +#ifdef NMEA_EXTENSIONS +/**************************************************************************/ +/*! + @brief Fakes time of receipt of a sentence. Use between build() and parse() + to make the timing look like the sentence arrived from the GPS. +*/ +/**************************************************************************/ +void Adafruit_GPS::resetSentTime(){ + sentTime = millis(); +} + +/**************************************************************************/ +/*! + @brief Build an NMEA sentence string based on the relevant variables. + Sentences start with a $, then a two character source identifier, then + a three character sentence name 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. Most of these sentence + definitions were found at http://fort21.ru/download/NMEAdescription.pdf + + build() will work with other lengths for source and sentence to allow + extension to building proprietary sentences like $PMTK220,100*2F. + + build() will not work properly in an environment that does not support + the %f floating point formatter in sprintf(), and will return NULL. + + build() adds Carriage Return and Line Feed to sentences to conform to + NMEA-183, so send your output with a print, not a println. + + Some of the data in these test sentences may be arbitrary, e.g. for the + TXT sentence which has a more complicated protocol for multiple lines + sent as a message set. Also, the data in the class variables are presumed + to be valid, so these sentences may contain values that are stale, or + the result of initialization rather than measurement. + + @param nmea Pointer to the NMEA string buffer. Must be big enough to + hold the sentence. No guarantee what will be in it if the + building of the sentence fails. + @param thisSource Pointer to the source name string (2 upper case) + @param thisSentence Pointer to the sentence name string (3 upper case) + @param ref Reference for the sentence, usually relative (R) or true (T) + @return Pointer to sentence if successful, NULL if fails +*/ +/**************************************************************************/ +char * Adafruit_GPS::build(char *nmea, const char *thisSource, const char *thisSentence, char ref) { + sprintf(nmea,"%6.2f",123.45); // fail if sprintf() doesn't handle floats + if(strcmp(nmea,"123.45")) return NULL; + *nmea = '$'; + char *p = nmea + 1; // Pointer to move through the sentence + strncpy(p,thisSource,strlen(thisSource)); p += strlen(thisSource); + strncpy(p,thisSentence,strlen(thisSentence)); p += strlen(thisSentence); + *p = ','; p += 1; //Now $XXSSS, and need to add argument fields + // 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 + //GGA Global Positioning System Fix Data. Time, Position and fix related data for a GPS receiver + // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + // | | | | | | | | | | | | | | | + //$--GGA,hhmmss.ss,ddmm.mm,a,dddmm.mm,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh + //1) Time (UTC) + //2) Latitude + //3) N or S (North or South) + //4) Longitude + //5) E or W (East or West) + //6) GPS Quality Indicator, 0 - fix not available, 1 - GPS fix, 2 - Differential GPS fix + //7) Number of satellites in view, 00 - 12 + //8) Horizontal Dilution of precision + //9) Antenna Altitude above/below mean-sea-level (geoid) + //10) Units of antenna altitude, meters + //11) Geoidal separation, the difference between the WGS-84 earth + // ellipsoid and mean-sea-level (geoid), "-" means mean-sea-level below ellipsoid + //12) Units of geoidal separation, meters + //13) Age of differential GPS data, time in seconds since last SC104 + // type 1 or 9 update, null field when DGPS is not used + //14) Differential reference station ID, 0000-1023 + //15) Checksum + sprintf(p,"%09.2f,%09.4f,%c,%010.4f,%c,%d,%02d,%f,%f,M,%f,M,,",hour*10000L + minute*100L + seconds + milliseconds/1000., + latitude,lat,longitude,lon,fixquality,satellites,HDOP,altitude,geoidheight); + + } else if (!strcmp(thisSentence,"GLL")){ //********************************************GLL + //GLL Geographic Position – Latitude/Longitude + // 1 2 3 4 5 6 7 + // | | | | | | | + //$--GLL,llll.ll,a,yyyyy.yy,a,hhmmss.ss,A*hh + //1) Latitude ddmm.mm format + //2) N or S (North or South) + //3) Longitude dddmm.mm format + //4) E or W (East or West) + //5) Time (UTC) + //6) Status A - Data Valid, V - Data Invalid + //7) Checksum + sprintf(p,"%09.4f,%c,%010.4f,%c,%09.2f,A",latitude,lat,longitude,lon,hour*10000L + minute*100L + seconds + milliseconds/1000.); + + } else if (!strcmp(thisSentence,"GSA")){ //******************************************** + //GSA GPS DOP and active satellites + // 1 2 3 14 15 16 17 18 + // | | | | | | | | + //$--GSA,a,a,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x.x,x.x,x.x*hh + // 1) Selection mode + // 2) Mode + // 3) ID of 1st satellite used for fix + // 4) ID of 2nd satellite used for fix + // ... + //14) ID of 12th satellite used for fix + //15) PDOP in meters + //16) HDOP in meters + //17) VDOP in meters + //18) Checksum + return NULL; + + } else if (!strcmp(thisSentence,"RMC")){ //********************************************RMC + //RMC Recommended Minimum Navigation Information + // 12 + // 1 2 3 4 5 6 7 8 9 10 11 | + // | | | | | | | | | | | | + //$--RMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,xxxxxx,x.x,a*hh + // 1) Time (UTC) + // 2) Status, V = Navigation receiver warning + // 3) Latitude + // 4) N or S + // 5) Longitude + // 6) E or W + // 7) Speed over ground, knots + // 8) Track made good, degrees true + // 9) Date, ddmmyy + //10) Magnetic Variation, degrees + //11) E or W + //12) Checksum + sprintf(p,"%09.2f,A,%09.4f,%c,%010.4f,%c,%f,%f,%06d,%f,%c",hour*10000L + minute*100L + seconds + milliseconds/1000., + latitude,lat,longitude,lon,speed,angle,day*10000+month*100+year,magvariation,mag); + + } else if (!strcmp(thisSentence,"TXT")){ //********************************************TXT + // as mentioned in https://github.com/adafruit/Adafruit_GPS/issues/95 + //TXT Text Transmission + // 1 2 3 4 5 + // | | | | | + //$--TXT,xx,xx,xx,c--c*hh + //1) Total Number of Sentences 01-99 + //2) Sentence Number 01-99 + //3) Text Identifier 01-99 + //4) Text String, max 61 characters + //5) Checksum + sprintf(p,"01,01,23,This is the text of the sample message"); + + } else { + return NULL; // didn't find a match for the build request + } + + addChecksum(nmea); // Successful completion + sprintf(nmea,"%s\r\n",nmea); // Add Carriage Return and Line Feed to comply with NMEA-183 + return nmea; // return pointer to finished product +} + +#endif // NMEA_EXTENSIONS diff --git a/Adafruit_GPS.h b/Adafruit_GPS.h index 2b4f4da..19b1a5d 100644 --- a/Adafruit_GPS.h +++ b/Adafruit_GPS.h @@ -26,6 +26,15 @@ #ifndef _ADAFRUIT_GPS_H #define _ADAFRUIT_GPS_H +/**************************************************************************/ +/** + Comment out the definition of NMEA_EXTENSIONS to make the library use as + little memory as possible for GPS functionality only. The ARDUINO_ARCH_AVR + test should leave it out of any compilations for the UNO and similar. */ +#ifndef ARDUINO_ARCH_AVR +#define NMEA_EXTENSIONS ///< if defined will include more NMEA sentences +#endif + #define USE_SW_SERIAL ///< comment this out if you don't want to include ///< software serial in the library #define GPS_DEFAULT_I2C_ADDR \ @@ -232,9 +241,9 @@ public: ///< vertical position float PDOP; ///< Position Dilution of Precision - Complex maths derives a ///< simple, single number for each kind of DOP - char lat; ///< N/S - char lon; ///< E/W - char mag; ///< Magnetic variation direction + char lat = 'X'; ///< N/S + char lon = 'X'; ///< E/W + char mag = 'X'; ///< Magnetic variation direction boolean fix; ///< Have a fix? uint8_t fixquality; ///< Fix quality (0, 1, 2 = Invalid, GPS, DGPS) uint8_t fixquality_3d; ///< 3D fix quality (1, 3, 3 = Nofix, 2D fix, 3D fix) @@ -257,8 +266,22 @@ public: uint8_t LOCUS_status; ///< 0: Logging, 1: Stop logging uint8_t LOCUS_percent; ///< Log life used percentage +#ifdef NMEA_EXTENSIONS + // NMEA additional public functions + char * build(char *nmea, const char *thisSource, const char *thisSentence, char ref = 'R'); + void resetSentTime(); + + // NMEA additional public variables + char txtTXT[63] = {0}; ///< text content from most recent TXT sentence + int txtTot = 0; ///< total TXT sentences in group + int txtID = 0; ///< id of the text message + int txtN = 0; ///< the TXT sentence number +#endif // NMEA_EXTENSIONS + private: const char *tokenOnList(char *token, const char **list); + char * parseStr(char * buff, char *p, int n); + bool isEmpty(char *pStart); void parseTime(char *); void parseLat(char *); boolean parseLatDir(char *);