diff --git a/src/TinyGPS++.cpp b/src/TinyGPS++.cpp index e1ec777..1248f55 100644 --- a/src/TinyGPS++.cpp +++ b/src/TinyGPS++.cpp @@ -27,15 +27,11 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include -#define _GPRMCterm "GPRMC" -#define _GPGGAterm "GPGGA" -#define _GNRMCterm "GNRMC" -#define _GNGGAterm "GNGGA" - TinyGPSPlus::TinyGPSPlus() : parity(0) , isChecksumTerm(false) , curSentenceType(GPS_SENTENCE_OTHER) + , curSentenceSystem(GPS_SYSTEM_GPS) , curTermNumber(0) , curTermOffset(0) , sentenceHasFix(false) @@ -82,6 +78,7 @@ bool TinyGPSPlus::encode(char c) curTermNumber = curTermOffset = 0; parity = 0; curSentenceType = GPS_SENTENCE_OTHER; + curSentenceSystem = GPS_SYSTEM_GPS; isChecksumTerm = false; sentenceHasFix = false; return false; @@ -190,6 +187,15 @@ bool TinyGPSPlus::endOfTermHandler() satellites.commit(); hdop.commit(); break; + case GPS_SENTENCE_GPGSV: + satellitesStats.commit(); + break; + case GPS_SENTENCE_GPGSA: + if (!satellitesStats.snrDataPresent) { + satellitesStats.commit(); + } + hdop.commit(); + break; } // Commit all custom listeners of this sentence type @@ -209,12 +215,7 @@ bool TinyGPSPlus::endOfTermHandler() // the first term determines the sentence type if (curTermNumber == 0) { - if (!strcmp(term, _GPRMCterm) || !strcmp(term, _GNRMCterm)) - curSentenceType = GPS_SENTENCE_GPRMC; - else if (!strcmp(term, _GPGGAterm) || !strcmp(term, _GNGGAterm)) - curSentenceType = GPS_SENTENCE_GPGGA; - else - curSentenceType = GPS_SENTENCE_OTHER; + parseSentenceType(term); // Any custom candidates of this sentence type? for (customCandidates = customElts; customCandidates != NULL && strcmp(customCandidates->sentenceName, term) < 0; customCandidates = customCandidates->next); @@ -224,59 +225,98 @@ bool TinyGPSPlus::endOfTermHandler() return false; } - if (curSentenceType != GPS_SENTENCE_OTHER && term[0]) + if (curSentenceType != GPS_SENTENCE_OTHER && term[0]) { switch(COMBINE(curSentenceType, curTermNumber)) - { - case COMBINE(GPS_SENTENCE_GPRMC, 1): // Time in both sentences - case COMBINE(GPS_SENTENCE_GPGGA, 1): - time.setTime(term); - break; - case COMBINE(GPS_SENTENCE_GPRMC, 2): // GPRMC validity - sentenceHasFix = term[0] == 'A'; - break; - case COMBINE(GPS_SENTENCE_GPRMC, 3): // Latitude - case COMBINE(GPS_SENTENCE_GPGGA, 2): - location.setLatitude(term); - break; - case COMBINE(GPS_SENTENCE_GPRMC, 4): // N/S - case COMBINE(GPS_SENTENCE_GPGGA, 3): - location.rawNewLatData.negative = term[0] == 'S'; - break; - case COMBINE(GPS_SENTENCE_GPRMC, 5): // Longitude - case COMBINE(GPS_SENTENCE_GPGGA, 4): - location.setLongitude(term); - break; - case COMBINE(GPS_SENTENCE_GPRMC, 6): // E/W - case COMBINE(GPS_SENTENCE_GPGGA, 5): - location.rawNewLngData.negative = term[0] == 'W'; - break; - case COMBINE(GPS_SENTENCE_GPRMC, 7): // Speed (GPRMC) - speed.set(term); - break; - case COMBINE(GPS_SENTENCE_GPRMC, 8): // Course (GPRMC) - course.set(term); - break; - case COMBINE(GPS_SENTENCE_GPRMC, 9): // Date (GPRMC) - date.setDate(term); - break; - case COMBINE(GPS_SENTENCE_GPGGA, 6): // Fix data (GPGGA) - sentenceHasFix = term[0] > '0'; - break; - case COMBINE(GPS_SENTENCE_GPGGA, 7): // Satellites used (GPGGA) - satellites.set(term); - break; - case COMBINE(GPS_SENTENCE_GPGGA, 8): // HDOP - hdop.set(term); - break; - case COMBINE(GPS_SENTENCE_GPGGA, 9): // Altitude (GPGGA) - altitude.set(term); - break; + { + case COMBINE(GPS_SENTENCE_GPRMC, 1): // Time in both sentences + case COMBINE(GPS_SENTENCE_GPGGA, 1): + time.setTime(term); + break; + case COMBINE(GPS_SENTENCE_GPRMC, 2): // GPRMC validity + sentenceHasFix = term[0] == 'A'; + break; + case COMBINE(GPS_SENTENCE_GPRMC, 3): // Latitude + case COMBINE(GPS_SENTENCE_GPGGA, 2): + location.setLatitude(term); + break; + case COMBINE(GPS_SENTENCE_GPRMC, 4): // N/S + case COMBINE(GPS_SENTENCE_GPGGA, 3): + location.rawNewLatData.negative = term[0] == 'S'; + break; + case COMBINE(GPS_SENTENCE_GPRMC, 5): // Longitude + case COMBINE(GPS_SENTENCE_GPGGA, 4): + location.setLongitude(term); + break; + case COMBINE(GPS_SENTENCE_GPRMC, 6): // E/W + case COMBINE(GPS_SENTENCE_GPGGA, 5): + location.rawNewLngData.negative = term[0] == 'W'; + break; + case COMBINE(GPS_SENTENCE_GPRMC, 7): // Speed (GPRMC) + speed.set(term); + break; + case COMBINE(GPS_SENTENCE_GPRMC, 8): // Course (GPRMC) + course.set(term); + break; + case COMBINE(GPS_SENTENCE_GPRMC, 9): // Date (GPRMC) + date.setDate(term); + break; + case COMBINE(GPS_SENTENCE_GPGGA, 6): // Fix data (GPGGA) + sentenceHasFix = term[0] > '0'; + location.newFixQuality = sentenceHasFix ? (FixQuality)(term[0] - '0') : Invalid; + break; + case COMBINE(GPS_SENTENCE_GPGGA, 7): // Satellites used (GPGGA) + satellites.set(term); + break; + case COMBINE(GPS_SENTENCE_GPGGA, 8): // HDOP + case COMBINE(GPS_SENTENCE_GPGSA, 16): // HDOP + hdop.set(term); + break; + case COMBINE(GPS_SENTENCE_GPGGA, 9): // Altitude (GPGGA) + altitude.set(term); + break; + case COMBINE(GPS_SENTENCE_GPRMC, 12): + location.newFixMode = (FixMode)term[0]; + break; + case COMBINE(GPS_SENTENCE_GPGSV, 2): // GSV message index + satellitesStats.setMessageSeqNr(term, curSentenceSystem); + break; + case COMBINE(GPS_SENTENCE_GPGSV, 4): // GSV satellite PRN number + case COMBINE(GPS_SENTENCE_GPGSV, 8): + case COMBINE(GPS_SENTENCE_GPGSV, 12): + case COMBINE(GPS_SENTENCE_GPGSV, 16): + satellitesStats.setSatId(term); + break; + case COMBINE(GPS_SENTENCE_GPGSV, 7): // GSV satellite SNR + case COMBINE(GPS_SENTENCE_GPGSV, 11): + case COMBINE(GPS_SENTENCE_GPGSV, 15): + case COMBINE(GPS_SENTENCE_GPGSV, 19): + satellitesStats.setSatSNR(term); + break; + } + // GSA messages have the items sequential, so handle them separately + if (curSentenceType == GPS_SENTENCE_GPGSA) + { + if (!satellitesStats.snrDataPresent) + { + // GSA messages may only be used when no GSV messages are being processed. + // Satellite IDs in GSA messages may be in different order compared to GSV messages. + if (curTermNumber >= 3 && curTermNumber <= 14) + { + satellitesStats.setSatId(term); + } + } + } } // Set custom values as needed - for (TinyGPSCustom *p = customCandidates; p != NULL && strcmp(p->sentenceName, customCandidates->sentenceName) == 0 && p->termNumber <= curTermNumber; p = p->next) - if (p->termNumber == curTermNumber) - p->set(term); + for (TinyGPSCustom *p = customCandidates; + p != NULL && strcmp(p->sentenceName, customCandidates->sentenceName) == 0 && p->termNumber <= curTermNumber; + p = p->next) + { + if (p->termNumber == curTermNumber) { + p->set(term); + } + } return false; } @@ -366,6 +406,27 @@ double TinyGPSLocation::lng() return rawLngData.negative ? -ret : ret; } +void TinyGPSSatellites::commit() +{ + satsTracked = 0; + satsVisible = 0; + bestSNR = 0; + for (byte i = 0; i < _GPS_MAX_ARRAY_LENGTH; ++i) { + if (id[i] != 0) { + if (snr[i] != 0) { + ++satsTracked; + if (snr[i] > bestSNR) { + bestSNR = snr[i]; + } + } + ++satsVisible; + } + } + pos = -1; + lastCommitTime = millis(); + valid = updated = true; +} + void TinyGPSDate::commit() { date = newDate; @@ -380,6 +441,40 @@ void TinyGPSTime::commit() valid = updated = true; } +void TinyGPSSatellites::setSatId(const char *term) +{ + if (pos < _GPS_MAX_ARRAY_LENGTH) { + ++pos; + uint32_t value = atol(term); + if (id[pos] != value) { + id[pos] = static_cast(value); + snr[pos] = 0; + } + } +} + +void TinyGPSSatellites::setSatSNR(const char *term) +{ + if (pos < _GPS_MAX_ARRAY_LENGTH) { + // Do not increase pos, GSV line has ID first and other parameters later + snr[pos] = atol(term); + snrDataPresent = true; + } +} + +void TinyGPSSatellites::setMessageSeqNr(const char *term, uint8_t sentenceSystem) +{ + int32_t seqNr = atol(term); + int32_t newPos = (seqNr - 1) * 4 + (sentenceSystem * _GPS_MAX_NR_ACTIVE_SATELLITES); + if (newPos >= 0 && newPos < _GPS_MAX_ARRAY_LENGTH) { + for (byte i = newPos; i < (newPos + 4) && i < _GPS_MAX_ARRAY_LENGTH; ++i) { + id[i] = 0; + snr[i] = 0; + } + pos = static_cast(newPos - 1); // setSatId will increment pos first. + } +} + void TinyGPSTime::setTime(const char *term) { newTime = (uint32_t)TinyGPSPlus::parseDecimal(term); @@ -487,6 +582,38 @@ void TinyGPSCustom::set(const char *term) strncpy(this->stagingBuffer, term, sizeof(this->stagingBuffer)); } +void TinyGPSPlus::parseSentenceType(const char *term) +{ + curSentenceType = GPS_SENTENCE_OTHER; + curSentenceSystem = GPS_SYSTEM_GPS; + size_t length = strlen(term); + if (length < 5 || term[0] != 'G') { + return; + } + switch (term[1]) { + case 'L': curSentenceSystem = GPS_SYSTEM_GLONASS; break; + case 'A': curSentenceSystem = GPS_SYSTEM_GALILEO; break; + case 'B': curSentenceSystem = GPS_SYSTEM_BEIDOU; break; + } + if (strcmp(&term[2], "RMC") == 0) + { + curSentenceType = GPS_SENTENCE_GPRMC; + } + else if (strcmp(&term[2], "GGA") == 0) + { + curSentenceType = GPS_SENTENCE_GPGGA; + } + else if (strcmp(&term[2], "GSA") == 0) + { + curSentenceType = GPS_SENTENCE_GPGSA; + } + else if (strcmp(&term[2], "GSV") == 0) + { + curSentenceType = GPS_SENTENCE_GPGSV; + } +} + + void TinyGPSPlus::insertCustom(TinyGPSCustom *pElt, const char *sentenceName, int termNumber) { TinyGPSCustom **ppelt; diff --git a/src/TinyGPS++.h b/src/TinyGPS++.h index 60485cb..ea14e3d 100644 --- a/src/TinyGPS++.h +++ b/src/TinyGPS++.h @@ -39,6 +39,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define _GPS_KM_PER_METER 0.001 #define _GPS_FEET_PER_METER 3.2808399 #define _GPS_MAX_FIELD_SIZE 15 +#define _GPS_MAX_NR_ACTIVE_SATELLITES 16 // NMEA allows upto 12, some receivers report more using GSV strings +//#define _GPS_MAX_NR_SYSTEMS 3 // GPS, GLONASS, GALILEO +#define _GPS_MAX_NR_SYSTEMS 2 // GPS, GLONASS +#define _GPS_MAX_ARRAY_LENGTH (_GPS_MAX_NR_ACTIVE_SATELLITES * _GPS_MAX_NR_SYSTEMS) struct RawDegrees { @@ -74,6 +78,50 @@ struct TinyGPSLocation void setLongitude(const char *term); }; +struct TinyGPSSatellites +{ + friend class TinyGPSPlus; +public: + bool isValid() const { return valid; } + bool isUpdated() const { return updated; } + uint32_t age() const { return valid ? millis() - lastCommitTime : (uint32_t)ULONG_MAX; } + uint8_t nrSatsTracked() const { return satsTracked; } + uint8_t nrSatsVisible() const { return satsVisible; } + uint8_t getBestSNR() const { return bestSNR; } + + TinyGPSSatellites() : valid(false), updated(false), pos(-1), bestSNR(0), satsTracked(0), satsVisible(0), snrDataPresent(false) + {} + + uint8_t id[_GPS_MAX_ARRAY_LENGTH] = {0}; + uint8_t snr[_GPS_MAX_ARRAY_LENGTH] = {0}; + +private: + bool valid, updated; + /* + Satellite IDs: + - 01 ~ 32 are for GPS + - 33 ~ 64 are for SBAS (PRN minus 87) + - 65 ~ 96 are for GLONASS (64 plus slot numbers) + - 193 ~ 197 are for QZSS + - 01 ~ 37 are for Beidou (BD PRN). + GPS and Beidou satellites are differentiated by the GP and BD prefix. + */ + int8_t pos; // Use signed int, only increase pos to set satellite ID + uint8_t bestSNR; + uint8_t satsTracked; + uint8_t satsVisible; + bool snrDataPresent; + uint32_t lastCommitTime; + + void commit(); + void setSatId(const char *term); + void setSatSNR(const char *term); + + // GSV messages form a sequence, so the initial position in the array must + // be set before inserting values. + void setMessageSeqNr(const char *term, uint8_t sentenceSystem); +}; + struct TinyGPSDate { friend class TinyGPSPlus; @@ -231,6 +279,7 @@ class TinyGPSPlus TinyGPSAltitude altitude; TinyGPSInteger satellites; TinyGPSHDOP hdop; + TinyGPSSatellites satellitesStats; static const char *libraryVersion() { return _GPS_VERSION; } @@ -247,13 +296,30 @@ class TinyGPSPlus uint32_t passedChecksum() const { return passedChecksumCount; } private: - enum {GPS_SENTENCE_GPGGA, GPS_SENTENCE_GPRMC, GPS_SENTENCE_OTHER}; + enum { + GPS_SENTENCE_GPGGA, // GGA - Global Positioning System Fix Data + GPS_SENTENCE_GPRMC, // RMC - Recommended Minimum Navigation Information + + GPS_SENTENCE_GPGSA, // GSA - GPS DOP and active satellites + GPS_SENTENCE_GPGSV, // GSV - Satellites in view + + GPS_SENTENCE_OTHER}; + + enum { + GPS_SYSTEM_GPS = 0, // GP: GPS, SBAS, QZSS & GN: Any combination of GNSS + GPS_SYSTEM_GLONASS, + GPS_SYSTEM_GALILEO, + GPS_SYSTEM_BEIDOU + }; + + void parseSentenceType(const char *term); // parsing state variables uint8_t parity; bool isChecksumTerm; char term[_GPS_MAX_FIELD_SIZE]; uint8_t curSentenceType; + uint8_t curSentenceSystem; uint8_t curTermNumber; uint8_t curTermOffset; bool sentenceHasFix;